add generation

This commit is contained in:
Matthieu Jolimaitre 2024-11-19 05:00:12 +01:00
parent b204118b82
commit b5c6067979
10 changed files with 250 additions and 130 deletions

23
Cargo.lock generated
View file

@ -1127,6 +1127,16 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "ctrlc"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "cursor-icon" name = "cursor-icon"
version = "1.1.0" version = "1.1.0"
@ -1150,6 +1160,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy_eventwork", "bevy_eventwork",
"ctrlc",
"itermore", "itermore",
"rand", "rand",
"serde", "serde",
@ -1817,6 +1828,18 @@ dependencies = [
"jni-sys", "jni-sys",
] ]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases 0.2.1",
"libc",
]
[[package]] [[package]]
name = "nonmax" name = "nonmax"
version = "0.5.5" version = "0.5.5"

View file

@ -8,6 +8,7 @@ bevy = { version = "0.14.2", default-features = false, features = [
"multi_threaded", "multi_threaded",
] } ] }
bevy_eventwork = "0.9.0" bevy_eventwork = "0.9.0"
ctrlc = "3.4.5"
itermore = { version = "0.7.1", features = ["array_chunks"] } itermore = { version = "0.7.1", features = ["array_chunks"] }
rand = "0.8.5" rand = "0.8.5"
serde = { version = "1.0.215", features = ["derive"] } serde = { version = "1.0.215", features = ["derive"] }

View file

