mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 10:07:51 -06:00
refactor: move position/movement related components into systems/movement
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
|
|
||||||
use crate::systems::components::NodeId;
|
use crate::systems::movement::NodeId;
|
||||||
|
|
||||||
use super::direction::Direction;
|
use super::direction::Direction;
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ use crate::error::{GameError, GameResult, TextureError};
|
|||||||
use crate::events::GameEvent;
|
use crate::events::GameEvent;
|
||||||
use crate::map::builder::Map;
|
use crate::map::builder::Map;
|
||||||
use crate::systems::blinking::Blinking;
|
use crate::systems::blinking::Blinking;
|
||||||
|
use crate::systems::movement::{Movable, MovementState, Position};
|
||||||
use crate::systems::{
|
use crate::systems::{
|
||||||
blinking::blinking_system,
|
blinking::blinking_system,
|
||||||
collision::collision_system,
|
collision::collision_system,
|
||||||
components::{
|
components::{
|
||||||
Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider, Movable,
|
Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider,
|
||||||
MovementState, PacmanCollider, PlayerBundle, PlayerControlled, Position, Renderable, Score, ScoreResource,
|
PacmanCollider, PlayerBundle, PlayerControlled, Renderable, Score, ScoreResource,
|
||||||
},
|
},
|
||||||
control::player_system,
|
control::player_system,
|
||||||
input::input_system,
|
input::input_system,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::entity::direction::Direction;
|
|||||||
use crate::entity::graph::{Graph, Node, TraversalFlags};
|
use crate::entity::graph::{Graph, Node, TraversalFlags};
|
||||||
use crate::map::parser::MapTileParser;
|
use crate::map::parser::MapTileParser;
|
||||||
use crate::map::render::MapRenderer;
|
use crate::map::render::MapRenderer;
|
||||||
use crate::systems::components::NodeId;
|
use crate::systems::movement::NodeId;
|
||||||
use crate::texture::sprite::SpriteAtlas;
|
use crate::texture::sprite::SpriteAtlas;
|
||||||
use bevy_ecs::resource::Resource;
|
use bevy_ecs::resource::Resource;
|
||||||
use glam::{IVec2, Vec2};
|
use glam::{IVec2, Vec2};
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use bevy_ecs::system::{Query, Res};
|
|||||||
use crate::error::GameError;
|
use crate::error::GameError;
|
||||||
use crate::events::GameEvent;
|
use crate::events::GameEvent;
|
||||||
use crate::map::builder::Map;
|
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(
|
pub fn collision_system(
|
||||||
map: Res<Map>,
|
map: Res<Map>,
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
use bevy_ecs::{bundle::Bundle, component::Component, resource::Resource};
|
use bevy_ecs::{bundle::Bundle, component::Component, resource::Resource};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use glam::Vec2;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entity::{
|
entity::graph::TraversalFlags,
|
||||||
direction::Direction,
|
systems::movement::{Movable, MovementState, Position},
|
||||||
graph::{Graph, TraversalFlags},
|
|
||||||
},
|
|
||||||
error::{EntityError, GameResult},
|
|
||||||
texture::{animated::AnimatedTexture, sprite::AtlasTile},
|
texture::{animated::AnimatedTexture, sprite::AtlasTile},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,7 +18,6 @@ pub enum EntityType {
|
|||||||
Ghost,
|
Ghost,
|
||||||
Pellet,
|
Pellet,
|
||||||
PowerPellet,
|
PowerPellet,
|
||||||
Wall,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityType {
|
impl EntityType {
|
||||||
@@ -53,113 +48,6 @@ pub struct DirectionalAnimated {
|
|||||||
pub stopped_textures: [Option<AnimatedTexture>; 4],
|
pub stopped_textures: [Option<AnimatedTexture>; 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<EdgeProgress>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<Direction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Vec2> {
|
|
||||||
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<NodeId> {
|
|
||||||
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! {
|
bitflags! {
|
||||||
#[derive(Component, Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Component, Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct CollisionLayer: u8 {
|
pub struct CollisionLayer: u8 {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ use bevy_ecs::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::GameError,
|
error::GameError,
|
||||||
events::{GameCommand, GameEvent},
|
events::{GameCommand, GameEvent},
|
||||||
systems::components::{GlobalState, Movable, PlayerControlled},
|
systems::components::{GlobalState, PlayerControlled},
|
||||||
|
systems::movement::Movable,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles player input and control
|
// Handles player input and control
|
||||||
|
|||||||
@@ -1,9 +1,119 @@
|
|||||||
use crate::entity::graph::Edge;
|
use crate::entity::graph::Graph;
|
||||||
use crate::error::{EntityError, GameError};
|
use crate::entity::{direction::Direction, graph::Edge};
|
||||||
|
use crate::error::{EntityError, GameError, GameResult};
|
||||||
use crate::map::builder::Map;
|
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::event::EventWriter;
|
||||||
use bevy_ecs::system::{Query, Res};
|
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<EdgeProgress>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Direction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Vec2> {
|
||||||
|
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<NodeId> {
|
||||||
|
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 {
|
fn can_traverse(entity_type: EntityType, edge: Edge) -> bool {
|
||||||
let entity_flags = entity_type.traversal_flags();
|
let entity_flags = entity_type.traversal_flags();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::error::{GameError, TextureError};
|
use crate::error::{GameError, TextureError};
|
||||||
use crate::map::builder::Map;
|
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 crate::texture::sprite::SpriteAtlas;
|
||||||
use bevy_ecs::entity::Entity;
|
use bevy_ecs::entity::Entity;
|
||||||
use bevy_ecs::event::EventWriter;
|
use bevy_ecs::event::EventWriter;
|
||||||
|
|||||||
Reference in New Issue
Block a user