mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 03:15:48 -06:00
feat: ecs keyboard interactions
This commit is contained in:
@@ -1,16 +1,144 @@
|
||||
use bevy_ecs::{
|
||||
event::{EventReader, EventWriter},
|
||||
query::With,
|
||||
system::{Query, ResMut},
|
||||
system::{Query, Res, ResMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ecs::{GlobalState, PlayerControlled, Velocity},
|
||||
error::GameError,
|
||||
ecs::{DeltaTime, GlobalState, PlayerControlled, Position, Velocity},
|
||||
error::{EntityError, GameError},
|
||||
game::events::GameEvent,
|
||||
input::commands::GameCommand,
|
||||
map::builder::Map,
|
||||
};
|
||||
|
||||
pub fn movement_system(
|
||||
map: Res<Map>,
|
||||
delta_time: Res<DeltaTime>,
|
||||
mut entities: Query<(&PlayerControlled, &mut Velocity, &mut Position)>,
|
||||
mut errors: EventWriter<GameError>,
|
||||
) {
|
||||
for (player, mut velocity, mut position) in entities.iter_mut() {
|
||||
let distance = velocity.speed.unwrap_or(0.0) * delta_time.0;
|
||||
|
||||
// Decrement the remaining frames for the next direction
|
||||
if let Some((direction, remaining)) = velocity.next_direction {
|
||||
if remaining > 0 {
|
||||
velocity.next_direction = Some((direction, remaining - 1));
|
||||
} else {
|
||||
velocity.next_direction = None;
|
||||
}
|
||||
}
|
||||
|
||||
match *position {
|
||||
Position::AtNode(node_id) => {
|
||||
// We're not moving, but a buffered direction is available.
|
||||
if let Some((next_direction, _)) = velocity.next_direction {
|
||||
if let Some(edge) = map.graph.find_edge_in_direction(node_id, next_direction) {
|
||||
// if edge.permissions.can_traverse(edge) {
|
||||
// // Start moving in that direction
|
||||
*position = Position::BetweenNodes {
|
||||
from: node_id,
|
||||
to: edge.target,
|
||||
traversed: distance,
|
||||
};
|
||||
velocity.direction = next_direction;
|
||||
// } else {
|
||||
// return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(
|
||||
// format!(
|
||||
// "Cannot traverse edge from {} to {} in direction {:?}",
|
||||
// node_id, edge.target, next_direction
|
||||
// ),
|
||||
// )));
|
||||
// }
|
||||
} else {
|
||||
errors.write(
|
||||
EntityError::InvalidMovement(format!(
|
||||
"No edge found in direction {:?} from node {}",
|
||||
next_direction, node_id
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
velocity.next_direction = None; // Consume the buffered direction regardless of whether we started moving with it
|
||||
}
|
||||
}
|
||||
Position::BetweenNodes { from, to, traversed } => {
|
||||
// There is no point in any of the next logic if we don't travel at all
|
||||
if distance <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let edge = map
|
||||
.graph
|
||||
.find_edge(from, to)
|
||||
.ok_or_else(|| {
|
||||
errors.write(
|
||||
EntityError::InvalidMovement(format!(
|
||||
"Inconsistent state: Traverser is on a non-existent edge from {} to {}.",
|
||||
from, to
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
return;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let new_traversed = traversed + distance;
|
||||
|
||||
if new_traversed < edge.distance {
|
||||
// Still on the same edge, just update the distance.
|
||||
*position = Position::BetweenNodes {
|
||||
from,
|
||||
to,
|
||||
traversed: new_traversed,
|
||||
};
|
||||
} else {
|
||||
let overflow = new_traversed - edge.distance;
|
||||
let mut moved = false;
|
||||
|
||||
// If we buffered a direction, try to find an edge in that direction
|
||||
if let Some((next_dir, _)) = velocity.next_direction {
|
||||
if let Some(edge) = map.graph.find_edge_in_direction(to, next_dir) {
|
||||
// if edge.permissions.can_traverse(edge) {
|
||||
// *position = Position::BetweenNodes {
|
||||
// from: to,
|
||||
// to: edge.target,
|
||||
// traversed: overflow,
|
||||
// };
|
||||
|
||||
velocity.direction = next_dir; // Remember our new direction
|
||||
velocity.next_direction = None; // Consume the buffered direction
|
||||
moved = true;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't move, try to continue in the current direction
|
||||
if !moved {
|
||||
if let Some(edge) = map.graph.find_edge_in_direction(to, velocity.direction) {
|
||||
// if edge.permissions.can_traverse(edge) {
|
||||
*position = Position::BetweenNodes {
|
||||
from: to,
|
||||
to: edge.target,
|
||||
traversed: overflow,
|
||||
};
|
||||
// } else {
|
||||
// *position = Position::AtNode(to);
|
||||
// velocity.next_direction = None;
|
||||
// }
|
||||
} else {
|
||||
*position = Position::AtNode(to);
|
||||
velocity.next_direction = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles
|
||||
pub fn interact_system(
|
||||
mut events: EventReader<GameEvent>,
|
||||
|
||||
@@ -125,6 +125,7 @@ impl Position {
|
||||
#[derive(Default, Component)]
|
||||
pub struct Velocity {
|
||||
pub direction: Direction,
|
||||
pub next_direction: Option<(Direction, u8)>,
|
||||
pub speed: Option<f32>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,181 +1,161 @@
|
||||
// use tracing::error;
|
||||
use tracing::error;
|
||||
|
||||
// use crate::error::GameResult;
|
||||
use crate::ecs::{NodeId, Position};
|
||||
use crate::error::GameResult;
|
||||
|
||||
// use super::direction::Direction;
|
||||
// use super::graph::{Edge, Graph, NodeId};
|
||||
use super::direction::Direction;
|
||||
use super::graph::{Edge, Graph};
|
||||
|
||||
// /// Manages an entity's movement through the graph.
|
||||
// ///
|
||||
// /// A `Traverser` encapsulates the state of an entity's position and direction,
|
||||
// /// providing a way to advance along the graph's paths based on a given distance.
|
||||
// /// It also handles direction changes, buffering the next intended direction.
|
||||
// pub struct Traverser {
|
||||
// /// The current position of the traverser in the graph.
|
||||
// pub position: Position,
|
||||
// /// The current direction of movement.
|
||||
// pub direction: Direction,
|
||||
// /// Buffered direction change with remaining frame count for timing.
|
||||
// ///
|
||||
// /// The `u8` value represents the number of frames remaining before
|
||||
// /// the buffered direction expires. This allows for responsive controls
|
||||
// /// by storing direction changes for a limited time.
|
||||
// pub next_direction: Option<(Direction, u8)>,
|
||||
// }
|
||||
/// Manages an entity's movement through the graph.
|
||||
///
|
||||
/// A `Traverser` encapsulates the state of an entity's position and direction,
|
||||
/// providing a way to advance along the graph's paths based on a given distance.
|
||||
/// It also handles direction changes, buffering the next intended direction.
|
||||
pub struct Traverser {
|
||||
/// The current position of the traverser in the graph.
|
||||
pub position: Position,
|
||||
/// The current direction of movement.
|
||||
pub direction: Direction,
|
||||
/// Buffered direction change with remaining frame count for timing.
|
||||
///
|
||||
/// The `u8` value represents the number of frames remaining before
|
||||
/// the buffered direction expires. This allows for responsive controls
|
||||
/// by storing direction changes for a limited time.
|
||||
pub next_direction: Option<(Direction, u8)>,
|
||||
}
|
||||
|
||||
// impl Traverser {
|
||||
// /// Creates a new traverser starting at the given node ID.
|
||||
// ///
|
||||
// /// The traverser will immediately attempt to start moving in the initial direction.
|
||||
// pub fn new<F>(graph: &Graph, start_node: NodeId, initial_direction: Direction, can_traverse: &F) -> Self
|
||||
// where
|
||||
// F: Fn(Edge) -> bool,
|
||||
// {
|
||||
// let mut traverser = Traverser {
|
||||
// position: Position::AtNode(start_node),
|
||||
// direction: initial_direction,
|
||||
// next_direction: Some((initial_direction, 1)),
|
||||
// };
|
||||
impl Traverser {
|
||||
/// Sets the next direction for the traverser to take.
|
||||
///
|
||||
/// The direction is buffered and will be applied at the next opportunity,
|
||||
/// typically when the traverser reaches a new node. This allows for responsive
|
||||
/// controls, as the new direction is stored for a limited time.
|
||||
pub fn set_next_direction(&mut self, new_direction: Direction) {
|
||||
if self.direction != new_direction {
|
||||
self.next_direction = Some((new_direction, 30));
|
||||
}
|
||||
}
|
||||
|
||||
// // This will kickstart the traverser into motion
|
||||
// if let Err(e) = traverser.advance(graph, 0.0, can_traverse) {
|
||||
// error!("Traverser initialization error: {}", e);
|
||||
// }
|
||||
/// Advances the traverser along the graph by a specified distance.
|
||||
///
|
||||
/// This method updates the traverser's position based on its current state
|
||||
/// and the distance to travel.
|
||||
///
|
||||
/// - If at a node, it checks for a buffered direction to start moving.
|
||||
/// - If between nodes, it moves along the current edge.
|
||||
/// - If it reaches a node, it attempts to transition to a new edge based on
|
||||
/// the buffered direction or by continuing straight.
|
||||
/// - If no valid move is possible, it stops at the node.
|
||||
///
|
||||
/// Returns an error if the movement is invalid (e.g., trying to move in an impossible direction).
|
||||
pub fn advance<F>(&mut self, graph: &Graph, distance: f32, can_traverse: &F) -> GameResult<()>
|
||||
where
|
||||
F: Fn(Edge) -> bool,
|
||||
{
|
||||
// Decrement the remaining frames for the next direction
|
||||
if let Some((direction, remaining)) = self.next_direction {
|
||||
if remaining > 0 {
|
||||
self.next_direction = Some((direction, remaining - 1));
|
||||
} else {
|
||||
self.next_direction = None;
|
||||
}
|
||||
}
|
||||
|
||||
// traverser
|
||||
// }
|
||||
match self.position {
|
||||
Position::AtNode(node_id) => {
|
||||
// We're not moving, but a buffered direction is available.
|
||||
if let Some((next_direction, _)) = self.next_direction {
|
||||
if let Some(edge) = graph.find_edge_in_direction(node_id, next_direction) {
|
||||
if can_traverse(edge) {
|
||||
// Start moving in that direction
|
||||
self.position = Position::BetweenNodes {
|
||||
from: node_id,
|
||||
to: edge.target,
|
||||
traversed: distance.max(0.0),
|
||||
};
|
||||
self.direction = next_direction;
|
||||
} else {
|
||||
return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(
|
||||
format!(
|
||||
"Cannot traverse edge from {} to {} in direction {:?}",
|
||||
node_id, edge.target, next_direction
|
||||
),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(
|
||||
format!("No edge found in direction {:?} from node {}", next_direction, node_id),
|
||||
)));
|
||||
}
|
||||
|
||||
// /// Sets the next direction for the traverser to take.
|
||||
// ///
|
||||
// /// The direction is buffered and will be applied at the next opportunity,
|
||||
// /// typically when the traverser reaches a new node. This allows for responsive
|
||||
// /// controls, as the new direction is stored for a limited time.
|
||||
// pub fn set_next_direction(&mut self, new_direction: Direction) {
|
||||
// if self.direction != new_direction {
|
||||
// self.next_direction = Some((new_direction, 30));
|
||||
// }
|
||||
// }
|
||||
self.next_direction = None; // Consume the buffered direction regardless of whether we started moving with it
|
||||
}
|
||||
}
|
||||
Position::BetweenNodes { from, to, traversed } => {
|
||||
// There is no point in any of the next logic if we don't travel at all
|
||||
if distance <= 0.0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// /// Advances the traverser along the graph by a specified distance.
|
||||
// ///
|
||||
// /// This method updates the traverser's position based on its current state
|
||||
// /// and the distance to travel.
|
||||
// ///
|
||||
// /// - If at a node, it checks for a buffered direction to start moving.
|
||||
// /// - If between nodes, it moves along the current edge.
|
||||
// /// - If it reaches a node, it attempts to transition to a new edge based on
|
||||
// /// the buffered direction or by continuing straight.
|
||||
// /// - If no valid move is possible, it stops at the node.
|
||||
// ///
|
||||
// /// Returns an error if the movement is invalid (e.g., trying to move in an impossible direction).
|
||||
// pub fn advance<F>(&mut self, graph: &Graph, distance: f32, can_traverse: &F) -> GameResult<()>
|
||||
// where
|
||||
// F: Fn(Edge) -> bool,
|
||||
// {
|
||||
// // Decrement the remaining frames for the next direction
|
||||
// if let Some((direction, remaining)) = self.next_direction {
|
||||
// if remaining > 0 {
|
||||
// self.next_direction = Some((direction, remaining - 1));
|
||||
// } else {
|
||||
// self.next_direction = None;
|
||||
// }
|
||||
// }
|
||||
let edge = graph.find_edge(from, to).ok_or_else(|| {
|
||||
crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(format!(
|
||||
"Inconsistent state: Traverser is on a non-existent edge from {} to {}.",
|
||||
from, to
|
||||
)))
|
||||
})?;
|
||||
|
||||
// match self.position {
|
||||
// Position::AtNode(node_id) => {
|
||||
// // We're not moving, but a buffered direction is available.
|
||||
// if let Some((next_direction, _)) = self.next_direction {
|
||||
// if let Some(edge) = graph.find_edge_in_direction(node_id, next_direction) {
|
||||
// if can_traverse(edge) {
|
||||
// // Start moving in that direction
|
||||
// self.position = Position::BetweenNodes {
|
||||
// from: node_id,
|
||||
// to: edge.target,
|
||||
// traversed: distance.max(0.0),
|
||||
// };
|
||||
// self.direction = next_direction;
|
||||
// } else {
|
||||
// return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(
|
||||
// format!(
|
||||
// "Cannot traverse edge from {} to {} in direction {:?}",
|
||||
// node_id, edge.target, next_direction
|
||||
// ),
|
||||
// )));
|
||||
// }
|
||||
// } else {
|
||||
// return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(
|
||||
// format!("No edge found in direction {:?} from node {}", next_direction, node_id),
|
||||
// )));
|
||||
// }
|
||||
let new_traversed = traversed + distance;
|
||||
|
||||
// self.next_direction = None; // Consume the buffered direction regardless of whether we started moving with it
|
||||
// }
|
||||
// }
|
||||
// Position::BetweenNodes { from, to, traversed } => {
|
||||
// // There is no point in any of the next logic if we don't travel at all
|
||||
// if distance <= 0.0 {
|
||||
// return Ok(());
|
||||
// }
|
||||
if new_traversed < edge.distance {
|
||||
// Still on the same edge, just update the distance.
|
||||
self.position = Position::BetweenNodes {
|
||||
from,
|
||||
to,
|
||||
traversed: new_traversed,
|
||||
};
|
||||
} else {
|
||||
let overflow = new_traversed - edge.distance;
|
||||
let mut moved = false;
|
||||
|
||||
// let edge = graph.find_edge(from, to).ok_or_else(|| {
|
||||
// crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(format!(
|
||||
// "Inconsistent state: Traverser is on a non-existent edge from {} to {}.",
|
||||
// from, to
|
||||
// )))
|
||||
// })?;
|
||||
// If we buffered a direction, try to find an edge in that direction
|
||||
if let Some((next_dir, _)) = self.next_direction {
|
||||
if let Some(edge) = graph.find_edge_in_direction(to, next_dir) {
|
||||
if can_traverse(edge) {
|
||||
self.position = Position::BetweenNodes {
|
||||
from: to,
|
||||
to: edge.target,
|
||||
traversed: overflow,
|
||||
};
|
||||
|
||||
// let new_traversed = traversed + distance;
|
||||
self.direction = next_dir; // Remember our new direction
|
||||
self.next_direction = None; // Consume the buffered direction
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if new_traversed < edge.distance {
|
||||
// // Still on the same edge, just update the distance.
|
||||
// self.position = Position::BetweenNodes {
|
||||
// from,
|
||||
// to,
|
||||
// traversed: new_traversed,
|
||||
// };
|
||||
// } else {
|
||||
// let overflow = new_traversed - edge.distance;
|
||||
// let mut moved = false;
|
||||
// If we didn't move, try to continue in the current direction
|
||||
if !moved {
|
||||
if let Some(edge) = graph.find_edge_in_direction(to, self.direction) {
|
||||
if can_traverse(edge) {
|
||||
self.position = Position::BetweenNodes {
|
||||
from: to,
|
||||
to: edge.target,
|
||||
traversed: overflow,
|
||||
};
|
||||
} else {
|
||||
self.position = Position::AtNode(to);
|
||||
self.next_direction = None;
|
||||
}
|
||||
} else {
|
||||
self.position = Position::AtNode(to);
|
||||
self.next_direction = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // If we buffered a direction, try to find an edge in that direction
|
||||
// if let Some((next_dir, _)) = self.next_direction {
|
||||
// if let Some(edge) = graph.find_edge_in_direction(to, next_dir) {
|
||||
// if can_traverse(edge) {
|
||||
// self.position = Position::BetweenNodes {
|
||||
// from: to,
|
||||
// to: edge.target,
|
||||
// traversed: overflow,
|
||||
// };
|
||||
|
||||
// self.direction = next_dir; // Remember our new direction
|
||||
// self.next_direction = None; // Consume the buffered direction
|
||||
// moved = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // If we didn't move, try to continue in the current direction
|
||||
// if !moved {
|
||||
// if let Some(edge) = graph.find_edge_in_direction(to, self.direction) {
|
||||
// if can_traverse(edge) {
|
||||
// self.position = Position::BetweenNodes {
|
||||
// from: to,
|
||||
// to: edge.target,
|
||||
// traversed: overflow,
|
||||
// };
|
||||
// } else {
|
||||
// self.position = Position::AtNode(to);
|
||||
// self.next_direction = None;
|
||||
// }
|
||||
// } else {
|
||||
// self.position = Position::AtNode(to);
|
||||
// self.next_direction = None;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/atlas_data.rs"));
|
||||
|
||||
use crate::constants::CANVAS_SIZE;
|
||||
use crate::ecs::interact::interact_system;
|
||||
use crate::ecs::interact::{interact_system, movement_system};
|
||||
use crate::ecs::render::{directional_render_system, render_system, BackbufferResource, MapTextureResource};
|
||||
use crate::ecs::{DeltaTime, DirectionalAnimated, GlobalState, PlayerBundle, PlayerControlled, Position, Renderable, Velocity};
|
||||
use crate::entity::direction::Direction;
|
||||
@@ -132,7 +132,11 @@ impl Game {
|
||||
let player = PlayerBundle {
|
||||
player: PlayerControlled,
|
||||
position: Position::AtNode(pacman_start_node),
|
||||
velocity: Velocity::default(),
|
||||
velocity: Velocity {
|
||||
direction: Direction::Up,
|
||||
next_direction: None,
|
||||
speed: Some(1.0),
|
||||
},
|
||||
sprite: Renderable {
|
||||
sprite: SpriteAtlas::get_tile(&atlas, "pacman/full.png")
|
||||
.ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?,
|
||||
@@ -164,7 +168,16 @@ impl Game {
|
||||
},
|
||||
});
|
||||
|
||||
schedule.add_systems((handle_input, interact_system, directional_render_system, render_system).chain());
|
||||
schedule.add_systems(
|
||||
(
|
||||
handle_input,
|
||||
interact_system,
|
||||
movement_system,
|
||||
directional_render_system,
|
||||
render_system,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
|
||||
// Spawn player
|
||||
world.spawn(player);
|
||||
|
||||
Reference in New Issue
Block a user