From ce8ea347e1c679dc20053d39d1e6a6e834a09643 Mon Sep 17 00:00:00 2001 From: Ryan Walters Date: Tue, 9 Sep 2025 17:00:32 -0500 Subject: [PATCH] refactor: reorganize hud-related elements into systems/hud submodule --- .cargo/config.toml | 2 +- src/systems/hud/lives.rs | 89 ++++++++++++++ src/systems/hud/mod.rs | 7 ++ src/systems/hud/score.rs | 86 ++++++++++++++ src/systems/hud/touch.rs | 81 +++++++++++++ src/systems/mod.rs | 2 + src/systems/render.rs | 242 +-------------------------------------- 7 files changed, 270 insertions(+), 239 deletions(-) create mode 100644 src/systems/hud/lives.rs create mode 100644 src/systems/hud/mod.rs create mode 100644 src/systems/hud/score.rs create mode 100644 src/systems/hud/touch.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 275e984..324caee 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,7 +7,7 @@ rustflags = [ ] runner = "node" -# despite being semantically identical to `target_os = "linux"`, the `cfg(linux)` syntax is not supported here. Who knows why...\ +# despite being semantically identical to `target_os = "linux"`, the `cfg(linux)` syntax is not supported here. Who knows why... # https://github.com/Xevion/Pac-Man/actions/runs/17596477856 [target.'cfg(target_os = "linux")'] rustflags = [ diff --git a/src/systems/hud/lives.rs b/src/systems/hud/lives.rs new file mode 100644 index 0000000..152a68e --- /dev/null +++ b/src/systems/hud/lives.rs @@ -0,0 +1,89 @@ +use std::cmp::Ordering; + +use crate::constants::{BOARD_BOTTOM_PIXEL_OFFSET, CANVAS_SIZE, CELL_SIZE}; +use crate::error::GameError; +use crate::map::direction::Direction; +use crate::systems::{PixelPosition, PlayerLife, PlayerLives, Renderable}; +use crate::texture::sprite::SpriteAtlas; +use crate::texture::sprites::{GameSprite, PacmanSprite}; +use bevy_ecs::entity::Entity; +use bevy_ecs::event::EventWriter; +use bevy_ecs::system::{Commands, NonSendMut, Query, Res}; +use glam::Vec2; + +/// Calculates the pixel position for a life sprite based on its index +fn calculate_life_sprite_position(index: u32) -> Vec2 { + let start_x = CELL_SIZE * 2; // 2 cells from left + let start_y = CANVAS_SIZE.y - BOARD_BOTTOM_PIXEL_OFFSET.y + (CELL_SIZE / 2) + 1; // In bottom area + let sprite_spacing = CELL_SIZE + CELL_SIZE / 2; // 1.5 cells between sprites + + let x = start_x + ((index as f32) * (sprite_spacing as f32 * 1.5)).round() as u32; + let y = start_y - CELL_SIZE / 2; + + Vec2::new((x + CELL_SIZE) as f32, (y + CELL_SIZE) as f32) +} + +/// System that manages player life sprite entities. +/// Spawns and despawns life sprite entities based on changes to PlayerLives resource. +/// Each life sprite is positioned based on its index (0, 1, 2, etc. from left to right). +pub fn player_life_sprite_system( + mut commands: Commands, + atlas: NonSendMut, + current_life_sprites: Query<(Entity, &PlayerLife)>, + player_lives: Res, + mut errors: EventWriter, +) { + let displayed_lives = player_lives.0.saturating_sub(1); + + // Get current life sprite entities, sorted by index + let mut current_sprites: Vec<_> = current_life_sprites.iter().collect(); + current_sprites.sort_by_key(|(_, life)| life.index); + let current_count = current_sprites.len() as u8; + + // Calculate the difference + let diff = (displayed_lives as i8) - (current_count as i8); + + match diff.cmp(&0) { + // Ignore when the number of lives displayed is correct + Ordering::Equal => {} + // Spawn new life sprites + Ordering::Greater => { + let life_sprite = match atlas.get_tile(&GameSprite::Pacman(PacmanSprite::Moving(Direction::Left, 1)).to_path()) { + Ok(sprite) => sprite, + Err(e) => { + errors.write(e.into()); + return; + } + }; + + for i in 0..diff { + let position = calculate_life_sprite_position(i as u32); + + commands.spawn(( + PlayerLife { index: i as u32 }, + Renderable { + sprite: life_sprite, + layer: 255, // High layer to render on top + }, + PixelPosition { + pixel_position: position, + }, + )); + } + } + // Remove excess life sprites (highest indices first) + Ordering::Less => { + let to_remove = diff.unsigned_abs(); + let sprites_to_remove: Vec<_> = current_sprites + .iter() + .rev() // Start from highest index + .take(to_remove as usize) + .map(|(entity, _)| *entity) + .collect(); + + for entity in sprites_to_remove { + commands.entity(entity).despawn(); + } + } + } +} diff --git a/src/systems/hud/mod.rs b/src/systems/hud/mod.rs new file mode 100644 index 0000000..5477855 --- /dev/null +++ b/src/systems/hud/mod.rs @@ -0,0 +1,7 @@ +pub mod lives; +pub mod score; +pub mod touch; + +pub use self::lives::*; +pub use self::score::*; +pub use self::touch::*; diff --git a/src/systems/hud/score.rs b/src/systems/hud/score.rs new file mode 100644 index 0000000..99e9531 --- /dev/null +++ b/src/systems/hud/score.rs @@ -0,0 +1,86 @@ +use crate::constants; +use crate::error::{GameError, TextureError}; +use crate::systems::{BackbufferResource, GameStage, ScoreResource, StartupSequence}; +use crate::texture::sprite::SpriteAtlas; +use crate::texture::text::TextTexture; +use bevy_ecs::event::EventWriter; +use bevy_ecs::system::{NonSendMut, Res}; +use sdl2::pixels::Color; +use sdl2::render::Canvas; +use sdl2::video::Window; + +/// Renders the HUD (score, lives, etc.) on top of the game. +#[allow(clippy::too_many_arguments)] +pub fn hud_render_system( + mut backbuffer: NonSendMut, + mut canvas: NonSendMut<&mut Canvas>, + mut atlas: NonSendMut, + score: Res, + stage: Res, + mut errors: EventWriter, +) { + let _ = canvas.with_texture_canvas(&mut backbuffer.0, |canvas| { + let mut text_renderer = TextTexture::new(1.0); + + // Render lives and high score text in white + let lives_text = "1UP HIGH SCORE "; + let lives_position = glam::UVec2::new(4 + 8 * 3, 2); // x_offset + lives_offset * 8, y_offset + + if let Err(e) = text_renderer.render(canvas, &mut atlas, lives_text, lives_position) { + errors.write(TextureError::RenderFailed(format!("Failed to render lives text: {}", e)).into()); + } + + // Render score text + let score_text = format!("{:02}", score.0); + let score_offset = 7 - (score_text.len() as i32); + let score_position = glam::UVec2::new(4 + 8 * score_offset as u32, 10); // x_offset + score_offset * 8, 8 + y_offset + + if let Err(e) = text_renderer.render(canvas, &mut atlas, &score_text, score_position) { + errors.write(TextureError::RenderFailed(format!("Failed to render score text: {}", e)).into()); + } + + // Render high score text + let high_score_text = format!("{:02}", score.0); + let high_score_offset = 17 - (high_score_text.len() as i32); + let high_score_position = glam::UVec2::new(4 + 8 * high_score_offset as u32, 10); // x_offset + score_offset * 8, 8 + y_offset + if let Err(e) = text_renderer.render(canvas, &mut atlas, &high_score_text, high_score_position) { + errors.write(TextureError::RenderFailed(format!("Failed to render high score text: {}", e)).into()); + } + + // Render GAME OVER text + if matches!(*stage, GameStage::GameOver) { + let game_over_text = "GAME OVER"; + let game_over_width = text_renderer.text_width(game_over_text); + let game_over_position = glam::UVec2::new((constants::CANVAS_SIZE.x - game_over_width) / 2, 160); + if let Err(e) = text_renderer.render_with_color(canvas, &mut atlas, game_over_text, game_over_position, Color::RED) { + errors.write(TextureError::RenderFailed(format!("Failed to render GAME OVER text: {}", e)).into()); + } + } + + // Render text based on StartupSequence stage + if matches!( + *stage, + GameStage::Starting(StartupSequence::TextOnly { .. }) + | GameStage::Starting(StartupSequence::CharactersVisible { .. }) + ) { + let ready_text = "READY!"; + let ready_width = text_renderer.text_width(ready_text); + let ready_position = glam::UVec2::new((constants::CANVAS_SIZE.x - ready_width) / 2, 160); + if let Err(e) = text_renderer.render_with_color(canvas, &mut atlas, ready_text, ready_position, Color::YELLOW) { + errors.write(TextureError::RenderFailed(format!("Failed to render READY text: {}", e)).into()); + } + + if matches!(*stage, GameStage::Starting(StartupSequence::TextOnly { .. })) { + let player_one_text = "PLAYER ONE"; + let player_one_width = text_renderer.text_width(player_one_text); + let player_one_position = glam::UVec2::new((constants::CANVAS_SIZE.x - player_one_width) / 2, 113); + + if let Err(e) = + text_renderer.render_with_color(canvas, &mut atlas, player_one_text, player_one_position, Color::CYAN) + { + errors.write(TextureError::RenderFailed(format!("Failed to render PLAYER ONE text: {}", e)).into()); + } + } + } + }); +} diff --git a/src/systems/hud/touch.rs b/src/systems/hud/touch.rs new file mode 100644 index 0000000..91abeae --- /dev/null +++ b/src/systems/hud/touch.rs @@ -0,0 +1,81 @@ +use crate::error::{GameError, TextureError}; +use crate::systems::{BackbufferResource, TouchState}; +use bevy_ecs::event::EventWriter; +use bevy_ecs::system::{NonSendMut, Res}; +use sdl2::pixels::Color; +use sdl2::rect::Point; +use sdl2::render::{BlendMode, Canvas}; +use sdl2::video::Window; + +/// Renders touch UI overlay for mobile/testing. +pub fn touch_ui_render_system( + mut backbuffer: NonSendMut, + mut canvas: NonSendMut<&mut Canvas>, + touch_state: Res, + mut errors: EventWriter, +) { + if let Some(ref touch_data) = touch_state.active_touch { + let _ = canvas.with_texture_canvas(&mut backbuffer.0, |canvas| { + // Set blend mode for transparency + canvas.set_blend_mode(BlendMode::Blend); + + // Draw semi-transparent circle at touch start position + canvas.set_draw_color(Color::RGBA(255, 255, 255, 100)); + let center = Point::new(touch_data.start_pos.x as i32, touch_data.start_pos.y as i32); + + // Draw a simple circle by drawing filled rectangles (basic approach) + let radius = 30; + for dy in -radius..=radius { + for dx in -radius..=radius { + if dx * dx + dy * dy <= radius * radius { + let point = Point::new(center.x + dx, center.y + dy); + if let Err(e) = canvas.draw_point(point) { + errors.write(TextureError::RenderFailed(format!("Touch UI render error: {}", e)).into()); + return; + } + } + } + } + + // Draw direction indicator if we have a direction + if let Some(direction) = touch_data.current_direction { + canvas.set_draw_color(Color::RGBA(0, 255, 0, 150)); + + // Draw arrow indicating direction + let arrow_length = 40; + let (dx, dy) = match direction { + crate::map::direction::Direction::Up => (0, -arrow_length), + crate::map::direction::Direction::Down => (0, arrow_length), + crate::map::direction::Direction::Left => (-arrow_length, 0), + crate::map::direction::Direction::Right => (arrow_length, 0), + }; + + let end_point = Point::new(center.x + dx, center.y + dy); + if let Err(e) = canvas.draw_line(center, end_point) { + errors.write(TextureError::RenderFailed(format!("Touch arrow render error: {}", e)).into()); + } + + // Draw arrowhead (simple approach) + let arrow_size = 8; + match direction { + crate::map::direction::Direction::Up => { + let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y + arrow_size)); + let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y + arrow_size)); + } + crate::map::direction::Direction::Down => { + let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y - arrow_size)); + let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y - arrow_size)); + } + crate::map::direction::Direction::Left => { + let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y - arrow_size)); + let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y + arrow_size)); + } + crate::map::direction::Direction::Right => { + let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y - arrow_size)); + let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y + arrow_size)); + } + } + } + }); + } +} diff --git a/src/systems/mod.rs b/src/systems/mod.rs index d9774de..f1ad0df 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -15,6 +15,7 @@ pub mod blinking; pub mod collision; pub mod common; pub mod ghost; +pub mod hud; pub mod input; pub mod item; pub mod lifetime; @@ -31,6 +32,7 @@ pub use self::collision::*; pub use self::common::*; pub use self::debug::*; pub use self::ghost::*; +pub use self::hud::*; pub use self::input::*; pub use self::item::*; pub use self::lifetime::*; diff --git a/src/systems/render.rs b/src/systems/render.rs index e7e4cd1..0757f27 100644 --- a/src/systems/render.rs +++ b/src/systems/render.rs @@ -1,29 +1,21 @@ +use crate::error::{GameError, TextureError}; use crate::map::builder::Map; -use crate::map::direction::Direction; use crate::systems::{ - debug_render_system, BatchedLinesResource, Collider, CursorPosition, DebugState, DebugTextureResource, GameStage, PlayerLife, - PlayerLives, Position, ScoreResource, StartupSequence, SystemId, SystemTimings, TouchState, TtfAtlasResource, + debug_render_system, BatchedLinesResource, Collider, CursorPosition, DebugState, DebugTextureResource, Position, SystemId, + SystemTimings, TtfAtlasResource, }; use crate::texture::sprite::{AtlasTile, SpriteAtlas}; -use crate::texture::sprites::{GameSprite, PacmanSprite}; -use crate::texture::text::TextTexture; -use crate::{ - constants::{BOARD_BOTTOM_PIXEL_OFFSET, CANVAS_SIZE, CELL_SIZE}, - error::{GameError, TextureError}, -}; use bevy_ecs::component::Component; use bevy_ecs::entity::Entity; use bevy_ecs::event::EventWriter; use bevy_ecs::query::{Changed, Or, With, Without}; use bevy_ecs::removal_detection::RemovedComponents; use bevy_ecs::resource::Resource; -use bevy_ecs::system::{Commands, NonSendMut, Query, Res, ResMut}; +use bevy_ecs::system::{NonSendMut, Query, Res, ResMut}; use glam::Vec2; -use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; use sdl2::render::{BlendMode, Canvas, Texture}; use sdl2::video::Window; -use std::cmp::Ordering; use std::time::Instant; /// A component for entities that have a sprite, with a layer for ordering. @@ -64,244 +56,18 @@ pub fn dirty_render_system( } } -/// System that manages player life sprite entities. -/// Spawns and despawns life sprite entities based on changes to PlayerLives resource. -/// Each life sprite is positioned based on its index (0, 1, 2, etc. from left to right). -pub fn player_life_sprite_system( - mut commands: Commands, - atlas: NonSendMut, - current_life_sprites: Query<(Entity, &PlayerLife)>, - player_lives: Res, - mut errors: EventWriter, -) { - let displayed_lives = player_lives.0.saturating_sub(1); - - // Get current life sprite entities, sorted by index - let mut current_sprites: Vec<_> = current_life_sprites.iter().collect(); - current_sprites.sort_by_key(|(_, life)| life.index); - let current_count = current_sprites.len() as u8; - - // Calculate the difference - let diff = (displayed_lives as i8) - (current_count as i8); - - match diff.cmp(&0) { - // Ignore when the number of lives displayed is correct - Ordering::Equal => {} - // Spawn new life sprites - Ordering::Greater => { - let life_sprite = match atlas.get_tile(&GameSprite::Pacman(PacmanSprite::Moving(Direction::Left, 1)).to_path()) { - Ok(sprite) => sprite, - Err(e) => { - errors.write(e.into()); - return; - } - }; - - for i in 0..diff { - let position = calculate_life_sprite_position(i as u32); - - commands.spawn(( - PlayerLife { index: i as u32 }, - Renderable { - sprite: life_sprite, - layer: 255, // High layer to render on top - }, - PixelPosition { - pixel_position: position, - }, - )); - } - } - // Remove excess life sprites (highest indices first) - Ordering::Less => { - let to_remove = diff.unsigned_abs(); - let sprites_to_remove: Vec<_> = current_sprites - .iter() - .rev() // Start from highest index - .take(to_remove as usize) - .map(|(entity, _)| *entity) - .collect(); - - for entity in sprites_to_remove { - commands.entity(entity).despawn(); - } - } - } -} - /// Component for Renderables to store an exact pixel position #[derive(Component)] pub struct PixelPosition { pub pixel_position: Vec2, } -/// Calculates the pixel position for a life sprite based on its index -fn calculate_life_sprite_position(index: u32) -> Vec2 { - let start_x = CELL_SIZE * 2; // 2 cells from left - let start_y = CANVAS_SIZE.y - BOARD_BOTTOM_PIXEL_OFFSET.y + (CELL_SIZE / 2) + 1; // In bottom area - let sprite_spacing = CELL_SIZE + CELL_SIZE / 2; // 1.5 cells between sprites - - let x = start_x + ((index as f32) * (sprite_spacing as f32 * 1.5)).round() as u32; - let y = start_y - CELL_SIZE / 2; - - Vec2::new((x + CELL_SIZE) as f32, (y + CELL_SIZE) as f32) -} - /// A non-send resource for the map texture. This just wraps the texture with a type so it can be differentiated when exposed as a resource. pub struct MapTextureResource(pub Texture); /// A non-send resource for the backbuffer texture. This just wraps the texture with a type so it can be differentiated when exposed as a resource. pub struct BackbufferResource(pub Texture); -/// Renders touch UI overlay for mobile/testing. -pub fn touch_ui_render_system( - mut backbuffer: NonSendMut, - mut canvas: NonSendMut<&mut Canvas>, - touch_state: Res, - mut errors: EventWriter, -) { - if let Some(ref touch_data) = touch_state.active_touch { - let _ = canvas.with_texture_canvas(&mut backbuffer.0, |canvas| { - // Set blend mode for transparency - canvas.set_blend_mode(BlendMode::Blend); - - // Draw semi-transparent circle at touch start position - canvas.set_draw_color(Color::RGBA(255, 255, 255, 100)); - let center = Point::new(touch_data.start_pos.x as i32, touch_data.start_pos.y as i32); - - // Draw a simple circle by drawing filled rectangles (basic approach) - let radius = 30; - for dy in -radius..=radius { - for dx in -radius..=radius { - if dx * dx + dy * dy <= radius * radius { - let point = Point::new(center.x + dx, center.y + dy); - if let Err(e) = canvas.draw_point(point) { - errors.write(TextureError::RenderFailed(format!("Touch UI render error: {}", e)).into()); - return; - } - } - } - } - - // Draw direction indicator if we have a direction - if let Some(direction) = touch_data.current_direction { - canvas.set_draw_color(Color::RGBA(0, 255, 0, 150)); - - // Draw arrow indicating direction - let arrow_length = 40; - let (dx, dy) = match direction { - crate::map::direction::Direction::Up => (0, -arrow_length), - crate::map::direction::Direction::Down => (0, arrow_length), - crate::map::direction::Direction::Left => (-arrow_length, 0), - crate::map::direction::Direction::Right => (arrow_length, 0), - }; - - let end_point = Point::new(center.x + dx, center.y + dy); - if let Err(e) = canvas.draw_line(center, end_point) { - errors.write(TextureError::RenderFailed(format!("Touch arrow render error: {}", e)).into()); - } - - // Draw arrowhead (simple approach) - let arrow_size = 8; - match direction { - crate::map::direction::Direction::Up => { - let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y + arrow_size)); - let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y + arrow_size)); - } - crate::map::direction::Direction::Down => { - let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y - arrow_size)); - let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y - arrow_size)); - } - crate::map::direction::Direction::Left => { - let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y - arrow_size)); - let _ = canvas.draw_line(end_point, Point::new(end_point.x + arrow_size, end_point.y + arrow_size)); - } - crate::map::direction::Direction::Right => { - let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y - arrow_size)); - let _ = canvas.draw_line(end_point, Point::new(end_point.x - arrow_size, end_point.y + arrow_size)); - } - } - } - }); - } -} - -/// Renders the HUD (score, lives, etc.) on top of the game. -#[allow(clippy::too_many_arguments)] -pub fn hud_render_system( - mut backbuffer: NonSendMut, - mut canvas: NonSendMut<&mut Canvas>, - mut atlas: NonSendMut, - score: Res, - stage: Res, - mut errors: EventWriter, -) { - let _ = canvas.with_texture_canvas(&mut backbuffer.0, |canvas| { - let mut text_renderer = TextTexture::new(1.0); - - // Render lives and high score text in white - let lives_text = "1UP HIGH SCORE "; - let lives_position = glam::UVec2::new(4 + 8 * 3, 2); // x_offset + lives_offset * 8, y_offset - - if let Err(e) = text_renderer.render(canvas, &mut atlas, lives_text, lives_position) { - errors.write(TextureError::RenderFailed(format!("Failed to render lives text: {}", e)).into()); - } - - // Render score text - let score_text = format!("{:02}", score.0); - let score_offset = 7 - (score_text.len() as i32); - let score_position = glam::UVec2::new(4 + 8 * score_offset as u32, 10); // x_offset + score_offset * 8, 8 + y_offset - - if let Err(e) = text_renderer.render(canvas, &mut atlas, &score_text, score_position) { - errors.write(TextureError::RenderFailed(format!("Failed to render score text: {}", e)).into()); - } - - // Render high score text - let high_score_text = format!("{:02}", score.0); - let high_score_offset = 17 - (high_score_text.len() as i32); - let high_score_position = glam::UVec2::new(4 + 8 * high_score_offset as u32, 10); // x_offset + score_offset * 8, 8 + y_offset - if let Err(e) = text_renderer.render(canvas, &mut atlas, &high_score_text, high_score_position) { - errors.write(TextureError::RenderFailed(format!("Failed to render high score text: {}", e)).into()); - } - - // Render GAME OVER text - if matches!(*stage, GameStage::GameOver) { - let game_over_text = "GAME OVER"; - let game_over_width = text_renderer.text_width(game_over_text); - let game_over_position = glam::UVec2::new((CANVAS_SIZE.x - game_over_width) / 2, 160); - if let Err(e) = text_renderer.render_with_color(canvas, &mut atlas, game_over_text, game_over_position, Color::RED) { - errors.write(TextureError::RenderFailed(format!("Failed to render GAME OVER text: {}", e)).into()); - } - } - - // Render text based on StartupSequence stage - if matches!( - *stage, - GameStage::Starting(StartupSequence::TextOnly { .. }) - | GameStage::Starting(StartupSequence::CharactersVisible { .. }) - ) { - let ready_text = "READY!"; - let ready_width = text_renderer.text_width(ready_text); - let ready_position = glam::UVec2::new((CANVAS_SIZE.x - ready_width) / 2, 160); - if let Err(e) = text_renderer.render_with_color(canvas, &mut atlas, ready_text, ready_position, Color::YELLOW) { - errors.write(TextureError::RenderFailed(format!("Failed to render READY text: {}", e)).into()); - } - - if matches!(*stage, GameStage::Starting(StartupSequence::TextOnly { .. })) { - let player_one_text = "PLAYER ONE"; - let player_one_width = text_renderer.text_width(player_one_text); - let player_one_position = glam::UVec2::new((CANVAS_SIZE.x - player_one_width) / 2, 113); - - if let Err(e) = - text_renderer.render_with_color(canvas, &mut atlas, player_one_text, player_one_position, Color::CYAN) - { - errors.write(TextureError::RenderFailed(format!("Failed to render PLAYER ONE text: {}", e)).into()); - } - } - } - }); -} - #[allow(clippy::too_many_arguments)] #[allow(clippy::type_complexity)] pub fn render_system(