Members - Reputation: 194
Posted 11 February 2012 - 11:18 PM
I'm making a top-down 2D space shooter (a la Asteroids, or more accurately, Subspace/Continuum) and am trying to implement the Valve server model for state synchronization. I've got state interpolation in, and just implemented server-side rollbacks, but am having a little trouble with the corrections caused by rolling back (see here: video).
As you can see, ships will start turning, overshoot where I let go of the key, and then snap back. I can smooth the snapping, but even with a degree of interpolation, that's undesirable behavior. Note that I haven't implemented client-side prediction yet, but that won't solve this since it appears on other players as well.
A little about my server architecture: The client captures input every 33ms, and the server sends out a snapshot every 33ms. Clients are buffered by 100ms for interpolation. Whenever the server receives input from a client, the server rolls back to the point at which the input was sent, incorporating latency and the flat 100ms buffer delay (with a maximum rollback of 500ms), applies that input, and re-simulates the world accounting for all other input it received after the rollback point. Each state is immutable, and generated from the previous immutable state plus any commands/events either received from clients or generated from the last update. The actual state simulation architecture is described in greater detail, with diagrams, here: http://www.gamedev.n...arallelization/
I know the Valve model rolls back just to apply rays from instant-hit weapons. Most of my projectiles in my game won't be instant, but rather missiles/rockets. Should I only roll back for certain commands? If so, how do I keep the controls feeling responsive while still being accurate? If I didn't rollback, the ships in that video would just plain overshoot the point at which the player let go of the keyboard (and that's with very little latency).
Moderators - Reputation: 4467
Posted 13 February 2012 - 06:24 AM
Members - Reputation: 194
Posted 13 February 2012 - 01:54 PM
Here's an extended discussion of my problem:
I have two options, rollback on the server for movement input, or no rollback. Rolling back for movement input creates the jitter you saw in the video I linked above. Let's say I perform rollback for client user input for movement. Here's what happens:
(Here's the full album of the illustrations if you want to play them like slides: http://imgur.com/a/wSiVG#0)
(1) At time t0, Client 1 is moving forward normally. The server knows that the last user input hasn't changed, and since the user is buffered by 100ms, the server predicts the future position. The server sends that predicted position to all other clients (Client 2), which are also buffered 100ms and interpolate.
(2) Immediately after time t0 (1ms, well before time t1), the user input changes, and so Client 1 sends that message to the server. The state sent from the server to Client 2 is traveling through the Intertubes.
(3) Immediately before time t1 (1ms, let's say), the state sent from the server to Client 2 has arrived, and so Client 2 starts interpolating Client 1's towards the wrong position. The Server has also predicted Client 1's ship well ahead of where it actually should be, having not yet received Client 1's input.
(4) Now, actually at time t1, the Server receives Client 1's input and rolls back to correct the simulation, placing Client 1's ship where Client 1 expects it to be. The Server dispatches a new state to both Client 1 (which we ignore, since our prediction was correct), and Client 2.
(5) The state we sent to Client 2 arrives, and Client 2 is forced to interpolate Client 1's ship going backwards. This creates the snapping effect you see in my video.
But what if we didn't rollback? Well, let's see. Here's the no-rollback version:
(1) Time t0 starts as normal, except this time we're paying attention to the states the Server sends to Client 1. Currently, the Server sends the predicted state of Client 1's ship to Client 1, as well as Client 2.
(2) Immediately after the states sent to Clients 1 and 2 are sent flowing through the Intertubes, Client 1 lets go of the forward key and stops moving (let's pretend there's no inertia). That input is sent to the Server, and we predict on Client 1 that the ship will indeed stop moving. (Or we don't, and keep moving the ship forward, but that causes a feeling of unresponsive controls.)
(3) At time t1, the states sent at time t0 arrive at Clients 1 and 2. This is no problem for Client 2, but for Client 1, the ship we just thought had stopped is now interpolating forward. The input Client 1 sent to the Server is applied, but the Server has already predicted Client 1 as moving forward for a step before it could apply that input. The Server has now wrongly predicted Client 1's position for two timesteps before finally applying Client 1's input at time t0. The Server sends out states to Clients 1 and 2 with the position for Client 1, which is two timesteps out of date.
(4) Client 1 receives the terribly out-of-date state from the server, and we see the ship snapping/interpolating far beyond where we had originally intended to stop it (this means any projectiles we want to fire will have to be lead). There is no snapping or jittering for Client 2, though.
So, which one do I choose, or is there a third option? A blend of the two?
TL;DR -- The only real server-side prediction I do is assuming a client will retain a given input configuration until a client says otherwise. When a client does say otherwise, I roll back and re-simulate with the correct input configuration applied at the correct time. Is there a happy medium, or a best practice? Do shooters really only rollback to apply hit correction? If I didn't rollback, there would either be lag waiting for your movement commands to be applied (no prediction), or snapping back from the commands that were applied (prediction). Is that just something someone will have to deal with?
Any guidance on this front would be incredibly helpful. Thank you!
Moderators - Reputation: 4467
Posted 14 February 2012 - 09:41 AM
And that's a lot of prediction. When Client A changes direction and sends this message to the Server, the server performs a correction, then sends this out to Client B - and for all that time, Client B was simulating Client A's old position, based on old data from the server. That's a whole round-trip of latency to smooth out and no way you slice and dice it can get around that. Information takes time to travel, so you either have the correct information later or an estimate now.
The only real server-side prediction I do is assuming a client will retain a given input configuration until a client says otherwise. When a client does say otherwise, I roll back and re-simulate with the correct input configuration applied at the correct time.
You don't strictly need to do any prediction at all. Broadcast the new client positions as the server receives them and have every client see other clients slightly in the past. If your update rate is reliable enough this will work well - although whether it is fair is another matter since players with different latency will see slightly different representations of the world based on that. Although, any model that allows you to start moving locally rather than waiting for server confirmation will show different representations to all players.
But that brings you to another issue - do you really need to start moving instantly? You talk about control responsiveness, but the more instant they act locally, the more discrepancy you get between the client and the server. You also say "let's pretend there's no inertia", but that actually makes your problem worse - imagine it takes 500ms to slow to a stop from your current speed. The server can receive the 'stop' message, notes that your current latency is 40ms, and can instruct the other clients that your ship will actually stop in 460ms. (Although you probably want to do another allowance for each client's latency.) This is still just interpolation and snapping, but by building the buffer into the game design you can minimise or eliminate any overshoot. Similarly, if it takes a frame or two for an input command to be registered or for the rocket thrusters to start up, that buys you 30-50 milliseconds of time where a server can catch up to the client's intent, minimising the amount of interpolation you have to do.
Some don't rollback at all. There are arguments between various factions as to which approach is fairer. The Valve model is fairer for the shooter than the shootee, because it ensures that the shot is resolved relative to what the shooter was observing. Other models look at the authoritative server data copy at that time and may resolve the shot differently. Client A might have been able to see Client B in the open when he pressed the trigger, but the server may know that Client B was already behind cover by then and won't punish Client B for Client A's poor latency. It's just a decision each developer has to make.
Do shooters really only rollback to apply hit correction?
But yes, generally they won't roll back movement. People move fast and often enough in shooters that you can just keep interpolating towards the true position without ever really needing to snap someone back. The limited field of view also makes it less important to get high-fidelity positioning, also. Game design leaks into the networking considerations.