diff --git a/src/game.rs b/src/game.rs index 1cfbe48..b51fbe8 100644 --- a/src/game.rs +++ b/src/game.rs @@ -8,7 +8,7 @@ use crate::events::GameEvent; use crate::map::builder::Map; use crate::map::direction::Direction; use crate::systems::blinking::Blinking; -use crate::systems::{self, ghost_collision_system, MovementModifiers}; +use crate::systems::{self, ghost_collision_system, present_system, MovementModifiers}; use crate::systems::movement::{BufferedDirection, Position, Velocity}; use crate::systems::profiling::SystemId; @@ -24,10 +24,10 @@ use crate::texture::animated::AnimatedTexture; use bevy_ecs::event::EventRegistry; use bevy_ecs::observer::Trigger; use bevy_ecs::schedule::{IntoScheduleConfigs, Schedule, SystemSet}; -use bevy_ecs::system::{NonSendMut, Res, ResMut}; +use bevy_ecs::system::ResMut; use bevy_ecs::world::World; use sdl2::image::LoadTexture; -use sdl2::render::{Canvas, ScaleMode, TextureCreator}; +use sdl2::render::{BlendMode, Canvas, ScaleMode, TextureCreator}; use sdl2::rwops::RWops; use sdl2::video::{Window, WindowContext}; use sdl2::EventPump; @@ -104,6 +104,8 @@ impl Game { let mut debug_texture = texture_creator .create_texture_target(None, output_size.0, output_size.1) .map_err(|e| GameError::Sdl(e.to_string()))?; + + debug_texture.set_blend_mode(BlendMode::Blend); debug_texture.set_scale_mode(ScaleMode::Nearest); let font_data = get_asset_bytes(Asset::Font)?; @@ -248,23 +250,10 @@ impl Game { let blinking_system = profile(SystemId::Blinking, blinking_system); let directional_render_system = profile(SystemId::DirectionalRender, directional_render_system); let dirty_render_system = profile(SystemId::DirtyRender, dirty_render_system); - let hud_render_system = profile(SystemId::HudRender, hud_render_system); let render_system = profile(SystemId::Render, render_system); + let hud_render_system = profile(SystemId::HudRender, hud_render_system); let debug_render_system = profile(SystemId::DebugRender, debug_render_system); - - let present_system = profile( - SystemId::Present, - |mut canvas: NonSendMut<&mut Canvas>, debug_state: Res, mut dirty: ResMut| { - if dirty.0 || debug_state.enabled { - // Only copy backbuffer to main canvas if debug rendering is off - // (debug rendering draws directly to main canvas) - if !debug_state.enabled { - canvas.present(); - } - dirty.0 = false; - } - }, - ); + let present_system = profile(SystemId::Present, present_system); schedule.add_systems(( ( diff --git a/src/systems/debug.rs b/src/systems/debug.rs index 2b95c70..5a15b57 100644 --- a/src/systems/debug.rs +++ b/src/systems/debug.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use crate::constants::BOARD_PIXEL_OFFSET; use crate::map::builder::Map; -use crate::systems::{BackbufferResource, Collider, CursorPosition, Position, SystemTimings}; +use crate::systems::{Collider, CursorPosition, Position, SystemTimings}; use bevy_ecs::resource::Resource; use bevy_ecs::system::{NonSendMut, Query, Res}; use glam::{IVec2, UVec2, Vec2}; @@ -89,7 +89,6 @@ fn render_timing_display( #[allow(clippy::too_many_arguments)] pub fn debug_render_system( mut canvas: NonSendMut<&mut Canvas>, - backbuffer: NonSendMut, mut debug_texture: NonSendMut, debug_font: NonSendMut, debug_state: Res, @@ -104,18 +103,6 @@ pub fn debug_render_system( let scale = (UVec2::from(canvas.output_size().unwrap()).as_vec2() / UVec2::from(canvas.logical_size()).as_vec2()).min_element(); - // Copy the current backbuffer to the debug texture - canvas - .with_texture_canvas(&mut debug_texture.0, |debug_canvas| { - // Clear the debug canvas - debug_canvas.set_draw_color(Color::BLACK); - debug_canvas.clear(); - - // Copy the backbuffer to the debug canvas - debug_canvas.copy(&backbuffer.0, None, None).unwrap(); - }) - .unwrap(); - // Get texture creator before entering the closure to avoid borrowing conflicts let mut texture_creator = canvas.texture_creator(); let font = &debug_font.0; @@ -128,8 +115,11 @@ pub fn debug_render_system( // Draw debug info on the high-resolution debug texture canvas .with_texture_canvas(&mut debug_texture.0, |debug_canvas| { - // Find the closest node to the cursor + // Clear the debug canvas + debug_canvas.set_draw_color(Color::RGBA(0, 0, 0, 0)); + debug_canvas.clear(); + // Find the closest node to the cursor let closest_node = if let Some(cursor_world_pos) = cursor_world_pos { map.graph .nodes() @@ -214,8 +204,4 @@ pub fn debug_render_system( render_timing_display(debug_canvas, &mut texture_creator, &timings, font); }) .unwrap(); - - // Draw the debug texture directly onto the main canvas at full resolution - canvas.copy(&debug_texture.0, None, None).unwrap(); - canvas.present(); } diff --git a/src/systems/render.rs b/src/systems/render.rs index 1e95cdd..e46dc6a 100644 --- a/src/systems/render.rs +++ b/src/systems/render.rs @@ -1,7 +1,10 @@ use crate::constants::CANVAS_SIZE; use crate::error::{GameError, TextureError}; use crate::map::builder::Map; -use crate::systems::{DeltaTime, DirectionalAnimated, Position, Renderable, ScoreResource, StartupSequence, Velocity}; +use crate::systems::{ + DebugState, DebugTextureResource, DeltaTime, DirectionalAnimated, Position, Renderable, ScoreResource, StartupSequence, + Velocity, +}; use crate::texture::sprite::SpriteAtlas; use crate::texture::text::TextTexture; use bevy_ecs::component::Component; @@ -13,7 +16,7 @@ use bevy_ecs::resource::Resource; use bevy_ecs::system::{NonSendMut, Query, Res, ResMut}; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; -use sdl2::render::{Canvas, Texture}; +use sdl2::render::{BlendMode, Canvas, Texture}; use sdl2::video::Window; #[derive(Resource, Default)] @@ -25,10 +28,11 @@ pub struct Hidden; #[allow(clippy::type_complexity)] pub fn dirty_render_system( mut dirty: ResMut, - changed_renderables: Query<(), Or<(Changed, Changed)>>, + changed: Query<(), Or<(Changed, Changed)>>, + removed_hidden: RemovedComponents, removed_renderables: RemovedComponents, ) { - if !changed_renderables.is_empty() || !removed_renderables.is_empty() { + if !changed.is_empty() || !removed_hidden.is_empty() || !removed_renderables.is_empty() { dirty.0 = true; } } @@ -74,56 +78,59 @@ pub struct BackbufferResource(pub Texture<'static>); /// Renders the HUD (score, lives, etc.) on top of the game. pub fn hud_render_system( + mut backbuffer: NonSendMut, mut canvas: NonSendMut<&mut Canvas>, mut atlas: NonSendMut, score: Res, startup: Res, mut errors: EventWriter, ) { - let mut text_renderer = TextTexture::new(1.0); + 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 = 3; // TODO: Get from actual lives resource - let lives_text = format!("{lives}UP HIGH SCORE "); - let lives_position = glam::UVec2::new(4 + 8 * 3, 2); // x_offset + lives_offset * 8, y_offset + // Render lives and high score text in white + let lives = 3; // TODO: Get from actual lives resource + let lives_text = format!("{lives}UP 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(&mut canvas, &mut atlas, &lives_text, lives_position) { - errors.write(TextureError::RenderFailed(format!("Failed to render lives text: {}", e)).into()); - } - - // Render score text in yellow (Pac-Man's color) - 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(&mut canvas, &mut atlas, &score_text, score_position) { - errors.write(TextureError::RenderFailed(format!("Failed to render score text: {}", e)).into()); - } - - // Render text based on StartupSequence stage - if matches!( - *startup, - StartupSequence::TextOnly { .. } | 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(&mut canvas, &mut atlas, ready_text, ready_position, Color::YELLOW) { - errors.write(TextureError::RenderFailed(format!("Failed to render READY text: {}", e)).into()); + 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()); } - if matches!(*startup, 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); + // Render score text in yellow (Pac-Man's color) + 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_with_color(&mut canvas, &mut atlas, player_one_text, player_one_position, Color::CYAN) - { - errors.write(TextureError::RenderFailed(format!("Failed to render PLAYER ONE text: {}", e)).into()); + 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 text based on StartupSequence stage + if matches!( + *startup, + StartupSequence::TextOnly { .. } | 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!(*startup, 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)] @@ -184,3 +191,25 @@ pub fn render_system( canvas.copy(&backbuffer.0, None, None).unwrap(); } + +pub fn present_system( + mut canvas: NonSendMut<&mut Canvas>, + mut dirty: ResMut, + backbuffer: NonSendMut, + debug_texture: NonSendMut, + debug_state: Res, +) { + if dirty.0 { + // Copy the backbuffer to the main canvas + canvas.copy(&backbuffer.0, None, None).unwrap(); + + // Copy the debug texture to the canvas + if debug_state.enabled { + canvas.set_blend_mode(BlendMode::Blend); + canvas.copy(&debug_texture.0, None, None).unwrap(); + } + + canvas.present(); + dirty.0 = false; + } +}