@ -1,16 +1,11 @@
use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network}; use bevy_eventwork::{tcp::TcpProvider, Network};
use std::{ use std::{
io::{stdin, stdout, Stdout}, io::{stdin, stdout},
process, process,
sync::{mpsc, Arc, Mutex}, sync::{mpsc, Arc, Mutex},
thread::{self, JoinHandle}, thread,
};
use termion::{
cursor::{self, HideCursor},
event::Key,
input::TermRead,
raw::{IntoRawMode, RawTerminal},
}; };
use termion::{clear, cursor, event::Key, input::TermRead, raw::IntoRawMode};
use bevy::prelude::*; use bevy::prelude::*;
@ -22,7 +17,7 @@ pub struct InputPlugin;
impl Plugin for InputPlugin { impl Plugin for InputPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
let (rec, _thread) = spawn_reader(); let rec = spawn_reader();
let rec = Arc::new(Mutex::new(rec)); let rec = Arc::new(Mutex::new(rec));
app.insert_resource(InputReceiver(rec)) app.insert_resource(InputReceiver(rec))
.add_event::<KeyEvent>() .add_event::<KeyEvent>()
@ -37,23 +32,36 @@ pub struct InputReceiver(Arc<Mutex<mpsc::Receiver<Key>>>);
#[derive(Debug, Event)] #[derive(Debug, Event)]
pub struct KeyEvent(Key); pub struct KeyEvent(Key);
fn spawn_reader() -> (mpsc::Receiver<Key>, JoinHandle<()>) { fn spawn_reader() -> mpsc::Receiver<Key> {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let stdout = stdout().into_raw_mode().unwrap(); let (exit_tx, exit_rx) = mpsc::channel();
thread::spawn(move || {
let raw_mode = stdout().into_raw_mode().unwrap();
println!("{}", cursor::Hide); println!("{}", cursor::Hide);
exit_rx.recv().ok();
drop(raw_mode);
println!("{}{}", cursor::Restore, clear::All);
process::exit(0);
});
let exit_tx_ = exit_tx.clone();
ctrlc::set_handler(move || {
exit_tx_.send(()).ok();
})
.unwrap();
thread::spawn(move || {
let mut keys = stdin().keys(); let mut keys = stdin().keys();
while let Some(Ok(key)) = keys.next() { while let Some(Ok(key)) = keys.next() {
if key == Key::Esc { if key == Key::Esc {
println!("exitting."); exit_tx.send(()).ok();
println!("{}", cursor::Restore);
drop(stdout);
process::exit(0);
} }
tx.send(key).ok(); tx.send(key).ok();
} }
}); });
(rx, thread)
rx
} }
fn try_read_keys(receiver: Res<InputReceiver>, mut writer: EventWriter<KeyEvent>) { fn try_read_keys(receiver: Res<InputReceiver>, mut writer: EventWriter<KeyEvent>) {

View file

@ -3,7 +3,7 @@ use bevy::{prelude::*, utils::hashbrown::HashSet};
use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network}; use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network};
use super::{ use super::{
physics::{update_physics_collisions, Pos}, physics::{self, Pos},
player::Player, player::Player,
}; };
@ -13,7 +13,7 @@ impl Plugin for DisplayPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<RenderUpdate>() app.add_event::<RenderUpdate>()
// .add_systems(Update, rerender_players) // .add_systems(Update, rerender_players)
.add_systems(Update, on_updates.after(update_physics_collisions)); .add_systems(Update, on_updates.after(physics::update_physics_pos));
} }
} }
@ -35,8 +35,8 @@ fn on_updates(
let mut to_render = HashSet::new(); let mut to_render = HashSet::new();
for RenderUpdate(event_pos) in updates.read() { for RenderUpdate(event_pos) in updates.read() {
for (Player(connection_id), player_pos) in &players { for (Player(connection_id), player_pos) in &players {
if player_pos.pos().distance_squared(*event_pos) <= RENDER_DISTANCE_SQ { if player_pos.0.distance_squared(*event_pos) <= RENDER_DISTANCE_SQ {
to_render.insert((*connection_id, player_pos.pos())); to_render.insert((*connection_id, player_pos.0));
} }
} }
} }
@ -54,14 +54,14 @@ fn render(
) { ) {
let updates = displayables let updates = displayables
.iter() .iter()
.filter(|(pos, _)| pos.pos().distance_squared(player_pos) <= RENDER_DISTANCE_SQ) .filter(|(pos, _)| pos.0.distance_squared(player_pos) <= RENDER_DISTANCE_SQ)
.map(|(pos, Display(display))| (pos.pos() - player_pos, *display)) .map(|(pos, Display(display))| (pos.0 - player_pos, *display))
.collect(); .collect();
net.send_message(id, MapUpdates(updates)).ok(); net.send_message(id, MapUpdates(updates)).ok();
} }
fn rerender_players(players: Query<&Pos, With<Player>>, mut writer: EventWriter<RenderUpdate>) { fn rerender_players(players: Query<&Pos, With<Player>>, mut writer: EventWriter<RenderUpdate>) {
for Pos(pos) in &players { for Pos(pos) in &players {
writer.send(RenderUpdate(pos.as_ivec2())); writer.send(RenderUpdate(*pos));
} }
} }

View file

