mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 04:07:52 -06:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f14b3d38a4 | ||
|
|
bf65c34b28 | ||
|
|
89b0790f19 |
@@ -1,6 +1,5 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use glam::Vec2;
|
||||
use sdl2::render::TextureCreator;
|
||||
use sdl2::ttf::Sdl2TtfContext;
|
||||
use sdl2::video::WindowContext;
|
||||
@@ -21,7 +20,6 @@ pub struct App {
|
||||
pub game: Game,
|
||||
last_tick: Instant,
|
||||
focused: bool,
|
||||
_cursor_pos: Vec2,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -81,7 +79,6 @@ impl App {
|
||||
game,
|
||||
focused: true,
|
||||
last_tick: Instant::now(),
|
||||
_cursor_pos: Vec2::ZERO,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
154
src/game.rs
154
src/game.rs
@@ -25,7 +25,9 @@ use crate::systems::{
|
||||
item::item_system,
|
||||
player::player_control_system,
|
||||
profiling::{profile, SystemTimings},
|
||||
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
|
||||
render::{
|
||||
directional_render_system, dirty_render_system, hud_render_system, render_system, BackbufferResource, MapTextureResource,
|
||||
},
|
||||
};
|
||||
use crate::texture::animated::AnimatedTexture;
|
||||
use bevy_ecs::event::EventRegistry;
|
||||
@@ -243,14 +245,12 @@ impl Game {
|
||||
profile(SystemId::Blinking, blinking_system),
|
||||
profile(SystemId::DirectionalRender, directional_render_system),
|
||||
profile(SystemId::DirtyRender, dirty_render_system),
|
||||
profile(SystemId::HudRender, hud_render_system),
|
||||
profile(SystemId::Render, render_system),
|
||||
profile(SystemId::DebugRender, debug_render_system),
|
||||
profile(
|
||||
SystemId::Present,
|
||||
|mut canvas: NonSendMut<&mut Canvas<Window>>,
|
||||
backbuffer: NonSendMut<BackbufferResource>,
|
||||
debug_state: Res<DebugState>,
|
||||
mut dirty: ResMut<RenderDirty>| {
|
||||
|mut canvas: NonSendMut<&mut Canvas<Window>>, debug_state: Res<DebugState>, mut dirty: ResMut<RenderDirty>| {
|
||||
if dirty.0 || debug_state.enabled {
|
||||
// Only copy backbuffer to main canvas if debug rendering is off
|
||||
// (debug rendering draws directly to main canvas)
|
||||
@@ -448,112 +448,6 @@ impl Game {
|
||||
state.exit
|
||||
}
|
||||
|
||||
// fn check_collisions(&mut self) {
|
||||
// // Check Pac-Man vs Items
|
||||
// let potential_collisions = self
|
||||
// .state
|
||||
// .collision_system
|
||||
// .potential_collisions(&self.state.pacman.position());
|
||||
|
||||
// for entity_id in potential_collisions {
|
||||
// if entity_id != self.state.pacman_id {
|
||||
// // Check if this is an item collision
|
||||
// if let Some(item_index) = self.find_item_by_id(entity_id) {
|
||||
// let item = &mut self.state.items[item_index];
|
||||
// if !item.is_collected() {
|
||||
// item.collect();
|
||||
// self.state.score += item.get_score();
|
||||
// self.state.audio.eat();
|
||||
|
||||
// // Handle energizer effects
|
||||
// if matches!(item.item_type, crate::entity::item::ItemType::Energizer) {
|
||||
// // TODO: Make ghosts frightened
|
||||
// tracing::info!("Energizer collected! Ghosts should become frightened.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Check if this is a ghost collision
|
||||
// if let Some(_ghost_index) = self.find_ghost_by_id(entity_id) {
|
||||
// // TODO: Handle Pac-Man being eaten by ghost
|
||||
// tracing::info!("Pac-Man collided with ghost!");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn find_item_by_id(&self, entity_id: EntityId) -> Option<usize> {
|
||||
// self.state.item_ids.iter().position(|&id| id == entity_id)
|
||||
// }
|
||||
|
||||
// fn find_ghost_by_id(&self, entity_id: EntityId) -> Option<usize> {
|
||||
// self.state.ghost_ids.iter().position(|&id| id == entity_id)
|
||||
// }
|
||||
|
||||
// pub fn draw<T: sdl2::render::RenderTarget>(&mut self, canvas: &mut Canvas<T>, backbuffer: &mut Texture) -> GameResult<()> {
|
||||
// // Only render the map texture once and cache it
|
||||
// if !self.state.map_rendered {
|
||||
// let mut map_texture = self
|
||||
// .state
|
||||
// .texture_creator
|
||||
// .create_texture_target(None, constants::CANVAS_SIZE.x, constants::CANVAS_SIZE.y)
|
||||
// .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?;
|
||||
|
||||
// canvas
|
||||
// .with_texture_canvas(&mut map_texture, |map_canvas| {
|
||||
// let mut map_tiles = Vec::with_capacity(35);
|
||||
// for i in 0..35 {
|
||||
// let tile_name = format!("maze/tiles/{}.png", i);
|
||||
// let tile = SpriteAtlas::get_tile(&self.state.atlas, &tile_name).unwrap();
|
||||
// map_tiles.push(tile);
|
||||
// }
|
||||
// MapRenderer::render_map(map_canvas, &mut self.state.atlas, &mut map_tiles);
|
||||
// })
|
||||
// .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?;
|
||||
// self.state.map_texture = Some(map_texture);
|
||||
// self.state.map_rendered = true;
|
||||
// }
|
||||
|
||||
// canvas.set_draw_color(Color::BLACK);
|
||||
// canvas.clear();
|
||||
// if let Some(ref map_texture) = self.state.map_texture {
|
||||
// canvas.copy(map_texture, None, None).unwrap();
|
||||
// }
|
||||
|
||||
// // Render all items
|
||||
// for item in &self.state.items {
|
||||
// if let Err(e) = item.render(canvas, &mut self.state.atlas, &self.state.map.graph) {
|
||||
// tracing::error!("Failed to render item: {}", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Render all ghosts
|
||||
// for ghost in &self.state.ghosts {
|
||||
// if let Err(e) = ghost.render(canvas, &mut self.state.atlas, &self.state.map.graph) {
|
||||
// tracing::error!("Failed to render ghost: {}", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if let Err(e) = self.state.pacman.render(canvas, &mut self.state.atlas, &self.state.map.graph) {
|
||||
// tracing::error!("Failed to render pacman: {}", e);
|
||||
// }
|
||||
|
||||
// if self.state.debug_mode {
|
||||
// if let Err(e) =
|
||||
// self.state
|
||||
// .map
|
||||
// .debug_render_with_cursor(canvas, &mut self.state.text_texture, &mut self.state.atlas, cursor_pos)
|
||||
// {
|
||||
// tracing::error!("Failed to render debug cursor: {}", e);
|
||||
// }
|
||||
// self.render_pathfinding_debug(canvas)?;
|
||||
// }
|
||||
// self.draw_hud(canvas)?;
|
||||
// canvas.present();
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// /// Renders pathfinding debug lines from each ghost to Pac-Man.
|
||||
// ///
|
||||
// /// Each ghost's path is drawn in its respective color with a small offset
|
||||
@@ -617,42 +511,4 @@ impl Game {
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// fn draw_hud<T: sdl2::render::RenderTarget>(&mut self, canvas: &mut Canvas<T>) -> GameResult<()> {
|
||||
// let lives = 3;
|
||||
// let score_text = format!("{:02}", self.state.score);
|
||||
// let x_offset = 4;
|
||||
// let y_offset = 2;
|
||||
// let lives_offset = 3;
|
||||
// let score_offset = 7 - (score_text.len() as i32);
|
||||
// self.state.text_texture.set_scale(1.0);
|
||||
// if let Err(e) = self.state.text_texture.render(
|
||||
// canvas,
|
||||
// &mut self.state.atlas,
|
||||
// &format!("{lives}UP HIGH SCORE "),
|
||||
// glam::UVec2::new(8 * lives_offset as u32 + x_offset, y_offset),
|
||||
// ) {
|
||||
// tracing::error!("Failed to render HUD text: {}", e);
|
||||
// }
|
||||
// if let Err(e) = self.state.text_texture.render(
|
||||
// canvas,
|
||||
// &mut self.state.atlas,
|
||||
// &score_text,
|
||||
// glam::UVec2::new(8 * score_offset as u32 + x_offset, 8 + y_offset),
|
||||
// ) {
|
||||
// tracing::error!("Failed to render score text: {}", e);
|
||||
// }
|
||||
|
||||
// // Display FPS information in top-left corner
|
||||
// // let fps_text = format!("FPS: {:.1} (1s) / {:.1} (10s)", self.fps_1s, self.fps_10s);
|
||||
// // self.render_text_on(
|
||||
// // canvas,
|
||||
// // &*texture_creator,
|
||||
// // &fps_text,
|
||||
// // IVec2::new(10, 10),
|
||||
// // Color::RGB(255, 255, 0), // Yellow color for FPS display
|
||||
// // );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pub enum SystemId {
|
||||
Blinking,
|
||||
DirectionalRender,
|
||||
DirtyRender,
|
||||
HudRender,
|
||||
Render,
|
||||
DebugRender,
|
||||
Present,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::error::{GameError, TextureError};
|
||||
use crate::map::builder::Map;
|
||||
use crate::systems::components::{DeltaTime, DirectionalAnimated, RenderDirty, Renderable};
|
||||
use crate::systems::components::{DeltaTime, DirectionalAnimated, RenderDirty, Renderable, ScoreResource};
|
||||
use crate::systems::movement::{Position, Velocity};
|
||||
use crate::texture::sprite::SpriteAtlas;
|
||||
use crate::texture::text::TextTexture;
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::event::EventWriter;
|
||||
use bevy_ecs::prelude::{Changed, Or, RemovedComponents};
|
||||
@@ -61,6 +62,34 @@ pub struct MapTextureResource(pub Texture<'static>);
|
||||
/// 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<'static>);
|
||||
|
||||
/// Renders the HUD (score, lives, etc.) on top of the game.
|
||||
pub fn hud_render_system(
|
||||
mut canvas: NonSendMut<&mut Canvas<Window>>,
|
||||
mut atlas: NonSendMut<SpriteAtlas>,
|
||||
score: Res<ScoreResource>,
|
||||
mut errors: EventWriter<GameError>,
|
||||
) {
|
||||
let mut text_renderer = TextTexture::new(1.0);
|
||||
|
||||
// Render lives and high score text
|
||||
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
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_system(
|
||||
mut canvas: NonSendMut<&mut Canvas<Window>>,
|
||||
|
||||
26
tests/hud.rs
Normal file
26
tests/hud.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use bevy_ecs::{event::Events, world::World};
|
||||
|
||||
use pacman::{error::GameError, systems::components::ScoreResource};
|
||||
|
||||
fn create_test_world() -> World {
|
||||
let mut world = World::new();
|
||||
|
||||
// Add required resources
|
||||
world.insert_resource(Events::<GameError>::default());
|
||||
world.insert_resource(ScoreResource(1230)); // Test score
|
||||
|
||||
world
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hud_render_system_runs_without_error() {
|
||||
let world = create_test_world();
|
||||
|
||||
// The HUD render system requires SDL2 resources that aren't available in tests,
|
||||
// but we can at least verify it doesn't panic when called
|
||||
// In a real test environment, we'd need to mock the SDL2 canvas and atlas
|
||||
|
||||
// For now, just verify the score resource is accessible
|
||||
let score = world.resource::<ScoreResource>();
|
||||
assert_eq!(score.0, 1230);
|
||||
}
|
||||
@@ -222,7 +222,7 @@ fn test_player_control_system_toggle_debug() {
|
||||
|
||||
// Check that debug state changed
|
||||
let debug_state = world.resource::<DebugState>();
|
||||
assert_eq!(debug_state.enabled, true);
|
||||
assert!(debug_state.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -565,7 +565,7 @@ fn test_player_state_persistence_across_systems() {
|
||||
let position = *query.single(&world).expect("Player should exist");
|
||||
|
||||
// Check that the state changes persisted individually
|
||||
assert_eq!(debug_state_after_toggle.enabled, true, "Debug state should have toggled");
|
||||
assert!(debug_state_after_toggle.enabled, "Debug state should have toggled");
|
||||
assert!(audio_muted_after_toggle, "Audio should be muted");
|
||||
|
||||
// Player position depends on actual map connectivity
|
||||
|
||||
Reference in New Issue
Block a user