Advertisement

Arcade Physics Movement W/ Latency

Started by July 16, 2016 09:52 AM
7 comments, last by hplus0603 8 years, 4 months ago

Hello everyone, I'm using the arcade physics system from the HTML 5 game framework Phaser.

This is how I handle player movement:

Player 1 holds down W, sends velocity signal to server to move up. Player 1 moves up locally (so it's smooth), while the data is sent. The server basically relays that Player 1 pressed W and notifies everyone else in the game.

If Player 1 releases W, same thing happens, except players are notified the player has stopped. (body.velocity.x=0, body.velocity.x=0 run in client game loop). So basically, I'm relying on packets to change players velocities.. This isn't going to end well, eh? Keep reading please!

This works beautifully and doesn't use any interpolation and makes it seem so fast-paced and smooth (when players move around) -- I run an update interval on the client that sends their x, y positions periodically to keep their positions synced. So when a new player joins the game it'll take a second or so and they will become in sync. Obviously this isn't the best way to go about doing this, as it's SO easy to manipulate packets with browsers and so easy to send fake x, y values. All I can do is basically check the difference of their before and new position so they don't move too far (speed hacking) -- or just run a physics loop on the server. But honestly, I don't really care that much about speed hacking right now. I will in the future though, but right now I have a yuuuuge problem.

The problem is latency. I just tested it outside of my localhost server and wow, it all fell apart :(

From the example above:

When Player 1 releases W to notify all other players he stopped moving.. there is a, lets say 20-50, 100 ms lag. Or let's just say that there was a server hiccup that added 20ms.

This 20ms lag is now effected locally, by the clients physics. Which means...... Player 1 is actually not in the same position they are on their screen, as the view from other players. Because that extra ms of lag was used in the players local client (arcade physics) and added 20ms worth of velocity time. Which would be 10, 20, 30 extra pixels.

Now, I came up with a solution to this. When the player lets GO of W, I would send x, y values aswell (the position they stopped at), and then I interpolated those x, y values on every players screen so if there was any latency lag the player would "snap" to their real position. Basically a band-aid fix. This method is not smooth at all because the player would be "snapping back/to" their real x,y positions.

Positives:

  • The real-time movement is insanely reactive and smooth

Negatives:

  • Latency lag makes game unplayable unless over LAN
  • When doing interpolation. Even at a 20hz server rate, the movement is still nowhere close to "real-time" as my above method. The interpolation makes it seem so "casual" and "non-responsive". Which I hate, and don't want.
  • If a clients FPS drops to around 20, when they send the movement signal off, their player actually doesn't move that far. The duration of releasing and pressing W doesn't match where they are locally. You know this is bad when graphics are affecting the gameplay of other players. :P

Solutions:

  • Run a physics loop on the server (60hz) for movement, and then a game loop at 20hz (that will periodically send new positions out 20/s) and then use interpolation.
  • Downsides: Extreme server resources and bandwidth. I'm looking at around 20 packets per second, * 2 (up and down) for ONE player.

..and now I'm here asking what I can do. I've been reading into latency compensation:

https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization

But that looks like it would require a physics loop. And to have a physics loop on a nodejs server handling basic collisions at 60hz, would I be insane to do this?

The problem is latency.


Welcome to networking! :-)


You have three options:

1) Delay response to commands until you hear back from the server. That way, you know that everyone runs all the commands in the same order/time, so you can assume that the world is consistent. This is the "deterministic lockstep" approach, and is most popular for RTS games, but works for other systems too. When you get it working, it solves pretty much all problems -- but you have to accept the command latency!

2) Trust the client. If my client says that I hit you with a shot, then send that information to the other clients. If the hit player saw something else, spawn a new shot right where they're at so they know what hit them. This can get somewhat confusing, and is super open to cheating, but can also work well.