@ -1,41 +1,94 @@
use bevy::{prelude::*, utils::HashSet}; use bevy::{prelude::*, utils::HashSet};
use super::physics::Pos; use super::{
display::RenderUpdate,
map::{self, spawn_block},
physics::Pos,
};
pub struct GenerationPlugin; pub struct GenerationPlugin;
impl Plugin for GenerationPlugin { impl Plugin for GenerationPlugin {
fn build(&self, app: &mut bevy::prelude::App) { fn build(&self, app: &mut bevy::prelude::App) {
app.insert_resource(GeneratedRegistry::default()) app.insert_resource(GeneratedRegistry::default())
// .add_systems(schedule, systems) .add_systems(Update, chunk_loading);
;
} }
} }
fn chunk_loading(cmd: &mut Commands, loaders: Query<&Pos, With<Loader>>, reg: ResMut<GeneratedRegistry>) { fn chunk_loading(
for pos in &loaders { mut cmd: Commands,
chunks_surrounding(&pos.pos()); loaders: Query<&Pos, With<Loader>>,
mut reg: ResMut<GeneratedRegistry>,
mut updater: EventWriter<RenderUpdate>,
) {
for loader in &loaders {
for pos in chunks_surrounding(&loader.0, GEN_DISTANCE) {
reg.load_chunk(&mut cmd, &mut updater, pos);
}
} }
} }
const GEN_DISTANCE: i32 = 1;
const CHUNK_WIDTH: i32 = 8; const CHUNK_WIDTH: i32 = 8;
fn chunks_surrounding(pos: &IVec2) -> Vec<IVec2> { fn chunks_surrounding(pos: &IVec2, radius: i32) -> Vec<IVec2> {
todo!() let center_chunk = pos.div_euclid(IVec2::splat(CHUNK_WIDTH));
} (-radius..=radius)
.flat_map(move |x| (-radius..=radius).map(move |y| center_chunk + IVec2::new(x, y)))
fn snap(value: i32, increment: i32) -> i32 { .collect()
todo!()
} }
#[test] #[test]
fn test_snap() { fn test_euclid() {
// assert_eq!(); assert_eq!((5_i32).div_euclid(2), 2);
assert_eq!((4_i32).div_euclid(2), 2);
assert_eq!((3_i32).div_euclid(2), 1);
assert_eq!((2_i32).div_euclid(2), 1);
assert_eq!((1_i32).div_euclid(2), 0);
assert_eq!((0_i32).div_euclid(2), 0);
assert_eq!((-1_i32).div_euclid(2), -1);
assert_eq!((-2_i32).div_euclid(2), -1);
assert_eq!((-3_i32).div_euclid(2), -2);
assert_eq!((-4_i32).div_euclid(2), -2);
assert_eq!((-5_i32).div_euclid(2), -3);
}
#[test]
fn test_snap_mod() {
assert_eq!((5_i32).rem_euclid(2), 1);
assert_eq!((4_i32).rem_euclid(2), 0);
assert_eq!((3_i32).rem_euclid(2), 1);
assert_eq!((2_i32).rem_euclid(2), 0);
assert_eq!((1_i32).rem_euclid(2), 1);
assert_eq!((0_i32).rem_euclid(2), 0);
assert_eq!((-1_i32).rem_euclid(2), 1);
assert_eq!((-2_i32).rem_euclid(2), 0);
assert_eq!((-3_i32).rem_euclid(2), 1);
assert_eq!((-4_i32).rem_euclid(2), 0);
assert_eq!((-5_i32).rem_euclid(2), 1);
} }
#[derive(Debug, Resource, Default)] #[derive(Debug, Resource, Default)]
pub struct GeneratedRegistry(HashSet<IVec2>); pub struct GeneratedRegistry(HashSet<IVec2>);
impl GeneratedRegistry {
pub fn load_chunk(&mut self, cmd: &mut Commands, rerender: &mut EventWriter<RenderUpdate>, chunk: IVec2) {
if self.0.contains(&chunk) {
return;
}
println!("Loading chunk indexed at position {chunk:?}.");
let from = chunk * CHUNK_WIDTH;
let to = from + IVec2::splat(CHUNK_WIDTH);
for (pos, spawnable) in generate(from, to) {
match spawnable {
Spawnable::Block(display, mass) => spawn_block(cmd, rerender, pos, display, mass),
}
}
self.0.insert(chunk);
}
}
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct Loader; pub struct Loader;
@ -44,3 +97,19 @@ pub struct Generated;
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct Chunk(IVec2); pub struct Chunk(IVec2);
enum Spawnable {
Block((char, char), usize),
}
fn generate(from: IVec2, _to: IVec2) -> Vec<(IVec2, Spawnable)> {
let structure = "
XX XX
XX XX
";
map::str_to_struct(structure)
.into_iter()
.map(|(pos, display, mass)| (pos + from, Spawnable::Block(display, mass)))
.collect()
}

View file

