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