3) Run physics on the server, let the server determine what "actually" happens, and keep correcting/compensating the clients. This is what most FPS games do, because it hides the latency, and removes confusion/cheating, but instead sometimes causes "I got shot behind a wall" problems, and requires physics simulation on a server. (Note: Server could be user hosted)
enum Bool { True, False, FileNotFound };
Advertisement

The problem is latency.


Welcome to networking! :-)


You have three options:

1) Delay response to commands until you hear back from the server. That way, you know that everyone runs all the commands in the same order/time, so you can assume that the world is consistent. This is the "deterministic lockstep" approach, and is most popular for RTS games, but works for other systems too. When you get it working, it solves pretty much all problems -- but you have to accept the command latency!

2) Trust the client. If my client says that I hit you with a shot, then send that information to the other clients. If the hit player saw something else, spawn a new shot right where they're at so they know what hit them. This can get somewhat confusing, and is super open to cheating, but can also work well.

3) Run physics on the server, let the server determine what "actually" happens, and keep correcting/compensating the clients. This is what most FPS games do, because it hides the latency, and removes confusion/cheating, but instead sometimes causes "I got shot behind a wall" problems, and requires physics simulation on a server. (Note: Server could be user hosted)

Awesome response! I thought I would lose everyone with my long, drifted, boring post! :)

-- Can I still use client prediction with the first approach so I can continue to have smooth movement? (And not wait for a response to move the player locally only). Or would this end up in tons of rubber banding? I hate those games that only move you once they hear back. The 20, 50, 100ms is totally noticable and makes the character movement very.. what's the word, unreactive?

For #2, I'm honestly thinking of doing that too alongside with the deterministic lockstep mode. As I really don't care if a player is perfectly next to a mob, I will just check the range on the server and if they are less than a screen's width away... the attack will be processed. If someone is really that bent on hacking my game, I'm actually kind of flattered. haha.

For #3, I was thinking of that, but man. I have a feeling a 60hz server on nodejs would absolutely be destroyed. It would work, but once you start adding 10, 20, 40 players.. it's going to fail I can just feel it. I think if I were to go that route, I would use Elixir, or a C++ alternative. But, I could be wrong here, maybe nodejs is that powerful. I'd much rather just use it to relay commands and store x,y values and check for waypoints, picking up loot, etc (basic getRange(player, item)) checks on the server for small validations. Anything more than that, I have a feeling the server will poop out.

For your approach #1, is this a type of packet struct (since I'm using nodejs, it will be a object, but you know what I mean):


typedef struct usercmd_s
{
    // Interpolation time on client
    short        lerp_msec;
    // Duration in ms of command
    byte        msec;
    // Command view angles.
    vec3_t    viewangles;
    // intended velocities
    // Forward velocity.
    float        forwardmove;
    // Sideways velocity.
    float        sidemove;
    // Upward velocity.
    float        upmove;
    // Attack buttons
    unsigned short buttons;
    //
    // Additional fields omitted...
    //
} usercmd_t;

My only problem is, how exactly do they get the "duration" of a command when it's only pressed? Wouldn't the player need to release the key to get the duration? This coincides to your main point in #1, where you only move once you receive a response from the server? I'm getting lost I think, so I'm going to stop writing :) Sorry for all the "newb" questions.

Can I still use client prediction with the first approach so I can continue to have smooth movement?


Well, kind-of. You are no longer deterministic. The player will move based on delayed information about the world.
If two players race for a ground pick-up, they will both think that they are way ahead of the other (by one RTT) even if they are neck-to-neck.
You will need to detect and correct any cases where the forward-simulated player is out of sync with the world, which is generally any time that player interacts with another player.
The deterministic model isn't that great if you don't go all in, IMO. If you can't take the latency, and if the players interact a lot, then model 1 kind-of doesn't fit.
That being said, model 1 is actually the model I have the most experience with, including forward-projecting players, and including doing crazy stuff like letting the player upper body / aim / head move locally at ahead time, but the lower body / feet / center of mass move based on another player, which is useful for sitting as a passenger in a vehicle.
If I were to build a new networked game from scratch, and I couldn't take the latency of "pure" model 1, I'd use model 2 if I didn't need to worry about cheating (which most hobby games don't, actually!) and #3 if it were a competitive FPS type game or something like it.

