Server difference regarding input

Started by
9 comments, last by wodinoneeye 7 years, 10 months ago

Hi,

I'm was wondering whether this is a common/known problem when developing a realtime (fast paced) multiplayer game:

I have a ball on the client side and server side. If I press the forward key, the ball on the client side received an impulse (1 time applied). This input is sent to the server which also applies the input to the ball on the server.

I notice that the ball on the client is ahead a little bit (due to the latency from the input that was sent to the server) and I expected this.

I see the ball rolling on the client (still moving) and let's say that this is 50m from the starting point. I see it on the server at 40m.

Here is my problem: if I know press the down key arrow to move back, I see that my client is at 45 meter after a while but my server never reached the 50 meter point, and is now at 30 meter.

I was wondering how to exactly fix this kind of problem. I think that there is something wrong with my implementation of applying impulses, which is implemented like so:

- Once a key is pressed/released, an input struct with the keys that are pressed/released is being sent only once to the server (if each individual key is pressed/released, the struct is changed and 1 signal is sent to the server).

- Locally, I have a loop that runs 10 times a second for input: it checks based on the input struct which keys are pressed and applies the impulse accordingly.

- On the server I have another input loop (also 10 times a second). Once an object has been received, the loop applies the forces again depending on the input structure.

Advertisement
That's the general problem of all networked games. You have to design your gameplay, your physics, your graphics, and your networking to all come together to do the right thing for your particular game.

The basics is to make sure that you fix your simulation step, and count all inputs and simulation in step numbers, rather than time.
Then, you can batch N steps of input into a single network packet, and send to the server (and the same going the other way.)
At this point, the server knows that "the player was pressing forward on step 17 and backward on step 18" and thus can move forwards/backwards the same number of steps as the client.

Once you've done that, you will start running into problems where two players interact -- both want to pick up the same powerup, or both will bump into the same simulated block, or one shoots the other, or whatever.
How you handle those cases is what determines the "feel" of your gameplay, and there are many different solutions.
enum Bool { True, False, FileNotFound };

That's the general problem of all networked games. You have to design your gameplay, your physics, your graphics, and your networking to all come together to do the right thing for your particular game.

The basics is to make sure that you fix your simulation step, and count all inputs and simulation in step numbers, rather than time.
Then, you can batch N steps of input into a single network packet, and send to the server (and the same going the other way.)
At this point, the server knows that "the player was pressing forward on step 17 and backward on step 18" and thus can move forwards/backwards the same number of steps as the client.

Once you've done that, you will start running into problems where two players interact -- both want to pick up the same powerup, or both will bump into the same simulated block, or one shoots the other, or whatever.
How you handle those cases is what determines the "feel" of your gameplay, and there are many different solutions.

Thanks again hplus0603!

Oh no hplus0603, sorry for my late reply on this again.. I've just had time to get back on the networking learning.

I have a question regarding your comment. Because I need to keep track of the step as well, so here is the current problem:

1. Player presses UP on step 17 (client side). This is a message that is sent to the server. Let's say the server receives it at step 50 (server loop). It starts processing this input.

2. Player presses BACK on step 40 (client side). This is a message that is sent to the server. The server receives it at step 110. (server loop). It starts processing this input.

The server knows that the actual difference between 2 and 1 should be 33 (the amount that was pressed on the client side). However, it started at step 50, and since the other message came at step 110, the server now only knows it should be 33 steps between those key presses on the client. But the server itself is 60 steps late and pressed UP 60 ticks rather than 33.

Should the server correct itself? Because it sounds quite hard to fix this.

Sorry for this confusion.

If the client sends commands that arrive too late or seem otherwise to be timed wrong, you need to apply them when they arrive, rather than when the client said they should be applied.
This is to avoid cheating, where a client could "time travel" by sending inputs after the fact.
The server then needs to tell the client "You were corrected, and here's the new state."
enum Bool { True, False, FileNotFound };

The basics is to make sure that you fix your simulation step, and count all inputs and simulation in step numbers, rather than time.
Then, you can batch N steps of input into a single network packet, and send to the server (and the same going the other way.)
At this point, the server knows that "the player was pressing forward on step 17 and backward on step 18" and thus can move forwards/backwards the same number of steps as the client.

A quick question: Do you need to mark the packets with step numbers even if you are using TCP..? Or can you just use an array at server and pop the packets from there based on the order of arrival?

-

Do you need to mark the packets with step numbers even if you are using TCP..?


In general, yes, you'll want to do that.

If you don't, then you have to delay everybody's game if one client's stream gets delayed.
And TCP stream delays are worse than UDP delays, because even if the next packet makes it, the TCP stack will wait for the re-transmit of the previous packet before giving you any more data.
enum Bool { True, False, FileNotFound };

Do you need to mark the packets with step numbers even if you are using TCP..?


In general, yes, you'll want to do that.

If you don't, then you have to delay everybody's game if one client's stream gets delayed.
And TCP stream delays are worse than UDP delays, because even if the next packet makes it, the TCP stack will wait for the re-transmit of the previous packet before giving you any more data.

So, assuming that client / server runs in sync and both calculates the ticks and marks the packets.

When I press e.g. "move left" at client-side during tick: 0, I mark the packet with tick: 0, and send it to the server. Server receives this packet at tick 10 and stores it to an array or e.g. dict if it's easier to pull the data from.

Now, the next part confuses me. Because the data is already old, does it mean that server-side game loop needs to always read/process the packets from the past based on some maximum tick it allows players to be behind? So during tick 20, it will read and process packets of tick 10, at tick 21, tick 11 and so on? This would also mean that during each game loop cycle I need to first transfer everything (mobs and players) to a time of tick 10 at the server and execute the game logic from tick 10 -> tick 20 again.. then when it is done for all, I need to send corrected updates for the players? I would just assume that I would have to constantly send a lot of corrections for the players... but maybe I am wrong. I did read something about source networking how it does server-side lag compensation for shooting, but I have assumed that I don't need to this for typical commands like moving/picking up items etc.

Or

Is it preferred that the client-side marks the packet tick number to be a larger than current tick.. so if he presses "move left" during tick 0, it will mark the ongoing packet to be processed during tick 10 (?).. then server-side can just run as normally and then during each tick, lets say 10, 11, 12, it will look only those inputs from the players? and those should be there, assuming player can keep up with the game?

Sorry for polluting this thread with my own questions, this part just continues to confuse me. :)

-

Because the data is already old, does it mean that server-side game loop needs to always read/process the packets from the past based on some maximum tick it allows players to be behind?

sYou have three options:

1) Play it as soon as possible on the server. This is simple, but introduces game play artifacts because of jitter. It also will let the simulation on the client diverge from the server, so continual corrections are needed.

2) Rewind the server simulation and apply the command, with some maximum allowed amount of rewind (to avoid too much cheating.) This is the "Source" / "Counter-Strike" model. This reduces lag when players are not interacting, but causes corrections when players interfere with each other.

3) Make the client schedule the event for the future -- the client actually says "play this on step 12" when sending the command. Similarly, the client then also locally delays the command until that time step. This lets you do 100% deterministic lock-step simulation, but introduces a client-side command lag.
enum Bool { True, False, FileNotFound };

Thanks for the explanation hplus0603 :)

-

This topic is closed to new replies.

Advertisement