Skip to content

Commit fee4ab5

Browse files
committed
feat(movement): add pathfinding with mouse click & diagonal movement
delay
1 parent f41a93c commit fee4ab5

8 files changed

Lines changed: 225 additions & 22 deletions

File tree

Cargo.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dyrah_client/src/components.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub struct WorldPos {
1111
#[derive(Debug, Serialize, Deserialize, Default)]
1212
pub struct TargetWorldPos {
1313
pub vec: Vec2,
14+
pub path: Option<Vec<Vec2>>,
1415
}
1516

1617
#[derive(Debug)]

dyrah_client/src/game.rs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ impl Game {
117117
let player = self.world.spawn((
118118
Player,
119119
WorldPos { vec: position },
120-
TargetWorldPos { vec: position },
120+
TargetWorldPos {
121+
vec: position,
122+
path: None,
123+
},
121124
Sprite {
122125
anim: Animation::new(1, 6, 6, 0.2),
123126
frame_size: Vec2::splat(64.0),
@@ -137,10 +140,13 @@ impl Game {
137140
self.world.despawn(player);
138141
}
139142
}
140-
ServerMessage::PlayerMoved { id, position } => {
143+
ServerMessage::PlayerMoved { id, position, path } => {
141144
if let Some((player, _)) = self.lobby.get(&id) {
142145
let mut target_pos = self.world.get_mut::<TargetWorldPos>(*player).unwrap();
143146
target_pos.vec = position;
147+
if path.is_some() {
148+
target_pos.path = path;
149+
}
144150
}
145151
}
146152
ServerMessage::ChatReceived { sender_id, text } => {
@@ -208,7 +214,11 @@ impl Game {
208214
});
209215

210216
self.world.query(
211-
|_, _: &Player, pos: &mut WorldPos, target_pos: &TargetWorldPos, spr: &mut Sprite| {
217+
|_,
218+
_: &Player,
219+
pos: &mut WorldPos,
220+
target_pos: &mut TargetWorldPos,
221+
spr: &mut Sprite| {
212222
if pos.vec != target_pos.vec {
213223
pos.vec = pos.vec.lerp(target_pos.vec, 0.1);
214224

@@ -224,12 +234,13 @@ impl Game {
224234
}
225235
} else {
226236
spr.anim.set_frame(0);
237+
target_pos.path = None;
227238
}
228239
},
229240
);
230241

231242
self.last_input_time += timer.delta;
232-
if self.last_input_time >= 0.2 && moving {
243+
if self.last_input_time >= 0.3 && moving {
233244
self.last_input_time = 0.0;
234245

235246
let msg = ClientMessage::PlayerUpdate {
@@ -307,6 +318,48 @@ impl Game {
307318

308319
self.map.draw_tiles(gfx);
309320

321+
// after self.map.draw_tiles(gfx);
322+
if let Some(player) = self.player {
323+
if let Some(target) = self.world.get::<TargetWorldPos>(player) {
324+
if let Some(path) = &target.path {
325+
let tile_size = 32.0;
326+
let half = tile_size / 2.0;
327+
328+
// draw lines between tile centers
329+
if path.len() >= 2 {
330+
let centers: Vec<Vec2> =
331+
path.iter().map(|&p| p + Vec2::splat(half)).collect();
332+
gfx.polyline()
333+
.points(&centers)
334+
.thickness(1.0)
335+
.color(Color::new([0.0, 0.0, 0.0, 1.0]));
336+
}
337+
338+
// draw tile outlines
339+
for &point in path {
340+
let s = tile_size;
341+
gfx.polyline()
342+
.points(&[
343+
point,
344+
point + Vec2::new(s, 0.0),
345+
point + Vec2::new(s, s),
346+
point + Vec2::new(0.0, s),
347+
])
348+
.closed(true)
349+
.thickness(1.5)
350+
.color(Color::new([0.5, 0.0, 1.0, 0.8]));
351+
}
352+
for &point in path {
353+
gfx.polygon()
354+
.at(point + Vec2::splat(half))
355+
.radius(2.0)
356+
.segments(8)
357+
.color(Color::new([0.5, 0.0, 1.0, 1.0]));
358+
}
359+
}
360+
}
361+
}
362+
310363
let mut latest_msgs: HashMap<Entity, (String, f32)> = HashMap::new();
311364
for (username, text, age) in &self.chat_messages {
312365
// find the entity whose username matches

dyrah_server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ dyrah_shared = { path = "../dyrah_shared" }
1313
secs = "0.1.0"
1414
rusqlite = { version = "0.38.0", features = ["bundled"] }
1515
argon2 = { version = "0.5.3", features = ["std"] }
16+
pathfinding = "4.14.0"

dyrah_server/src/components.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub struct TilePos {
99
#[derive(Debug, Serialize, Deserialize, Default)]
1010
pub struct TargetTilePos {
1111
pub vec: IVec2,
12+
pub path: Option<Vec<IVec2>>,
13+
pub delay: f32,
1214
}
1315

1416
pub struct Collider;

dyrah_server/src/game.rs

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{collections::HashMap, path::Path, time::Duration};
22

33
use bincode::{deserialize, serialize};
4+
use glam::IVec2;
45
use secs::{Entity, World};
56
use wrym::{
67
Reliability,
@@ -134,18 +135,53 @@ impl Game {
134135
self.world.get_mut::<TargetTilePos>(player).unwrap();
135136
let mut tile_pos = self.world.get_mut::<TilePos>(player).unwrap();
136137

137-
let next_pos = target_pos.vec + input.to_direction();
138+
if let Some(mouse_tile_pos) = input.mouse_tile_pos {
139+
if let Some(path) = self.map.find_path(
140+
tile_pos.vec,
141+
mouse_tile_pos,
142+
&self.collision_grid,
143+
) {
144+
let world_path: Vec<_> = path
145+
.iter()
146+
.map(|&t| self.map.tiled.tile_to_world(t))
147+
.collect();
138148

139-
if self.map.is_walkable(next_pos, &self.collision_grid) {
140-
target_pos.vec = next_pos;
141-
tile_pos.vec = next_pos;
149+
target_pos.path = Some(path);
142150

143-
let msg = ServerMessage::PlayerMoved {
144-
id,
145-
position: self.map.tiled.tile_to_world(tile_pos.vec),
146-
};
147-
self.server
148-
.broadcast(&serialize(&msg).unwrap(), Reliability::Unreliable);
151+
let msg = ServerMessage::PlayerMoved {
152+
id,
153+
position: self.map.tiled.tile_to_world(tile_pos.vec),
154+
path: Some(world_path),
155+
};
156+
self.server.broadcast(
157+
&serialize(&msg).unwrap(),
158+
Reliability::Unreliable,
159+
);
160+
}
161+
} else {
162+
let dir = input.to_direction();
163+
if dir != IVec2::ZERO {
164+
let next_pos = tile_pos.vec + dir;
165+
if self.map.is_walkable(next_pos, &self.collision_grid) {
166+
target_pos.vec = next_pos;
167+
target_pos.path = None;
168+
tile_pos.vec = next_pos;
169+
170+
// diagonal costs 1.5x more than cardinal
171+
target_pos.delay =
172+
if dir.x != 0 && dir.y != 0 { 0.45 } else { 0.3 };
173+
174+
let msg = ServerMessage::PlayerMoved {
175+
id,
176+
position: self.map.tiled.tile_to_world(tile_pos.vec),
177+
path: None,
178+
};
179+
self.server.broadcast(
180+
&serialize(&msg).unwrap(),
181+
Reliability::Unreliable,
182+
);
183+
}
184+
}
149185
}
150186
}
151187
}
@@ -154,8 +190,44 @@ impl Game {
154190
}
155191
}
156192

157-
pub fn update(&mut self, _dt: f32) {
193+
pub fn update(&mut self, dt: f32) {
158194
self.collision_grid.update(&self.map, &self.world);
195+
196+
let moving: Vec<(NetId, Entity)> = self
197+
.lobby
198+
.iter()
199+
.map(|(&id, &(entity, _))| (id, entity))
200+
.collect();
201+
202+
for (id, player) in moving {
203+
let mut target_pos = self.world.get_mut::<TargetTilePos>(player).unwrap();
204+
let mut tile_pos = self.world.get_mut::<TilePos>(player).unwrap();
205+
206+
target_pos.delay -= dt;
207+
208+
if tile_pos.vec == target_pos.vec && target_pos.delay <= 0.0 {
209+
if let Some(path) = target_pos.path.as_mut() {
210+
if let Some(next) = path.first().copied() {
211+
if self.map.is_walkable(next, &self.collision_grid) {
212+
path.remove(0);
213+
target_pos.vec = next;
214+
tile_pos.vec = next;
215+
target_pos.delay = 0.3; // matches client rate
216+
217+
let msg = ServerMessage::PlayerMoved {
218+
id,
219+
position: self.map.tiled.tile_to_world(tile_pos.vec),
220+
path: None,
221+
};
222+
self.server
223+
.broadcast(&serialize(&msg).unwrap(), Reliability::Unreliable);
224+
} else {
225+
target_pos.path = None;
226+
}
227+
}
228+
}
229+
}
230+
}
159231
}
160232

161233
fn spawn_player(&mut self, id: NetId, username: String, addr: &String) {
@@ -178,7 +250,11 @@ impl Game {
178250
let player = self.world.spawn((
179251
Player,
180252
TilePos { vec: spawn_pos },
181-
TargetTilePos { vec: spawn_pos },
253+
TargetTilePos {
254+
vec: spawn_pos,
255+
path: None,
256+
delay: 0.0,
257+
},
182258
Collider,
183259
));
184260
self.lobby.insert(id, (player, username.clone()));

0 commit comments

Comments
 (0)