However, your game may be different. Only you can make the choice for how you want your game to play, and which of the options work best for you. The fact that you actually know you can't make it perfect is more than half the battle won already :-)
enum Bool { True, False, FileNotFound };

Yeah, I'll definitely be doing #1 or #2.

I just did the deterministic approach. Waiting for response from server before moving. It seems like ran into an issue:

If Player 1 has massive FPS drops, and when they press W, the server unfortunately doesn't know about their FPS and sends the velocity signal to other players to move them up. Unfortunately, while doing this, Player 1 who has 10 FPS only moves up about 20 pixels, but Player 2 who sees Player 1 move up (moves up about 100+ pixels)... thus, these players become out of sync.

How can I go about fixing that? Send the FPS along with the command packet and use some type of algorithm?

Only way this could be fixed that I see, is following #3.. lol Hmm.

You need to fix your time step, and count all times in step numbers.
When the player takes too long to render, you must step simulation more than once.
If you can't step simulation at real time speed, the player's computer is too slow, and needs to be dropped from the game.
enum Bool { True, False, FileNotFound };
Advertisement

You need to fix your time step, and count all times in step numbers.
When the player takes too long to render, you must step simulation more than once.
If you can't step simulation at real time speed, the player's computer is too slow, and needs to be dropped from the game.

Awww, thanks, I am viewing this now: http://gafferongames.com/game-physics/fix-your-timestep/

And for my case, I am using the Phaser Framework (which has a game.time.physicsElapsed) which is: 0.016667. Is this the "fixed timestep"? I have a feeling its not because I need to count in step numbers. Should I just set a variable to 0 and increment it in the game update? Would that then be my "fixed timestep"? but, then, how would I keep this tracked on the server? Have a loop on the server running at 60hz storing / keeping track of all the timesteps for every game? (Basically just incrementing numbers).

Then use that timestep value when players are sending commands to move them to the correct position? I'm getting lost again, apologize for the silly questions

Aww...... This is a stupid client side issue then! The fixed timestep basically makes it so physics are applied at the same rate regardless of the duration of input!?

I just tried my method within in the Godot Engine and I spammed a bunch of monsters, got around 5FPS. Went to 1 side of the map, held down S go go down. Counted for 1 second. Did this with 5FPS and 60FPS. My character ended up at the same spot! This means that engine has a working fixed timestep along with an accumulator (to increase step)?

For the HTML 5 Phaser Framework, my character ends up only 5 pixels down. (Same type of test) (5FPS and 60FPS). Which I assume the Phaser Framework doesn't have an accumulator that increases the physics step? It does have a fixed timestep, but there is no code to increase the step if you have low FPS. If that makes sense?

Basically, the accumulator helps with the low FPS issues and increases the step to synchronize it to a 60 FPS physics the best it can? I think I'm comprehending this correctly now? :)

The easiest way to calculate the current time step you should be at, is to store the high-precision real time that the level started at.
Then, subtract start time from current high-precision time, divide by time step length, this gives you the time step number you should be at.
Now, if your step counter is less than the target, step physics repeatedly until you catch up. You may want to put an upper limit so you never step more than, say, 30 steps before rendering again.
You can do this on the server, as well as on the client. However, because client clocks aren't generally in sync with the server, it's typically better to just send the target step number to clients from the server as part of the update packets.
There's another bit of optimization you'll typically do where the client will send its actual step number (or clock) to the server in each packet, and the server will send its step number (or clock) to the client in each packet, as well as the timestep/clock that the client provided in the last-received packet.
This allows the client to calculate approximately how much latency it should compensate for, or, put another way, now much offset to add to its local clock.

To read a high-resolution clock:
On Linux, use clock_gettime().
On Windows, use QueryPerformanceCounter().
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement