diff --git a/src/entity/graph.rs b/src/entity/graph.rs index e99ed67..c83d98c 100644 --- a/src/entity/graph.rs +++ b/src/entity/graph.rs @@ -1,6 +1,6 @@ use glam::Vec2; -use crate::systems::components::NodeId; +use crate::systems::movement::NodeId; use super::direction::Direction; diff --git a/src/game/mod.rs b/src/game/mod.rs index 25d7ec6..0aed0bb 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -8,12 +8,13 @@ use crate::error::{GameError, GameResult, TextureError}; use crate::events::GameEvent; use crate::map::builder::Map; use crate::systems::blinking::Blinking; +use crate::systems::movement::{Movable, MovementState, Position}; use crate::systems::{ blinking::blinking_system, collision::collision_system, components::{ - Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider, Movable, - MovementState, PacmanCollider, PlayerBundle, PlayerControlled, Position, Renderable, Score, ScoreResource, + Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider, + PacmanCollider, PlayerBundle, PlayerControlled, Renderable, Score, ScoreResource, }, control::player_system, input::input_system, diff --git a/src/map/builder.rs b/src/map/builder.rs index 880d244..fe1a9d5 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -4,7 +4,7 @@ use crate::entity::direction::Direction; use crate::entity::graph::{Graph, Node, TraversalFlags}; use crate::map::parser::MapTileParser; use crate::map::render::MapRenderer; -use crate::systems::components::NodeId; +use crate::systems::movement::NodeId; use crate::texture::sprite::SpriteAtlas; use bevy_ecs::resource::Resource; use glam::{IVec2, Vec2}; diff --git a/src/systems/collision.rs b/src/systems/collision.rs index c5f7334..cd291a7 100644 --- a/src/systems/collision.rs +++ b/src/systems/collision.rs @@ -6,7 +6,8 @@ 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}; +use crate::systems::components::{Collider, ItemCollider, PacmanCollider}; +use crate::systems::movement::Position; pub fn collision_system( map: Res, diff --git a/src/systems/components.rs b/src/systems/components.rs index e114d36..847f033 100644 --- a/src/systems/components.rs +++ b/src/systems/components.rs @@ -1,13 +1,9 @@ use bevy_ecs::{bundle::Bundle, component::Component, resource::Resource}; use bitflags::bitflags; -use glam::Vec2; use crate::{ - entity::{ - direction::Direction, - graph::{Graph, TraversalFlags}, - }, - error::{EntityError, GameResult}, + entity::graph::TraversalFlags, + systems::movement::{Movable, MovementState, Position}, texture::{animated::AnimatedTexture, sprite::AtlasTile}, }; @@ -22,7 +18,6 @@ pub enum EntityType { Ghost, Pellet, PowerPellet, - Wall, } impl EntityType { @@ -53,113 +48,6 @@ pub struct DirectionalAnimated { pub stopped_textures: [Option; 4], } -/// A unique identifier for a node, represented by its index in the graph's storage. -pub type NodeId = usize; - -/// Progress along an edge between two nodes. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct EdgeProgress { - pub target_node: NodeId, - /// Progress from 0.0 (at source node) to 1.0 (at target node) - pub progress: f32, -} - -/// Pure spatial position component - works for both static and dynamic entities. -#[derive(Component, Debug, Copy, Clone, PartialEq)] -pub struct Position { - /// The current/primary node this entity is at or traveling from - pub node: NodeId, - /// If Some, entity is traveling between nodes. If None, entity is stationary at node. - pub edge_progress: Option, -} - -/// Explicit movement state - only for entities that can move. -#[derive(Component, Debug, Clone, Copy, PartialEq)] -pub enum MovementState { - Stopped, - Moving { direction: Direction }, -} - -/// Movement capability and parameters - only for entities that can move. -#[derive(Component, Debug, Clone, Copy)] -pub struct Movable { - pub speed: f32, - pub current_direction: Direction, - pub requested_direction: Option, -} - -impl Position { - /// Calculates the current pixel position in the game world. - /// - /// 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 { - let pos = match &self.edge_progress { - None => { - // Entity is stationary at a node - let node = graph.get_node(self.node).ok_or(EntityError::NodeNotFound(self.node))?; - node.position - } - Some(edge_progress) => { - // Entity is traveling between nodes - let from_node = graph.get_node(self.node).ok_or(EntityError::NodeNotFound(self.node))?; - let to_node = graph - .get_node(edge_progress.target_node) - .ok_or(EntityError::NodeNotFound(edge_progress.target_node))?; - - // For zero-distance edges (tunnels), progress >= 1.0 means we're at the target - if edge_progress.progress >= 1.0 { - to_node.position - } else { - // Interpolate position based on progress - from_node.position + (to_node.position - from_node.position) * edge_progress.progress - } - } - }; - - Ok(Vec2::new( - pos.x + crate::constants::BOARD_PIXEL_OFFSET.x as f32, - pos.y + crate::constants::BOARD_PIXEL_OFFSET.y as f32, - )) - } -} - -impl Default for Position { - fn default() -> Self { - Position { - node: 0, - edge_progress: None, - } - } -} - -#[allow(dead_code)] -impl Position { - /// Returns `true` if the position is exactly at a node (not traveling). - pub fn is_at_node(&self) -> bool { - self.edge_progress.is_none() - } - - /// Returns the `NodeId` of the current node (source of travel if moving). - pub fn current_node(&self) -> NodeId { - self.node - } - - /// Returns the `NodeId` of the destination node, if currently traveling. - pub fn target_node(&self) -> Option { - self.edge_progress.as_ref().map(|ep| ep.target_node) - } - - /// Returns `true` if the entity is traveling between nodes. - pub fn is_moving(&self) -> bool { - self.edge_progress.is_some() - } -} - bitflags! { #[derive(Component, Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CollisionLayer: u8 { diff --git a/src/systems/control.rs b/src/systems/control.rs index 765d947..63b41fd 100644 --- a/src/systems/control.rs +++ b/src/systems/control.rs @@ -8,7 +8,8 @@ use bevy_ecs::{ use crate::{ error::GameError, events::{GameCommand, GameEvent}, - systems::components::{GlobalState, Movable, PlayerControlled}, + systems::components::{GlobalState, PlayerControlled}, + systems::movement::Movable, }; // Handles player input and control diff --git a/src/systems/movement.rs b/src/systems/movement.rs index 87581f1..9abe9e0 100644 --- a/src/systems/movement.rs +++ b/src/systems/movement.rs @@ -1,9 +1,119 @@ -use crate::entity::graph::Edge; -use crate::error::{EntityError, GameError}; +use crate::entity::graph::Graph; +use crate::entity::{direction::Direction, graph::Edge}; +use crate::error::{EntityError, GameError, GameResult}; use crate::map::builder::Map; -use crate::systems::components::{DeltaTime, EdgeProgress, EntityType, Movable, MovementState, Position}; +use crate::systems::components::{DeltaTime, EntityType}; +use bevy_ecs::component::Component; use bevy_ecs::event::EventWriter; use bevy_ecs::system::{Query, Res}; +use glam::Vec2; + +/// A unique identifier for a node, represented by its index in the graph's storage. +pub type NodeId = usize; + +/// Progress along an edge between two nodes. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct EdgeProgress { + pub target_node: NodeId, + /// Progress from 0.0 (at source node) to 1.0 (at target node) + pub progress: f32, +} + +/// Pure spatial position component - works for both static and dynamic entities. +#[derive(Component, Debug, Copy, Clone, PartialEq)] +pub struct Position { + /// The current/primary node this entity is at or traveling from + pub node: NodeId, + /// If Some, entity is traveling between nodes. If None, entity is stationary at node. + pub edge_progress: Option, +} + +/// Explicit movement state - only for entities that can move. +#[derive(Component, Debug, Clone, Copy, PartialEq)] +pub enum MovementState { + Stopped, + Moving { direction: Direction }, +} + +/// Movement capability and parameters - only for entities that can move. +#[derive(Component, Debug, Clone, Copy)] +pub struct Movable { + pub speed: f32, + pub current_direction: Direction, + pub requested_direction: Option, +} + +impl Position { + /// Calculates the current pixel position in the game world. + /// + /// 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 { + let pos = match &self.edge_progress { + None => { + // Entity is stationary at a node + let node = graph.get_node(self.node).ok_or(EntityError::NodeNotFound(self.node))?; + node.position + } + Some(edge_progress) => { + // Entity is traveling between nodes + let from_node = graph.get_node(self.node).ok_or(EntityError::NodeNotFound(self.node))?; + let to_node = graph + .get_node(edge_progress.target_node) + .ok_or(EntityError::NodeNotFound(edge_progress.target_node))?; + + // For zero-distance edges (tunnels), progress >= 1.0 means we're at the target + if edge_progress.progress >= 1.0 { + to_node.position + } else { + // Interpolate position based on progress + from_node.position + (to_node.position - from_node.position) * edge_progress.progress + } + } + }; + + Ok(Vec2::new( + pos.x + crate::constants::BOARD_PIXEL_OFFSET.x as f32, + pos.y + crate::constants::BOARD_PIXEL_OFFSET.y as f32, + )) + } +} + +impl Default for Position { + fn default() -> Self { + Position { + node: 0, + edge_progress: None, + } + } +} + +#[allow(dead_code)] +impl Position { + /// Returns `true` if the position is exactly at a node (not traveling). + pub fn is_at_node(&self) -> bool { + self.edge_progress.is_none() + } + + /// Returns the `NodeId` of the current node (source of travel if moving). + pub fn current_node(&self) -> NodeId { + self.node + } + + /// Returns the `NodeId` of the destination node, if currently traveling. + pub fn target_node(&self) -> Option { + self.edge_progress.as_ref().map(|ep| ep.target_node) + } + + /// Returns `true` if the entity is traveling between nodes. + pub fn is_moving(&self) -> bool { + self.edge_progress.is_some() + } +} fn can_traverse(entity_type: EntityType, edge: Edge) -> bool { let entity_flags = entity_type.traversal_flags(); diff --git a/src/systems/render.rs b/src/systems/render.rs index 8b02ebf..c5d8847 100644 --- a/src/systems/render.rs +++ b/src/systems/render.rs @@ -1,6 +1,7 @@ use crate::error::{GameError, TextureError}; use crate::map::builder::Map; -use crate::systems::components::{DeltaTime, DirectionalAnimated, Movable, MovementState, Position, Renderable}; +use crate::systems::components::{DeltaTime, DirectionalAnimated, Renderable}; +use crate::systems::movement::{Movable, MovementState, Position}; use crate::texture::sprite::SpriteAtlas; use bevy_ecs::entity::Entity; use bevy_ecs::event::EventWriter;