I'm developing a MMO RPG using Rust and bevy both in the client and the server, which means that I'm using ECS on both sides. My game is tile based, so each tile is a point in my map in the server, like this:
I am trying to implement a movement system which works with a movement speed, but all the calculations are being done in the server and it works fine, my problem here is in the client. The problem is that, when the user clicks to move in the screen, I am sending a message to the server to set a path to that destination, and since during the path things can occur, like, players walking through or enemies, I'm sending one tile per time that the player should move after the amount of time based on the move speed. So let's say that the player can move 1 tile per second, if I set a path that has 10 tiles, it will take 10 seconds to walk.
The problem happens that sometimes in the client, the player ends up with a transform that is not aligned with the tile, like if it is losing some pixels during the walk animation. At first I thought the server is sending messages faster than the client expects and it starts moving from a point in the middle of the way to other tile, so every time the server sends a message to walk to a direction, I store that in a queue and only starts the next move once the first is done. But this didn't solve the issue and I've been trying to figure this out for days now and I just don't know how to do this.
Each frame I update the translation a little bit based on the move speed, but at the end the translation must be a multiple of 32 since my game is all 32x32 pixels. I will put some code here, but I think the problem is not the code but the way that I'm doing things, so I would like a new idea.
for (mut transform, entity, moving, mut sprite, movement, mut direction, mut moves) in
query.iter_mut()
{
let mut should_move = false;
if let Some(moving) = moving {
if (transform.translation.x < moving.final_destination.translation.x - 2.
&& moving.x_move > 0.)
|| (transform.translation.x > moving.final_destination.translation.x + 2.
&& moving.x_move < 0.)
|| (moving.y_move > 0.0
&& transform.translation.y > moving.final_destination.translation.y + 2.)
|| (moving.y_move < 0.0
&& transform.translation.y < moving.final_destination.translation.y - 2.)
{
let constant = 32. / ((1. / movement.0 as f32) * 200.);
transform.translation = transform.translation.add(Vec3::new(
moving.x_move * constant,
-moving.y_move * constant,
0.0,
));
} else {
if sprite.index % 3 == 0 {
sprite.index += 1;
} else if sprite.index % 3 == 2 {
sprite.index -= 1;
}
transform.translation.y = moving.final_destination.translation.y;
transform.translation.x = moving.final_destination.translation.x;
for (entity, t) in destination_query.iter() {
if t.translation.x == transform.translation.x
&& t.translation.y + 10.0 == transform.translation.y
{
commands.entity(entity).despawn_recursive();
}
}
should_move = true;
}
} else {
should_move = true;
}
if should_move {
if moves.0.len() > 0 {
let m = moves.0.remove(0);
commands.entity(entity).insert(m.clone());
if m.x_move > 0.0 {
*direction = Direction::Right;
sprite.index = 7;
} else if m.x_move < 0.0 {
*direction = Direction::Left;
sprite.index = 4;
} else if m.y_move < 0.0 {
*direction = Direction::Up;
sprite.index = 10;
} else {
*direction = Direction::Down;
sprite.index = 1;
}
commands
.entity(entity)
.insert(AnimationTimer(Timer::from_seconds(
0.06 / movement.0 as f32,
TimerMode::Repeating,
)));
} else {
commands.entity(entity).remove::<Moving>();
commands.entity(entity).remove::<AnimationTimer>();
}
}
}
Basically when I receive the message from the server, I add to the Moves
component the next move, so the Moves
component is actually the queue. Then I check if it's moving already, if so, update the translation based on the move speed until reaches the final translation, which should be the next tile. This is the part that doesn't work. The bottom part of the code is just updating the sprite and getting the next move from the queue.
And to show what I mean that it loses some pixels during the animation, look the image below, the skeleton has the right alignment to the tile, see that the player is just a little bit off to the left?
This doesn't happen all the time, but happens very often, like 50% of the times, but I still can't figure why this is happening.