@ -4,15 +4,22 @@ use bevy::prelude::*;
use itermore::IterArrayChunks; use itermore::IterArrayChunks;
use super::{ use super::{
display::Display, display::{Display, RenderUpdate},
physics::{Mass, Pos}, physics::{Forces, Mass, Pos},
}; };
fn spawn_block(cmd: &mut Commands, pos: IVec2, display: (char, char), mass: usize) { pub fn spawn_block(
cmd.spawn((Pos(pos.as_vec2()), Mass(mass), Display(display))); cmd: &mut Commands,
rerender: &mut EventWriter<RenderUpdate>,
pos: IVec2,
display: (char, char),
mass: usize,
) {
cmd.spawn((Forces::zero(), Pos(pos), Mass(mass), Display(display)));
rerender.send(RenderUpdate(pos));
} }
fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> { pub fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> {
text.lines() text.lines()
.enumerate() .enumerate()
.flat_map(|(y, line)| { .flat_map(|(y, line)| {
@ -25,17 +32,17 @@ fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> {
.collect() .collect()
} }
fn mass_of(tiles: (char, char)) -> usize { pub fn mass_of(tiles: (char, char)) -> usize {
match tiles { match tiles {
('#', '#') => 100, ('#', '#') => 100,
('X', 'X') => 10, ('X', 'X') => 10,
('[', ']') => 5, ('[', ']') => 8,
('(', ')') => 1, ('(', ')') => 5,
_ => 20, _ => 20,
} }
} }
pub fn spawn_debug_map(mut cmd: Commands) { pub fn spawn_debug_map(mut cmd: Commands, mut rerender: EventWriter<RenderUpdate>) {
let raw = " let raw = "
() ()
@ -49,6 +56,6 @@ pub fn spawn_debug_map(mut cmd: Commands) {
"; ";
for (pos, display, mass) in str_to_struct(raw) { for (pos, display, mass) in str_to_struct(raw) {
spawn_block(&mut cmd, pos, display, mass); spawn_block(&mut cmd, &mut rerender, pos, display, mass);
} }
} }

View file

@ -16,12 +16,12 @@ impl Plugin for MetricsPlugin {
} }
} }
fn show_metrics(time: Res<Time>, mut sys: ResMut<SysRes>, players: Query<(), With<Player>>) { fn show_metrics(time: Res<Time>, mut sys: ResMut<SysRes>, players: Query<(), With<Player>>, entities: Query<Entity>) {
sys.0.refresh_all();
let cpu = sys.0.global_cpu_usage();
let player_count = players.iter().count(); let player_count = players.iter().count();
let entity_count = entities.iter().count();
let time_delta = time.delta_seconds(); let time_delta = time.delta_seconds();
let cpu = sys.0.global_cpu_usage();
sys.0.refresh_all();
let up = cursor::Up(1); let up = cursor::Up(1);
println!(" | players {player_count:2} | tick {time_delta:2.3} | cpu {cpu:2.1}{up} "); println!(" | ent {entity_count:2} | ply {player_count:2} | tick {time_delta:2.3} | cpu {cpu:2.1}{up} ");
} }

View file

@ -61,7 +61,7 @@ fn respond_greet(
net.send_message(source, ServerGreeting).ok(); net.send_message(source, ServerGreeting).ok();
for (pos, player) in &players { for (pos, player) in &players {
if player.0 == source { if player.0 == source {
render_update.send(RenderUpdate(pos.pos())); render_update.send(RenderUpdate(pos.0));
} }
} }
} }

View file

@ -6,92 +6,103 @@ pub struct PhysicsPlugin;
impl Plugin for PhysicsPlugin { impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(Update, update_physics_forces.after(player::handle_move))
Update, .add_systems(Update, update_physics_pos.after(update_physics_forces));
(
update_physics_forces.after(player::handle_move),
update_physics_center.after(update_physics_forces),
update_physics_collisions.after(update_physics_center),
),
);
} }
} }
#[derive(Debug, Component, Clone)] #[derive(Debug, Component, Clone)]
pub struct Pos(pub Vec2); pub struct Pos(pub IVec2);
impl Pos {
pub fn fpos(&self) -> Vec2 {
self.0
}
pub fn pos_center(&self) -> Vec2 {
self.pos().as_vec2()
}
pub fn pos(&self) -> IVec2 {
self.0.round().as_ivec2()
}
pub fn dir_to_center(&self) -> Vec2 {
self.pos_center() - self.fpos()
}
}
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct Mass(pub usize); pub struct Mass(pub usize);
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct Forces(Vec2); pub struct Forces(pub IVec2);
impl Forces { impl Forces {
pub fn zero() -> Self { pub fn zero() -> Self {
Self::new(Vec2::ZERO) Self::new(IVec2::ZERO)
} }
pub fn new(forces: Vec2) -> Self {
pub fn new(forces: IVec2) -> Self {
Self(forces) Self(forces)
} }
pub fn add(&mut self, force: Vec2) {
pub fn add(&mut self, force: IVec2) {
self.0 += force; self.0 += force;
} }
pub fn movement(&self, mass: usize) -> IVec2 {
self.0 / mass as i32
}
pub fn movement_step(&self, mass: usize) -> IVec2 {
self.movement(mass).clamp(IVec2::new(-1, -1), IVec2::new(1, 1))
}
} }
const MAX_MOVEMENT_PER_TICK: f32 = 0.3; // 1. reduce force of adjascent objects.
pub fn update_physics_forces(mut forces: Query<(&mut Forces, &Pos, &Mass)>) {
let mut combinations = forces.iter_combinations_mut();
while let Some([left, right]) = combinations.fetch_next() {
let (mut l_forces, l_pos, Mass(l_mass)) = left;
let (mut r_forces, r_pos, Mass(r_mass)) = right;
let l_step = l_forces.movement_step(*l_mass);
let r_step = r_forces.movement_step(*r_mass);
let (l_mass, r_mass) = (*l_mass as i32, *r_mass as i32);
let same_step_x = r_step.x == l_step.x;
let same_step_y = r_step.y == l_step.y;
fn update_physics_forces(mut blocks: Query<(&mut Pos, &mut Forces)>, mut updater: EventWriter<RenderUpdate>) { if l_pos.0.distance_squared(r_pos.0) > 2 {
for (mut pos, mut forces) in &mut blocks { continue;
let old_pos = pos.clone(); }
let applied = forces.0; if !same_step_x {
let applied = applied.clamp_length_max(MAX_MOVEMENT_PER_TICK); let l_will_overlap_x = l_pos.0 + IVec2::X * l_step.x == r_pos.0;
pos.0 += applied; let r_will_overlap_x = r_pos.0 + IVec2::X * r_step.x == l_pos.0;
forces.0 -= applied;
if old_pos.pos() != pos.pos() { if l_will_overlap_x {
updater.send(RenderUpdate(old_pos.pos())); r_forces.0.x += l_step.x * l_mass;
l_forces.0.x -= l_step.x * r_mass.min(l_mass);
}
if r_will_overlap_x {
l_forces.0.x += r_step.x * r_mass;
r_forces.0.x -= r_step.x * l_mass.min(r_mass);
}
}
if !same_step_y {
let l_will_overlap_y = l_pos.0 + IVec2::Y * l_step.y == r_pos.0;
let r_will_overlap_y = r_pos.0 + IVec2::Y * r_step.y == l_pos.0;
if l_will_overlap_y {
r_forces.0.y += l_step.y * l_mass;
l_forces.0.y -= l_step.y * r_mass.min(l_mass);
}
if r_will_overlap_y {
l_forces.0.y += r_step.y * r_mass;
r_forces.0.y -= r_step.y * l_mass.min(r_mass);
}
} }
} }
} }
pub fn update_physics_collisions(mut blocks: Query<(&mut Pos, &Mass)>) { // 2. apply 1 step of forces.
let mut combinations = blocks.iter_combinations_mut();
while let Some([(mut a_pos, Mass(a_mass)), (mut b_pos, Mass(b_mass))]) = combinations.fetch_next() {
if a_pos.pos() == b_pos.pos() {
let a_to_b = (b_pos.fpos() - a_pos.fpos()).normalize_or(Vec2::Y);
let total_mass = (a_mass + b_mass) as f32;
let a_mass_frac = (*a_mass) as f32 / total_mass;
let b_mass_frac = (*b_mass) as f32 / total_mass;
let a_movement = (-a_to_b) * b_mass_frac;
let b_movement = (a_to_b) * a_mass_frac;
a_pos.0 += a_movement;
b_pos.0 += b_movement;
}
}
}
pub fn update_physics_center(mut blocks: Query<(&Pos, &mut Forces)>) { pub fn update_physics_pos(mut bodies: Query<(&mut Forces, &mut Pos, &Mass)>, mut updater: EventWriter<RenderUpdate>) {
for (pos, mut forces) in &mut blocks { for (mut forces, mut pos, Mass(mass)) in &mut bodies {
let dir = pos.pos_center() - pos.0; let step = forces.movement_step(*mass);
forces.add(dir * 0.75);
if step == IVec2::ZERO {
continue;
}
updater.send(RenderUpdate(pos.0));
pos.0 += step;
forces.0 -= step * *mass as i32;
updater.send(RenderUpdate(pos.0));
} }
} }

