mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-16 10:12:37 -06:00
feat: implement generic optimized collision system
This commit is contained in:
27
src/systems/blinking.rs
Normal file
27
src/systems/blinking.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
system::{Query, Res},
|
||||
};
|
||||
|
||||
use crate::systems::components::{DeltaTime, Renderable};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Blinking {
|
||||
pub timer: f32,
|
||||
pub interval: f32,
|
||||
}
|
||||
|
||||
/// Updates blinking entities by toggling their visibility at regular intervals.
|
||||
///
|
||||
/// This system manages entities that have both `Blinking` and `Renderable` components,
|
||||
/// accumulating time and toggling visibility when the specified interval is reached.
|
||||
pub fn blinking_system(time: Res<DeltaTime>, mut query: Query<(&mut Blinking, &mut Renderable)>) {
|
||||
for (mut blinking, mut renderable) in query.iter_mut() {
|
||||
blinking.timer += time.0;
|
||||
|
||||
if blinking.timer >= blinking.interval {
|
||||
blinking.timer = 0.0;
|
||||
renderable.visible = !renderable.visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/systems/collision.rs
Normal file
47
src/systems/collision.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::event::EventWriter;
|
||||
use bevy_ecs::query::With;
|
||||
use bevy_ecs::system::{Query, Res};
|
||||
|
||||
use crate::error::GameError;
|
||||
use crate::events::GameEvent;
|
||||
use crate::map::builder::Map;
|
||||
use crate::systems::components::{Collider, ItemCollider, PacmanCollider, Position};
|
||||
|
||||
pub fn collision_system(
|
||||
map: Res<Map>,
|
||||
pacman_query: Query<(Entity, &Position, &Collider), With<PacmanCollider>>,
|
||||
item_query: Query<(Entity, &Position, &Collider), With<ItemCollider>>,
|
||||
mut events: EventWriter<GameEvent>,
|
||||
mut errors: EventWriter<GameError>,
|
||||
) {
|
||||
// Check PACMAN × ITEM collisions
|
||||
for (pacman_entity, pacman_pos, pacman_collider) in pacman_query.iter() {
|
||||
for (item_entity, item_pos, item_collider) in item_query.iter() {
|
||||
match (pacman_pos.get_pixel_pos(&map.graph), item_pos.get_pixel_pos(&map.graph)) {
|
||||
(Ok(pacman_pixel), Ok(item_pixel)) => {
|
||||
// Calculate the distance between the two entities's precise pixel positions
|
||||
let distance = pacman_pixel.distance(item_pixel);
|
||||
// Calculate the distance at which the two entities will collide
|
||||
let collision_distance = (pacman_collider.size + item_collider.size) / 2.0;
|
||||
|
||||
// If the distance between the two entities is less than the collision distance, then the two entities are colliding
|
||||
if distance < collision_distance {
|
||||
events.write(GameEvent::Collision(pacman_entity, item_entity));
|
||||
}
|
||||
}
|
||||
// Either or both of the pixel positions failed to get, so we need to report the error
|
||||
(result_a, result_b) => {
|
||||
for result in [result_a, result_b] {
|
||||
if let Err(e) = result {
|
||||
errors.write(GameError::InvalidState(format!(
|
||||
"Collision system failed to get pixel positions for entities {:?} and {:?}: {}",
|
||||
pacman_entity, item_entity, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use bevy_ecs::{bundle::Bundle, component::Component, resource::Resource};
|
||||
use bitflags::bitflags;
|
||||
use glam::Vec2;
|
||||
|
||||
use crate::{
|
||||
@@ -28,6 +29,7 @@ pub enum EntityType {
|
||||
pub struct Renderable {
|
||||
pub sprite: AtlasTile,
|
||||
pub layer: u8,
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
/// A component for entities that have a directional animated texture.
|
||||
@@ -62,6 +64,10 @@ impl Position {
|
||||
///
|
||||
/// Converts the graph position to screen coordinates, accounting for
|
||||
/// the board offset and centering the sprite.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `EntityError` if the node or edge is not found.
|
||||
pub fn get_pixel_pos(&self, graph: &Graph) -> GameResult<Vec2> {
|
||||
let pos = match self {
|
||||
Position::AtNode(node_id) => {
|
||||
@@ -130,6 +136,34 @@ pub struct Velocity {
|
||||
pub speed: f32,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Component, Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CollisionLayer: u8 {
|
||||
const PACMAN = 1 << 0;
|
||||
const GHOST = 1 << 1;
|
||||
const ITEM = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Collider {
|
||||
pub size: f32,
|
||||
pub layer: CollisionLayer,
|
||||
}
|
||||
|
||||
/// Marker components for collision filtering optimization
|
||||
#[derive(Component)]
|
||||
pub struct PacmanCollider;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct GhostCollider;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ItemCollider;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Score(pub u32);
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct PlayerBundle {
|
||||
pub player: PlayerControlled,
|
||||
@@ -138,6 +172,18 @@ pub struct PlayerBundle {
|
||||
pub sprite: Renderable,
|
||||
pub directional_animated: DirectionalAnimated,
|
||||
pub entity_type: EntityType,
|
||||
pub collider: Collider,
|
||||
pub pacman_collider: PacmanCollider,
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct ItemBundle {
|
||||
pub position: Position,
|
||||
pub sprite: Renderable,
|
||||
pub entity_type: EntityType,
|
||||
pub score: Score,
|
||||
pub collider: Collider,
|
||||
pub item_collider: ItemCollider,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
@@ -145,5 +191,8 @@ pub struct GlobalState {
|
||||
pub exit: bool,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ScoreResource(pub u32);
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct DeltaTime(pub f32);
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use bevy_ecs::{
|
||||
event::{EventReader, EventWriter},
|
||||
prelude::ResMut,
|
||||
query::With,
|
||||
system::{Query, ResMut},
|
||||
system::Query,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GameError,
|
||||
events::GameEvent,
|
||||
systems::{
|
||||
components::{GlobalState, PlayerControlled, Velocity},
|
||||
input::GameCommand,
|
||||
},
|
||||
events::{GameCommand, GameEvent},
|
||||
systems::components::{GlobalState, PlayerControlled, Velocity},
|
||||
};
|
||||
|
||||
// Handles
|
||||
@@ -41,6 +39,9 @@ pub fn player_system(
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
GameEvent::Collision(a, b) => {
|
||||
tracing::info!("Collision between {:?} and {:?}", a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bevy_ecs::{
|
||||
event::EventWriter,
|
||||
resource::Resource,
|
||||
system::{NonSendMut, Res},
|
||||
};
|
||||
use bevy_ecs::{event::EventWriter, prelude::Res, resource::Resource, system::NonSendMut};
|
||||
use sdl2::{event::Event, keyboard::Keycode, EventPump};
|
||||
|
||||
use crate::{entity::direction::Direction, events::GameEvent};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GameCommand {
|
||||
MovePlayer(Direction),
|
||||
Exit,
|
||||
TogglePause,
|
||||
ToggleDebug,
|
||||
MuteAudio,
|
||||
ResetLevel,
|
||||
}
|
||||
use crate::{
|
||||
entity::direction::Direction,
|
||||
events::{GameCommand, GameEvent},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Resource)]
|
||||
pub struct Bindings {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! This module contains all the ECS-related logic, including components, systems,
|
||||
//! and resources.
|
||||
|
||||
pub mod blinking;
|
||||
pub mod collision;
|
||||
pub mod components;
|
||||
pub mod control;
|
||||
pub mod input;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::entity::graph::{Edge, EdgePermissions, Graph};
|
||||
use crate::entity::graph::{Edge, EdgePermissions};
|
||||
use crate::error::{EntityError, GameError};
|
||||
use crate::map::builder::Map;
|
||||
use crate::systems::components::{DeltaTime, EntityType, Position, Velocity};
|
||||
|
||||
Reference in New Issue
Block a user