mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-07 20:07:46 -06:00
feat: high resolution debug rendering
This commit is contained in:
@@ -223,6 +223,19 @@ impl Graph {
|
|||||||
self.nodes.len()
|
self.nodes.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all nodes in the graph.
|
||||||
|
pub fn nodes(&self) -> impl Iterator<Item = &Node> {
|
||||||
|
self.nodes.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all edges in the graph.
|
||||||
|
pub fn edges(&self) -> impl Iterator<Item = (NodeId, Edge)> + '_ {
|
||||||
|
self.adjacency_list
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(node_id, intersection)| intersection.edges().map(move |edge| (node_id, edge)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds a specific edge from a source node to a target node.
|
/// Finds a specific edge from a source node to a target node.
|
||||||
pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option<Edge> {
|
pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option<Edge> {
|
||||||
self.adjacency_list.get(from)?.edges().find(|edge| edge.target == to)
|
self.adjacency_list.get(from)?.edges().find(|edge| edge.target == to)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use crate::systems::{
|
|||||||
PacmanCollider, PlayerBundle, PlayerControlled, RenderDirty, Renderable, Score, ScoreResource,
|
PacmanCollider, PlayerBundle, PlayerControlled, RenderDirty, Renderable, Score, ScoreResource,
|
||||||
},
|
},
|
||||||
control::player_system,
|
control::player_system,
|
||||||
|
debug::{debug_render_system, DebugState, DebugTextureResource},
|
||||||
input::input_system,
|
input::input_system,
|
||||||
movement::movement_system,
|
movement::movement_system,
|
||||||
profiling::{profile, SystemTimings},
|
profiling::{profile, SystemTimings},
|
||||||
@@ -24,7 +25,14 @@ use crate::systems::{
|
|||||||
};
|
};
|
||||||
use crate::texture::animated::AnimatedTexture;
|
use crate::texture::animated::AnimatedTexture;
|
||||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||||
use bevy_ecs::{event::EventRegistry, observer::Trigger, schedule::Schedule, system::ResMut, world::World};
|
use bevy_ecs::system::NonSendMut;
|
||||||
|
use bevy_ecs::{
|
||||||
|
event::EventRegistry,
|
||||||
|
observer::Trigger,
|
||||||
|
schedule::Schedule,
|
||||||
|
system::{Res, ResMut},
|
||||||
|
world::World,
|
||||||
|
};
|
||||||
use sdl2::image::LoadTexture;
|
use sdl2::image::LoadTexture;
|
||||||
use sdl2::render::{Canvas, ScaleMode, TextureCreator};
|
use sdl2::render::{Canvas, ScaleMode, TextureCreator};
|
||||||
use sdl2::video::{Window, WindowContext};
|
use sdl2::video::{Window, WindowContext};
|
||||||
@@ -72,6 +80,13 @@ impl Game {
|
|||||||
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
map_texture.set_scale_mode(ScaleMode::Nearest);
|
map_texture.set_scale_mode(ScaleMode::Nearest);
|
||||||
|
|
||||||
|
// Create debug texture at output resolution for crisp debug rendering
|
||||||
|
let output_size = canvas.output_size().unwrap();
|
||||||
|
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_scale_mode(ScaleMode::Nearest);
|
||||||
|
|
||||||
// Load atlas and create map texture
|
// Load atlas and create map texture
|
||||||
let atlas_bytes = get_asset_bytes(Asset::Atlas)?;
|
let atlas_bytes = get_asset_bytes(Asset::Atlas)?;
|
||||||
let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| {
|
let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| {
|
||||||
@@ -157,7 +172,7 @@ impl Game {
|
|||||||
},
|
},
|
||||||
entity_type: EntityType::Player,
|
entity_type: EntityType::Player,
|
||||||
collider: Collider {
|
collider: Collider {
|
||||||
size: constants::CELL_SIZE as f32 * 1.25,
|
size: constants::CELL_SIZE as f32 * 1.1,
|
||||||
layer: CollisionLayer::PACMAN,
|
layer: CollisionLayer::PACMAN,
|
||||||
},
|
},
|
||||||
pacman_collider: PacmanCollider,
|
pacman_collider: PacmanCollider,
|
||||||
@@ -168,6 +183,7 @@ impl Game {
|
|||||||
world.insert_non_send_resource(canvas);
|
world.insert_non_send_resource(canvas);
|
||||||
world.insert_non_send_resource(BackbufferResource(backbuffer));
|
world.insert_non_send_resource(BackbufferResource(backbuffer));
|
||||||
world.insert_non_send_resource(MapTextureResource(map_texture));
|
world.insert_non_send_resource(MapTextureResource(map_texture));
|
||||||
|
world.insert_non_send_resource(DebugTextureResource(debug_texture));
|
||||||
|
|
||||||
world.insert_resource(map);
|
world.insert_resource(map);
|
||||||
world.insert_resource(GlobalState { exit: false });
|
world.insert_resource(GlobalState { exit: false });
|
||||||
@@ -176,6 +192,7 @@ impl Game {
|
|||||||
world.insert_resource(Bindings::default());
|
world.insert_resource(Bindings::default());
|
||||||
world.insert_resource(DeltaTime(0f32));
|
world.insert_resource(DeltaTime(0f32));
|
||||||
world.insert_resource(RenderDirty::default());
|
world.insert_resource(RenderDirty::default());
|
||||||
|
world.insert_resource(DebugState::default());
|
||||||
|
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|event: Trigger<GameEvent>, mut state: ResMut<GlobalState>, _score: ResMut<ScoreResource>| match *event {
|
|event: Trigger<GameEvent>, mut state: ResMut<GlobalState>, _score: ResMut<ScoreResource>| match *event {
|
||||||
@@ -188,7 +205,6 @@ impl Game {
|
|||||||
GameEvent::Collision(_a, _b) => {}
|
GameEvent::Collision(_a, _b) => {}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
schedule.add_systems(
|
schedule.add_systems(
|
||||||
(
|
(
|
||||||
profile("input", input_system),
|
profile("input", input_system),
|
||||||
@@ -197,11 +213,29 @@ impl Game {
|
|||||||
profile("collision", collision_system),
|
profile("collision", collision_system),
|
||||||
profile("blinking", blinking_system),
|
profile("blinking", blinking_system),
|
||||||
profile("directional_render", directional_render_system),
|
profile("directional_render", directional_render_system),
|
||||||
|
profile("dirty_render", dirty_render_system),
|
||||||
|
profile("render", render_system),
|
||||||
|
profile("debug_render", debug_render_system),
|
||||||
|
profile(
|
||||||
|
"present",
|
||||||
|
|mut canvas: NonSendMut<&mut Canvas<Window>>,
|
||||||
|
backbuffer: NonSendMut<BackbufferResource>,
|
||||||
|
debug_state: Res<DebugState>,
|
||||||
|
mut dirty: ResMut<RenderDirty>| {
|
||||||
|
if dirty.0 {
|
||||||
|
// Only copy backbuffer to main canvas if debug rendering is off
|
||||||
|
// (debug rendering draws directly to main canvas)
|
||||||
|
if *debug_state == DebugState::Off {
|
||||||
|
canvas.copy(&backbuffer.0, None, None).unwrap();
|
||||||
|
}
|
||||||
|
dirty.0 = false;
|
||||||
|
canvas.present();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
);
|
);
|
||||||
schedule.add_systems((profile("dirty_render", dirty_render_system), profile("render", render_system)).chain());
|
|
||||||
|
|
||||||
// Spawn player
|
// Spawn player
|
||||||
world.spawn(player);
|
world.spawn(player);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
error::GameError,
|
error::GameError,
|
||||||
events::{GameCommand, GameEvent},
|
events::{GameCommand, GameEvent},
|
||||||
systems::components::{GlobalState, PlayerControlled},
|
systems::components::{GlobalState, PlayerControlled},
|
||||||
|
systems::debug::DebugState,
|
||||||
systems::movement::Movable,
|
systems::movement::Movable,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ use crate::{
|
|||||||
pub fn player_system(
|
pub fn player_system(
|
||||||
mut events: EventReader<GameEvent>,
|
mut events: EventReader<GameEvent>,
|
||||||
mut state: ResMut<GlobalState>,
|
mut state: ResMut<GlobalState>,
|
||||||
|
mut debug_state: ResMut<DebugState>,
|
||||||
mut players: Query<&mut Movable, With<PlayerControlled>>,
|
mut players: Query<&mut Movable, With<PlayerControlled>>,
|
||||||
mut errors: EventWriter<GameError>,
|
mut errors: EventWriter<GameError>,
|
||||||
) {
|
) {
|
||||||
@@ -38,6 +40,9 @@ pub fn player_system(
|
|||||||
GameCommand::Exit => {
|
GameCommand::Exit => {
|
||||||
state.exit = true;
|
state.exit = true;
|
||||||
}
|
}
|
||||||
|
GameCommand::ToggleDebug => {
|
||||||
|
*debug_state = debug_state.next();
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
143
src/systems/debug.rs
Normal file
143
src/systems/debug.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
//! Debug rendering system
|
||||||
|
use crate::constants::BOARD_PIXEL_OFFSET;
|
||||||
|
use crate::map::builder::Map;
|
||||||
|
use crate::systems::components::Collider;
|
||||||
|
use crate::systems::movement::Position;
|
||||||
|
use crate::systems::render::BackbufferResource;
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
use sdl2::rect::Rect;
|
||||||
|
use sdl2::render::{Canvas, Texture};
|
||||||
|
use sdl2::video::Window;
|
||||||
|
|
||||||
|
#[derive(Resource, Default, Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub enum DebugState {
|
||||||
|
#[default]
|
||||||
|
Off,
|
||||||
|
Graph,
|
||||||
|
Collision,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugState {
|
||||||
|
pub fn next(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
DebugState::Off => DebugState::Graph,
|
||||||
|
DebugState::Graph => DebugState::Collision,
|
||||||
|
DebugState::Collision => DebugState::Off,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource to hold the debug texture for persistent rendering
|
||||||
|
pub struct DebugTextureResource(pub Texture<'static>);
|
||||||
|
|
||||||
|
/// Transforms a position from logical canvas coordinates to output canvas coordinates
|
||||||
|
fn transform_position(pos: (f32, f32), output_size: (u32, u32), logical_size: (u32, u32)) -> (i32, i32) {
|
||||||
|
let scale_x = output_size.0 as f32 / logical_size.0 as f32;
|
||||||
|
let scale_y = output_size.1 as f32 / logical_size.1 as f32;
|
||||||
|
let scale = scale_x.min(scale_y); // Use the smaller scale to maintain aspect ratio
|
||||||
|
|
||||||
|
let x = (pos.0 * scale) as i32;
|
||||||
|
let y = (pos.1 * scale) as i32;
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms a position from logical canvas coordinates to output canvas coordinates (with board offset)
|
||||||
|
fn transform_position_with_offset(pos: (f32, f32), output_size: (u32, u32), logical_size: (u32, u32)) -> (i32, i32) {
|
||||||
|
let scale_x = output_size.0 as f32 / logical_size.0 as f32;
|
||||||
|
let scale_y = output_size.1 as f32 / logical_size.1 as f32;
|
||||||
|
let scale = scale_x.min(scale_y); // Use the smaller scale to maintain aspect ratio
|
||||||
|
|
||||||
|
let x = ((pos.0 + BOARD_PIXEL_OFFSET.x as f32) * scale) as i32;
|
||||||
|
let y = ((pos.1 + BOARD_PIXEL_OFFSET.y as f32) * scale) as i32;
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms a size from logical canvas coordinates to output canvas coordinates
|
||||||
|
fn transform_size(size: f32, output_size: (u32, u32), logical_size: (u32, u32)) -> u32 {
|
||||||
|
let scale_x = output_size.0 as f32 / logical_size.0 as f32;
|
||||||
|
let scale_y = output_size.1 as f32 / logical_size.1 as f32;
|
||||||
|
let scale = scale_x.min(scale_y); // Use the smaller scale to maintain aspect ratio
|
||||||
|
|
||||||
|
(size * scale) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug_render_system(
|
||||||
|
mut canvas: NonSendMut<&mut Canvas<Window>>,
|
||||||
|
backbuffer: NonSendMut<BackbufferResource>,
|
||||||
|
mut debug_texture: NonSendMut<DebugTextureResource>,
|
||||||
|
debug_state: Res<DebugState>,
|
||||||
|
map: Res<Map>,
|
||||||
|
colliders: Query<(&Collider, &Position)>,
|
||||||
|
) {
|
||||||
|
if *debug_state == DebugState::Off {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get canvas sizes for coordinate transformation
|
||||||
|
let output_size = canvas.output_size().unwrap();
|
||||||
|
let logical_size = canvas.logical_size();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Draw debug info on the high-resolution debug texture
|
||||||
|
canvas
|
||||||
|
.with_texture_canvas(&mut debug_texture.0, |debug_canvas| match *debug_state {
|
||||||
|
DebugState::Graph => {
|
||||||
|
debug_canvas.set_draw_color(Color::RED);
|
||||||
|
for (start_node, end_node) in map.graph.edges() {
|
||||||
|
let start_node = map.graph.get_node(start_node).unwrap().position;
|
||||||
|
let end_node = map.graph.get_node(end_node.target).unwrap().position;
|
||||||
|
|
||||||
|
// Transform positions using common method
|
||||||
|
let (start_x, start_y) =
|
||||||
|
transform_position_with_offset((start_node.x, start_node.y), output_size, logical_size);
|
||||||
|
let (end_x, end_y) = transform_position_with_offset((end_node.x, end_node.y), output_size, logical_size);
|
||||||
|
|
||||||
|
debug_canvas.draw_line((start_x, start_y), (end_x, end_y)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_canvas.set_draw_color(Color::BLUE);
|
||||||
|
for node in map.graph.nodes() {
|
||||||
|
let pos = node.position;
|
||||||
|
|
||||||
|
// Transform position using common method
|
||||||
|
let (x, y) = transform_position_with_offset((pos.x, pos.y), output_size, logical_size);
|
||||||
|
let size = transform_size(4.0, output_size, logical_size);
|
||||||
|
|
||||||
|
debug_canvas
|
||||||
|
.fill_rect(Rect::new(x - (size as i32 / 2), y - (size as i32 / 2), size, size))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugState::Collision => {
|
||||||
|
debug_canvas.set_draw_color(Color::GREEN);
|
||||||
|
for (collider, position) in colliders.iter() {
|
||||||
|
let pos = position.get_pixel_pos(&map.graph).unwrap();
|
||||||
|
|
||||||
|
// Transform position and size using common methods
|
||||||
|
let (x, y) = transform_position((pos.x, pos.y), output_size, logical_size);
|
||||||
|
let size = transform_size(collider.size, output_size, logical_size);
|
||||||
|
|
||||||
|
// Center the collision box on the entity
|
||||||
|
let rect = Rect::new(x - (size as i32 / 2), y - (size as i32 / 2), size, size);
|
||||||
|
debug_canvas.draw_rect(rect).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Draw the debug texture directly onto the main canvas at full resolution
|
||||||
|
canvas.copy(&debug_texture.0, None, None).unwrap();
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ pub mod blinking;
|
|||||||
pub mod collision;
|
pub mod collision;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod control;
|
pub mod control;
|
||||||
|
pub mod debug;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod profiling;
|
pub mod profiling;
|
||||||
|
|||||||
@@ -65,17 +65,13 @@ pub fn render_system(
|
|||||||
mut backbuffer: NonSendMut<BackbufferResource>,
|
mut backbuffer: NonSendMut<BackbufferResource>,
|
||||||
mut atlas: NonSendMut<SpriteAtlas>,
|
mut atlas: NonSendMut<SpriteAtlas>,
|
||||||
map: Res<Map>,
|
map: Res<Map>,
|
||||||
mut dirty: ResMut<RenderDirty>,
|
dirty: Res<RenderDirty>,
|
||||||
renderables: Query<(Entity, &Renderable, &Position)>,
|
renderables: Query<(Entity, &Renderable, &Position)>,
|
||||||
mut errors: EventWriter<GameError>,
|
mut errors: EventWriter<GameError>,
|
||||||
) {
|
) {
|
||||||
if !dirty.0 {
|
if !dirty.0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Clear the main canvas first
|
|
||||||
canvas.set_draw_color(sdl2::pixels::Color::BLACK);
|
|
||||||
canvas.clear();
|
|
||||||
|
|
||||||
// Render to backbuffer
|
// Render to backbuffer
|
||||||
canvas
|
canvas
|
||||||
.with_texture_canvas(&mut backbuffer.0, |backbuffer_canvas| {
|
.with_texture_canvas(&mut backbuffer.0, |backbuffer_canvas| {
|
||||||
@@ -116,14 +112,4 @@ pub fn render_system(
|
|||||||
})
|
})
|
||||||
.err()
|
.err()
|
||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||||
|
|
||||||
// Copy backbuffer to main canvas and present
|
|
||||||
canvas
|
|
||||||
.copy(&backbuffer.0, None, None)
|
|
||||||
.err()
|
|
||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
|
||||||
|
|
||||||
canvas.present();
|
|
||||||
|
|
||||||
dirty.0 = false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user