View file

@ -4,6 +4,7 @@ use bevy_eventwork::{tcp::TcpProvider, AppNetworkMessage, ConnectionId, NetworkD
use super::{ use super::{
display::{Display, RenderUpdate}, display::{Display, RenderUpdate},
generation::Loader,
physics::{Forces, Mass, Pos}, physics::{Forces, Mass, Pos},
}; };
@ -32,14 +33,17 @@ fn player_display_for_dir(dir: IVec2) -> (char, char) {
} }
} }
const PLAYER_MASS: usize = 10;
pub fn spawn_player(cmd: &mut Commands, conn_id: ConnectionId, pos: IVec2) { pub fn spawn_player(cmd: &mut Commands, conn_id: ConnectionId, pos: IVec2) {
cmd.spawn(( cmd.spawn((
Player(conn_id), Player(conn_id),
Pos(pos.as_vec2()), Loader,
Pos(pos),
Display(player_display_for_dir(IVec2::X)), Display(player_display_for_dir(IVec2::X)),
Dir(IVec2::X), Dir(IVec2::X),
Forces::zero(), Forces::zero(),
Mass(10), Mass(PLAYER_MASS),
)); ));
} }
@ -52,21 +56,18 @@ pub fn remove_player(cmd: &mut Commands, conn_id: ConnectionId, players: &Query<
} }
pub fn handle_move( pub fn handle_move(
mut messages: EventReader<NetworkData<Move>>, mut moves: EventReader<NetworkData<Move>>,
mut players: Query<(&Player, &mut Forces, &Pos, &mut Dir, &mut Display)>, mut players: Query<(&Player, &mut Forces, &mut Dir, &mut Display)>,
) { ) {
for message in messages.read() { for move_ in moves.read() {
let conn_id = message.source(); let conn_id = move_.source();
for (Player(id), mut forces, pos, mut dir, mut display) in &mut players { for (Player(id), mut forces, mut dir, mut display) in &mut players {
if conn_id == id { if conn_id == id {
let dir_movement = message.0.as_vec2().normalize_or_zero(); let mov_dir = move_.0.clamp(IVec2::splat(-1), IVec2::splat(1));
let added_force = dir_movement + pos.dir_to_center(); forces.0 = mov_dir * PLAYER_MASS as i32;
forces.add(added_force); if !(mov_dir == IVec2::ZERO) {
dir.0 = mov_dir;
let idir = dir_movement.as_ivec2(); display.0 = player_display_for_dir(mov_dir);
if !(idir == IVec2::ZERO) {
dir.0 = idir;
display.0 = player_display_for_dir(idir);
} }
} }
} }