mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 03:15:48 -06:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d86864b6a3 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -663,7 +663,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pacman"
|
name = "pacman"
|
||||||
version = "0.79.0"
|
version = "0.79.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pacman"
|
name = "pacman"
|
||||||
version = "0.79.0"
|
version = "0.79.1"
|
||||||
authors = ["Xevion"]
|
authors = ["Xevion"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.86.0"
|
rust-version = "1.86.0"
|
||||||
|
|||||||
19
src/game.rs
19
src/game.rs
@@ -13,14 +13,14 @@ use crate::map::builder::Map;
|
|||||||
use crate::map::direction::Direction;
|
use crate::map::direction::Direction;
|
||||||
use crate::systems::{
|
use crate::systems::{
|
||||||
self, audio_system, blinking_system, collision_system, combined_render_system, directional_render_system,
|
self, audio_system, blinking_system, collision_system, combined_render_system, directional_render_system,
|
||||||
dirty_render_system, eaten_ghost_system, ghost_collision_observer, ghost_movement_system, ghost_state_system,
|
dirty_render_system, eaten_ghost_system, fruit_sprite_system, ghost_collision_observer, ghost_movement_system,
|
||||||
hud_render_system, item_collision_observer, linear_render_system, player_life_sprite_system, present_system, profile,
|
ghost_state_system, hud_render_system, item_collision_observer, linear_render_system, player_life_sprite_system,
|
||||||
time_to_live_system, touch_ui_render_system, AudioEvent, AudioResource, AudioState, BackbufferResource, Blinking,
|
present_system, profile, time_to_live_system, touch_ui_render_system, AudioEvent, AudioResource, AudioState,
|
||||||
BufferedDirection, Collider, DebugState, DebugTextureResource, DeltaTime, DirectionalAnimation, EntityType, Frozen,
|
BackbufferResource, Blinking, BufferedDirection, Collider, DebugState, DebugTextureResource, DeltaTime, DirectionalAnimation,
|
||||||
GameStage, Ghost, GhostAnimation, GhostAnimations, GhostBundle, GhostCollider, GhostState, GlobalState, ItemBundle,
|
EntityType, Frozen, FruitSprites, GameStage, Ghost, GhostAnimation, GhostAnimations, GhostBundle, GhostCollider, GhostState,
|
||||||
ItemCollider, LastAnimationState, LinearAnimation, MapTextureResource, MovementModifiers, NodeId, PacmanCollider,
|
GlobalState, ItemBundle, ItemCollider, LastAnimationState, LinearAnimation, MapTextureResource, MovementModifiers, NodeId,
|
||||||
PlayerAnimation, PlayerBundle, PlayerControlled, PlayerDeathAnimation, PlayerLives, Position, RenderDirty, Renderable,
|
PacmanCollider, PlayerAnimation, PlayerBundle, PlayerControlled, PlayerDeathAnimation, PlayerLives, Position, RenderDirty,
|
||||||
ScoreResource, StartupSequence, SystemId, SystemTimings, Timing, TouchState, Velocity, Visibility,
|
Renderable, ScoreResource, StartupSequence, SystemId, SystemTimings, Timing, TouchState, Velocity, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::texture::animated::{DirectionalTiles, TileSequence};
|
use crate::texture::animated::{DirectionalTiles, TileSequence};
|
||||||
@@ -424,6 +424,7 @@ impl Game {
|
|||||||
world.insert_resource(PlayerAnimation(player_animation));
|
world.insert_resource(PlayerAnimation(player_animation));
|
||||||
world.insert_resource(PlayerDeathAnimation(death_animation));
|
world.insert_resource(PlayerDeathAnimation(death_animation));
|
||||||
|
|
||||||
|
world.insert_resource(FruitSprites::default());
|
||||||
world.insert_resource(BatchedLinesResource::new(&map, constants::LARGE_SCALE));
|
world.insert_resource(BatchedLinesResource::new(&map, constants::LARGE_SCALE));
|
||||||
world.insert_resource(map);
|
world.insert_resource(map);
|
||||||
world.insert_resource(GlobalState { exit: false });
|
world.insert_resource(GlobalState { exit: false });
|
||||||
@@ -468,6 +469,7 @@ impl Game {
|
|||||||
let dirty_render_system = profile(SystemId::DirtyRender, dirty_render_system);
|
let dirty_render_system = profile(SystemId::DirtyRender, dirty_render_system);
|
||||||
let hud_render_system = profile(SystemId::HudRender, hud_render_system);
|
let hud_render_system = profile(SystemId::HudRender, hud_render_system);
|
||||||
let player_life_sprite_system = profile(SystemId::HudRender, player_life_sprite_system);
|
let player_life_sprite_system = profile(SystemId::HudRender, player_life_sprite_system);
|
||||||
|
let fruit_sprite_system = profile(SystemId::HudRender, fruit_sprite_system);
|
||||||
let present_system = profile(SystemId::Present, present_system);
|
let present_system = profile(SystemId::Present, present_system);
|
||||||
let unified_ghost_state_system = profile(SystemId::GhostStateAnimation, ghost_state_system);
|
let unified_ghost_state_system = profile(SystemId::GhostStateAnimation, ghost_state_system);
|
||||||
let eaten_ghost_system = profile(SystemId::EatenGhost, eaten_ghost_system);
|
let eaten_ghost_system = profile(SystemId::EatenGhost, eaten_ghost_system);
|
||||||
@@ -504,6 +506,7 @@ impl Game {
|
|||||||
directional_render_system,
|
directional_render_system,
|
||||||
linear_render_system,
|
linear_render_system,
|
||||||
player_life_sprite_system,
|
player_life_sprite_system,
|
||||||
|
fruit_sprite_system,
|
||||||
)
|
)
|
||||||
.in_set(RenderSet::Animation),
|
.in_set(RenderSet::Animation),
|
||||||
stage_system.in_set(GameplaySet::Respond),
|
stage_system.in_set(GameplaySet::Respond),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use tracing::{debug, trace, warn};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants,
|
constants,
|
||||||
systems::{movement::Position, AudioEvent, DyingSequence, GameStage, Ghost, ScoreResource, SpawnTrigger},
|
systems::{movement::Position, AudioEvent, DyingSequence, FruitSprites, GameStage, Ghost, ScoreResource, SpawnTrigger},
|
||||||
};
|
};
|
||||||
use crate::{error::GameError, systems::GhostState};
|
use crate::{error::GameError, systems::GhostState};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -188,6 +188,7 @@ pub fn item_collision_observer(
|
|||||||
mut pellet_count: ResMut<PelletCount>,
|
mut pellet_count: ResMut<PelletCount>,
|
||||||
item_query: Query<(Entity, &EntityType, &Position), With<ItemCollider>>,
|
item_query: Query<(Entity, &EntityType, &Position), With<ItemCollider>>,
|
||||||
mut ghost_query: Query<&mut GhostState, With<GhostCollider>>,
|
mut ghost_query: Query<&mut GhostState, With<GhostCollider>>,
|
||||||
|
mut fruit_sprites: ResMut<FruitSprites>,
|
||||||
mut events: EventWriter<AudioEvent>,
|
mut events: EventWriter<AudioEvent>,
|
||||||
) {
|
) {
|
||||||
if let CollisionTrigger::ItemCollision { item } = *trigger {
|
if let CollisionTrigger::ItemCollision { item } = *trigger {
|
||||||
@@ -213,7 +214,9 @@ pub fn item_collision_observer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trigger bonus points effect if a fruit is collected
|
// Trigger bonus points effect if a fruit is collected
|
||||||
if matches!(*entity_type, EntityType::Fruit(_)) {
|
if let EntityType::Fruit(fruit) = *entity_type {
|
||||||
|
fruit_sprites.0.push(fruit);
|
||||||
|
|
||||||
commands.trigger(SpawnTrigger::Bonus {
|
commands.trigger(SpawnTrigger::Bonus {
|
||||||
position: *position,
|
position: *position,
|
||||||
value: entity_type.score_value().unwrap(),
|
value: entity_type.score_value().unwrap(),
|
||||||
|
|||||||
79
src/systems/hud/fruits.rs
Normal file
79
src/systems/hud/fruits.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use crate::systems::item::FruitType;
|
||||||
|
use crate::texture::sprites::GameSprite;
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
|
use bevy_ecs::resource::Resource;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct FruitInHud {
|
||||||
|
pub index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct FruitSprites(pub Vec<FruitType>);
|
||||||
|
|
||||||
|
use crate::constants::{BOARD_BOTTOM_PIXEL_OFFSET, CANVAS_SIZE, CELL_SIZE};
|
||||||
|
use crate::error::GameError;
|
||||||
|
use crate::systems::{PixelPosition, Renderable};
|
||||||
|
use crate::texture::sprite::SpriteAtlas;
|
||||||
|
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 fruit sprite based on its index
|
||||||
|
fn calculate_fruit_sprite_position(index: u32) -> Vec2 {
|
||||||
|
let start_x = CANVAS_SIZE.x - CELL_SIZE * 2; // 2 cells from right
|
||||||
|
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 fruit sprite entities in the HUD.
|
||||||
|
/// Spawns and despawns fruit sprite entities based on changes to FruitSprites resource.
|
||||||
|
/// Displays up to 6 fruits, sorted by value.
|
||||||
|
pub fn fruit_sprite_system(
|
||||||
|
mut commands: Commands,
|
||||||
|
atlas: NonSendMut<SpriteAtlas>,
|
||||||
|
current_fruit_sprites: Query<(Entity, &FruitInHud)>,
|
||||||
|
fruit_sprites: Res<FruitSprites>,
|
||||||
|
mut errors: EventWriter<GameError>,
|
||||||
|
) {
|
||||||
|
// We only want to display the greatest 6 fruits
|
||||||
|
let fruits_to_display: Vec<_> = fruit_sprites.0.iter().rev().take(6).collect();
|
||||||
|
|
||||||
|
let mut current_sprites: Vec<_> = current_fruit_sprites.iter().collect();
|
||||||
|
current_sprites.sort_by_key(|(_, fruit)| fruit.index);
|
||||||
|
|
||||||
|
// Despawn all current sprites. We will respawn them.
|
||||||
|
// This is simpler than trying to match them up.
|
||||||
|
for (entity, _) in ¤t_sprites {
|
||||||
|
commands.entity(*entity).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, fruit_type) in fruits_to_display.iter().enumerate() {
|
||||||
|
let fruit_sprite = match atlas.get_tile(&GameSprite::Fruit(**fruit_type).to_path()) {
|
||||||
|
Ok(sprite) => sprite,
|
||||||
|
Err(e) => {
|
||||||
|
errors.write(e.into());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = calculate_fruit_sprite_position(i as u32);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
FruitInHud { index: i as u32 },
|
||||||
|
Renderable {
|
||||||
|
sprite: fruit_sprite,
|
||||||
|
layer: 255, // High layer to render on top
|
||||||
|
},
|
||||||
|
PixelPosition {
|
||||||
|
pixel_position: position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
pub mod fruits;
|
||||||
pub mod lives;
|
pub mod lives;
|
||||||
pub mod score;
|
pub mod score;
|
||||||
pub mod touch;
|
pub mod touch;
|
||||||
|
|
||||||
|
pub use self::fruits::*;
|
||||||
pub use self::lives::*;
|
pub use self::lives::*;
|
||||||
pub use self::score::*;
|
pub use self::score::*;
|
||||||
pub use self::touch::*;
|
pub use self::touch::*;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ use crate::{
|
|||||||
|
|
||||||
use crate::{systems::common::components::EntityType, systems::ItemCollider};
|
use crate::{systems::common::components::EntityType, systems::ItemCollider};
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
/// Tracks the number of pellets consumed by the player for fruit spawning mechanics.
|
/// Tracks the number of pellets consumed by the player for fruit spawning mechanics.
|
||||||
#[derive(bevy_ecs::resource::Resource, Debug, Default)]
|
#[derive(bevy_ecs::resource::Resource, Debug, Default)]
|
||||||
pub struct PelletCount(pub u32);
|
pub struct PelletCount(pub u32);
|
||||||
@@ -36,6 +38,18 @@ pub enum FruitType {
|
|||||||
Key,
|
Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for FruitType {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for FruitType {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
(self.score_value()).cmp(&other.score_value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FruitType {
|
impl FruitType {
|
||||||
/// Returns the score value for this fruit type.
|
/// Returns the score value for this fruit type.
|
||||||
pub fn score_value(self) -> u32 {
|
pub fn score_value(self) -> u32 {
|
||||||
|
|||||||
Reference in New Issue
Block a user