Somewhat jerky movement when doing client side prediction

Started by
20 comments, last by All8Up 12 years, 7 months ago

[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling



What I believe most action games do (from working with them and reading about techniques over many years) is something very similar to "displayState = oldState + (newState - oldState) * (timeSinceTick / tickSize)"

This means that the physical simulation will be displayed up to one simulation frame behind real time. If you additionally have a lot of graphics command stream buffering, you'll build up a lot of latency. Thus, I recommend using only double-buffering for graphics frame buffers, and doing lock/copy tricks in the command stream to prevent the graphics card from buffering too much command data.

Assuming you have a good timer in your game loop (QueryPerformanceCounter() on Windows, for example), your loop looks something like:


lastTime = now() - simTickSize;
forever() {
curTime = now();
if (curTime >= lastTime + simTickSize) {
int n = 0;
while (curTime >= lastTime + simTickSize && n < 5) {
oldState = curState;
simulate_one_step();
lastTime += simTickSize;
n += 1;
}
lastTime = curTime - fmod((curTime - lastTime), simTickSize); // snap to current step
}
render_state(oldState, curState, (curTime - lastTime) / simTickSize); // lerp between 0 and 1
}



This loop will drop time if you fall more than 5 simulation steps behind, which will probably cause server-side corrections at that point -- choose your max N carefully. If the client machine is simply too slow to keep up the simulation, there's not much you can do, and you don't want to lock the CPU for too long just running simulation without display.

The input latency here will be at least two render frames, because of double-buffering plus the draw-behind. However, that's only 30 milliseconds, which actually will feel very snappy. Most modern online games actually have more in the 5-7 frames range of latency from input to display on screen. (Where "frames" means 60 Hz sim/display steps)

Another option is to use vsync, set the display frame rate to 60 Hz, and set the simulation rate to exactly 1/60 second. Consoles can do this a lot, and it would allow you to drop one frame of perceived latency. With Unity, that may be harder, though.
[/quote]

All good points though I actually move the render_state function to the very front since my renderer is in a separate thread and I just enque render nodes. From that point, I use the simple velocity version mentioned which for all intents and purposes should be identical in behavior except it separates the lerp target into the simulation/networking code. The only purpose to the change is to allow updating the target of the lerp on the fly (say when a network message is received that says, hey dumby you should be over here, not there) in a smooth manner without causing discontinuities between lerps. It really is a minor detail though, both work.
Advertisement

[quote name='fholm' timestamp='1314034082' post='4852407']
Ok so I changed the implementation to be like this:

Vector3 tpos; // the position we want to get to
Vector3 tvelocity; // the direction from the current position to the one we're going to

// Init code, just run once when code gets activated first
void Start () {
tpos = transform.position;
tvelocity = Vector3.zero;
}

// Physics step
void FixedUpdate() {
tpos += KeyStateToVector(GetKeyState());
tvelocity = tpos - transform.position;
}

// Every frame
void Update () {
transform.position = transform.position + (tvelocity * Time.deltaTime);
}




But it's still sort of a "springy" feeling, and when i go forward and then press say left i get a round nice change in direction, and while it looks good and all I need it to twitch reaction that changes direction instantly. Not trying to be ungreatfull but I just can't get to work the way I want.

Did you look at the update-based solution that I wrote a couple of posts up? Is that a solution that could work for a real game?


It's a bit strange since for all intents and purposes this should effectively be identical to the lerp case until you get into latency hiding and such. It's been a while since I wrote any code related to this but basically:

p' = o + (n-o) * dt
is the same as:
f( p' = p + v * dt )
over the same delta times, since v is just set to (n-o) outside of the loop.

The only reason I prefer the velocity version is because everyone is used to it and you can fiddle with v to hide network problems at a later point.

As to the other solution you mention, sure you can make it work. I'd have to think about it and probably put together a test for it to see how well it works when integrated into correction, latency etc code.
[/quote]

Ok, let me see if I can explain my reasoning as to why the springyness is happening and see if you can see where I went wrong (this is how it's working in my code, currently):

  1. I enter the fixed update (simulation/physics step) and notice a FORWARD command (W is down), which creates my movement vector: mv
  2. I take the currently wished position (wp) and add the FORWARD commands movement vector, as: wp' = wp + mv
  3. I calculate the velocity vector (vv) from the screen position (sp) to the wp like this: vv = wp - sp
  4. I'm now inside my update function (as the simulation/physics step is done), I update my screen position by doing this: sp' = sp + (vv * dt)
  5. Assuming low enough frame rate (physics step is 66Hz, so assume the game is running at 50fps or something) I'm now back in my fixed update function, and this is where i gets wonky
  6. I again register a FORWARD command, but I have not yet arrived at my previously wished position (wp) since since I took vv (which points from my on screen position to my wished position) and multiplied it by the time delta dt, assuming the velocity vector (vv) between my screen position (sp) and my previously wished position was (0, 0, 1) and the time delta dt was 0.02 i only moved (0, 0, 1) * 0.02 distance the last frame. And now, i'm holding down forward again, extending my wished position (wp) by another 1 unit forward, the velocity vector between my screen position and wished position is going to be extended by 1 unit further down the z axis, making it (0, 0, 1.98) (since I moved 0.02 units closer to the wished position last frame 0.98 is left of that distance), so now when I reach the next update call, again with a time delta of 0.02 I'm going to move (0, 0, 1.98) * 0.02 (0.0369 units) instead of the previous (0, 0, 1) * 0.2 (0.02 units) and this will continue until the distance I move each frame is equal to what vector is added to my wished position in the physics step which gives the "springyness" and "slow turns" effect, since the on screen position always lags behind by several ms compared to the real one.

This is how I'm understanding the current implementation I have to be working, maybe I implemented it wrong or maybe I miss-understood how it's supposed to work but this is what I got out of it at least! Hope you find my explenation clear enough!

[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling



What I believe most action games do (from working with them and reading about techniques over many years) is something very similar to "displayState = oldState + (newState - oldState) * (timeSinceTick / tickSize)"

This means that the physical simulation will be displayed up to one simulation frame behind real time. If you additionally have a lot of graphics command stream buffering, you'll build up a lot of latency. Thus, I recommend using only double-buffering for graphics frame buffers, and doing lock/copy tricks in the command stream to prevent the graphics card from buffering too much command data.

Assuming you have a good timer in your game loop (QueryPerformanceCounter() on Windows, for example), your loop looks something like:


lastTime = now() - simTickSize;
forever() {
curTime = now();
if (curTime >= lastTime + simTickSize) {
int n = 0;
while (curTime >= lastTime + simTickSize && n < 5) {
oldState = curState;
simulate_one_step();
lastTime += simTickSize;
n += 1;
}
lastTime = curTime - fmod((curTime - lastTime), simTickSize); // snap to current step
}
render_state(oldState, curState, (curTime - lastTime) / simTickSize); // lerp between 0 and 1
}



This loop will drop time if you fall more than 5 simulation steps behind, which will probably cause server-side corrections at that point -- choose your max N carefully. If the client machine is simply too slow to keep up the simulation, there's not much you can do, and you don't want to lock the CPU for too long just running simulation without display.

The input latency here will be at least two render frames, because of double-buffering plus the draw-behind. However, that's only 30 milliseconds, which actually will feel very snappy. Most modern online games actually have more in the 5-7 frames range of latency from input to display on screen. (Where "frames" means 60 Hz sim/display steps)

Another option is to use vsync, set the display frame rate to 60 Hz, and set the simulation rate to exactly 1/60 second. Consoles can do this a lot, and it would allow you to drop one frame of perceived latency. With Unity, that may be harder, though.
[/quote]

Thank you for your response! Very good information in here, if I understand it correctly what you have above the last call to "render_state" is your simulation/physics step(s) getting executed and once they are done you have your render_state function which draws on model on screen in the correct position (interpolated between old and cur state depending on how much we "overlap" (timewise) into the the state?).

Very informative post, i've been reading the QW and Q3 sources to try to figure out how they do their input prediction and this seems to be the way they do it, sort of? Did you see my post about using the normal frame update function and over time using frame delta accumulating state (grouping up ever 15ms of time into it's own command) and rendering positions on screen accordingly as the update function ticks? It did give me silky smooth movement but I don't know if this is the "proper way".

[quote name='AllEightUp' timestamp='1314040288' post='4852456']
It's a bit strange since for all intents and purposes this should effectively be identical to the lerp case until you get into latency hiding and such. It's been a while since I wrote any code related to this but basically:

p' = o + (n-o) * dt
is the same as:
f( p' = p + v * dt )
over the same delta times, since v is just set to (n-o) outside of the loop.

The only reason I prefer the velocity version is because everyone is used to it and you can fiddle with v to hide network problems at a later point.


Ok, let me see if I can explain my reasoning as to why the springyness is happening and see if you can see where I went wrong (this is how it's working in my code, currently):

  1. I enter the fixed update (simulation/physics step) and notice a FORWARD command (W is down), which creates my movement vector: mv
  2. I take the currently wished position (wp) and add the FORWARD commands movement vector, as: wp' = wp + mv
  3. I calculate the velocity vector (vv) from the screen position (sp) to the wp like this: vv = wp - sp
  4. I'm now inside my update function (as the simulation/physics step is done), I update my screen position by doing this: sp' = sp + (vv * dt)
  5. Assuming low enough frame rate (physics step is 66Hz, so assume the game is running at 50fps or something) I'm now back in my fixed update function, and this is where i gets wonky
  6. I again register a FORWARD command, but I have not yet arrived at my previously wished position (wp) since since I took vv (which points from my on screen position to my wished position) and multiplied it by the time delta dt, assuming the velocity vector (vv) between my screen position (sp) and my previously wished position was (0, 0, 1) and the time delta dt was 0.02 i only moved (0, 0, 1) * 0.02 distance the last frame. And now, i'm holding down forward again, extending my wished position (wp) by another 1 unit forward, the velocity vector between my screen position and wished position is going to be extended by 1 unit further down the z axis, making it (0, 0, 1.98) (since I moved 0.02 units closer to the wished position last frame 0.98 is left of that distance), so now when I reach the next update call, again with a time delta of 0.02 I'm going to move (0, 0, 1.98) * 0.02 (0.0369 units) instead of the previous (0, 0, 1) * 0.2 (0.02 units) and this will continue until the distance I move each frame is equal to what vector is added to my wished position in the physics step which gives the "springyness" and "slow turns" effect, since the on screen position always lags behind by several ms compared to the real one.

This is how I'm understanding the current implementation I have to be working, maybe I implemented it wrong or maybe I miss-understood how it's supposed to work but this is what I got out of it at least! Hope you find my explenation clear enough!
[/quote]

I believe I see the problem, assuming I am grokking things correctly. So basically you are updating by input in between your physics/sim loop which is a bit of a problem. The down side to doing that is that the client and server will quickly get out of sync since the client will be doing things the server didn't expect. Again, I "think" that is what you are doing here and the system I've been describing expects everything to stay fixed between physics/sim loops. You can do mouse look sorts of things but updating the velocity in between physics is bad news for the most part. (I.e. you can change the camera orientation all you want as long as you don't change the actual velocity vector or position information until the next update.)

This basically means that input is only acted upon within the physics/simulation loop if it changes state. I don't know the details of Q3+ but Q2 and prior basically split things such that you could look around anywhere you want but only the physics loop ever changed the velocity/speed/collision etc. I.e. you could unplug the network cable and you would keep moving your last direction for a bit till you froze in place for lack of updates, but you could look anywhere you wanted. The fact that you are on a fine grained linear path is completely hidden from view with camera bobbing, footsteps etc.
I have a project using Unity that does the physics / networking like so:

1. Client sends input (movement keys) to server.
2. Server does physics.
3. NetworkRigidbody script (found in the Unity networking tutorial) handles sending data (position, rotation, velocity, angular velocity) back to the client.
4. NetworkRigidbody script also does interpolation or extrapolation using the position / velocity, to a point at an arbitrary time in the past (100ms seems to work - so graphics on clients are always effectively 100ms behind the server). You can always change the point in time (within reason) to get more extrapolation or more interpolation.

I think this is the "recommended" way of doing things, in that it's the way the tutorial does things (which may not mean it's the best way, as Unity's networking is... a bit dodgy to say the least). Still it works fine for me on a local network (I have yet to test over the internet, however).

I have a project using Unity that does the physics / networking like so:

1. Client sends input (movement keys) to server.
2. Server does physics.
3. NetworkRigidbody script (found in the Unity networking tutorial) handles sending data (position, rotation, velocity, angular velocity) back to the client.
4. NetworkRigidbody script also does interpolation or extrapolation using the position / velocity, to a point at an arbitrary time in the past (100ms seems to work - so graphics on clients are always effectively 100ms behind the server). You can always change the point in time (within reason) to get more extrapolation or more interpolation.

I think this is the "recommended" way of doing things, in that it's the way the tutorial does things (which may not mean it's the best way, as Unity's networking is... a bit dodgy to say the least). Still it works fine for me on a local network (I have yet to test over the internet, however).

Hey!

First, thanks for your response! I'm aware of the way that Unity does networking if you choose to use it's built in mechanisms. However they are quite dodgy/buggy and doesn't perform very well and the solution I'm building is using the excellent Lidgren library. Also as far as I know the example unity networking doesn't do input prediction or lag compensation, but only smoothing over state frames. I already have a pretty solid solution that does input prediction, lag compensation and entity smoothing - it's just the last part of easing out the owning clients side when doing the input perdiction that is eluding me currently. I'm also planning to release the done code on the unity asset store as a full lidgren example (and example of implementation of the mentioned techniques, as this is pretty impossible to find, as I'm learning right now).


Ok, lets see if I got you right (or rather, who's misunderstanding who - guessing the problem is on my end ;p), again. The way I implemented is it that position (as in the position the physics engine wants me to be at) is only update in the FixedUpdate function, but I'm slowly moved towards it each frame in the Update function, I'll try to use clearer variable names but here's the code again in a more general implementation and without any unity specific stuff:



Vector3 onScreenPosition; // the position the model current is drawn at on screen
Vector3 realPosition; // the real position we're at, according to the physics/simulation step
Vector3 velocityFromOnScreenToReal; // the direction from the current position to the one we're going to

// Physics step
void FixedUpdate() {
realPosition += KeyStateToVector(GetKeyState());
velocityFromOnScreenToReal = realPosition - transform.position;
}

// Every frame
void Update () {
onScreenPosition = onScreenPosition + (velocityFromOnScreenToReal * Time.deltaTime);
}



Physics are currently running at 66.66Hz and the frame rate is about 65-66, shouldn't matter but just for completeness sake.

[quote name='AllEightUp']This basically means that input is only acted upon within the physics/simulation loop if it changes state.


I'm somehow thinking that the key point of your whole explanation/implementation is this? that I should only re-calculate the velocity vector if the state of the physics changes? and does this means if the target position changes (as in I keep moving forward) or should it change only if I say change direction?




[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling



What I believe most action games do (from working with them and reading about techniques over many years) is something very similar to "displayState = oldState + (newState - oldState) * (timeSinceTick / tickSize)"

This means that the physical simulation will be displayed up to one simulation frame behind real time. If you additionally have a lot of graphics command stream buffering, you'll build up a lot of latency. Thus, I recommend using only double-buffering for graphics frame buffers, and doing lock/copy tricks in the command stream to prevent the graphics card from buffering too much command data.

Assuming you have a good timer in your game loop (QueryPerformanceCounter() on Windows, for example), your loop looks something like:


lastTime = now() - simTickSize;
forever() {
curTime = now();
if (curTime >= lastTime + simTickSize) {
int n = 0;
while (curTime >= lastTime + simTickSize && n < 5) {
oldState = curState;
simulate_one_step();
lastTime += simTickSize;
n += 1;
}
lastTime = curTime - fmod((curTime - lastTime), simTickSize); // snap to current step
}
render_state(oldState, curState, (curTime - lastTime) / simTickSize); // lerp between 0 and 1
}



This loop will drop time if you fall more than 5 simulation steps behind, which will probably cause server-side corrections at that point -- choose your max N carefully. If the client machine is simply too slow to keep up the simulation, there's not much you can do, and you don't want to lock the CPU for too long just running simulation without display.

The input latency here will be at least two render frames, because of double-buffering plus the draw-behind. However, that's only 30 milliseconds, which actually will feel very snappy. Most modern online games actually have more in the 5-7 frames range of latency from input to display on screen. (Where "frames" means 60 Hz sim/display steps)

Another option is to use vsync, set the display frame rate to 60 Hz, and set the simulation rate to exactly 1/60 second. Consoles can do this a lot, and it would allow you to drop one frame of perceived latency. With Unity, that may be harder, though.
[/quote]

Respond again to this post, I tried implementing this and think I got it right (it's silky smooth and feels very snappy, here's the way the code looks for me, using Unity):




Vector3 oldState;
Vector3 currentState;
float stateTime;

// Physics step
void FixedUpdate() {
oldState = currentState;
currentState = oldState + KeyStateToVector(GetKeyState());
stateTime = Time.time;
}

// Every frame
void Update () {
var t = (Time.time - stateTime) / 0.015f;
onScreenPosition = oldState + ((currentState-oldState) * t);
}



I can configure unity to run the physics step at a set interval, and unity will make sure that interval is kept and FixedUpdate gets run an appropriate amount of times, etc. Anyway, this seems to be working very very nicely and gives me that snappy feeling i want and smooth movement rendering on screen, even though it's lagging 2 frames behind it's not noticable.

[quote name='AllEightUp']
I believe I see the problem, assuming I am grokking things correctly. So basically you are updating by input in between your physics/sim loop which is a bit of a problem. The down side to doing that is that the client and server will quickly get out of sync since the client will be doing things the server didn't expect. Again, I "think" that is what you are doing here and the system I've been describing expects everything to stay fixed between physics/sim loops. You can do mouse look sorts of things but updating the velocity in between physics is bad news for the most part. (I.e. you can change the camera orientation all you want as long as you don't change the actual velocity vector or position information until the next update.)


Ok, lets see if I got you right (or rather, who's misunderstanding who - guessing the problem is on my end ;p), again. The way I implemented is it that position (as in the position the physics engine wants me to be at) is only update in the FixedUpdate function, but I'm slowly moved towards it each frame in the Update function, I'll try to use clearer variable names but here's the code again in a more general implementation and without any unity specific stuff:



Vector3 onScreenPosition; // the position the model current is drawn at on screen
Vector3 realPosition; // the real position we're at, according to the physics/simulation step
Vector3 velocityFromOnScreenToReal; // the direction from the current position to the one we're going to

// Physics step
void FixedUpdate() {
realPosition += KeyStateToVector(GetKeyState());
velocityFromOnScreenToReal = realPosition - transform.position;
}

// Every frame
void Update () {
onScreenPosition = onScreenPosition + (velocityFromOnScreenToReal * Time.deltaTime);
}



Physics are currently running at 66.66Hz and the frame rate is about 65-66, shouldn't matter but just for completeness sake.



I'm somehow thinking that the key point of your whole explanation/implementation is this? that I should only re-calculate the velocity vector if the state of the physics changes? and does this means if the target position changes (as in I keep moving forward) or should it change only if I say change direction?


Yip, and as you found in the other version, it "should" work perfectly. I'll actually reply to the other item in a sec with the details.

[quote name='hplus0603' timestamp='1314037514' post='4852433']
[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling



What I believe most action games do (from working with them and reading about techniques over many years) is something very similar to "displayState = oldState + (newState - oldState) * (timeSinceTick / tickSize)"

This means that the physical simulation will be displayed up to one simulation frame behind real time. If you additionally have a lot of graphics command stream buffering, you'll build up a lot of latency. Thus, I recommend using only double-buffering for graphics frame buffers, and doing lock/copy tricks in the command stream to prevent the graphics card from buffering too much command data.

Assuming you have a good timer in your game loop (QueryPerformanceCounter() on Windows, for example), your loop looks something like:


lastTime = now() - simTickSize;
forever() {
curTime = now();
if (curTime >= lastTime + simTickSize) {
int n = 0;
while (curTime >= lastTime + simTickSize && n < 5) {
oldState = curState;
simulate_one_step();
lastTime += simTickSize;
n += 1;
}
lastTime = curTime - fmod((curTime - lastTime), simTickSize); // snap to current step
}
render_state(oldState, curState, (curTime - lastTime) / simTickSize); // lerp between 0 and 1
}



This loop will drop time if you fall more than 5 simulation steps behind, which will probably cause server-side corrections at that point -- choose your max N carefully. If the client machine is simply too slow to keep up the simulation, there's not much you can do, and you don't want to lock the CPU for too long just running simulation without display.

The input latency here will be at least two render frames, because of double-buffering plus the draw-behind. However, that's only 30 milliseconds, which actually will feel very snappy. Most modern online games actually have more in the 5-7 frames range of latency from input to display on screen. (Where "frames" means 60 Hz sim/display steps)

Another option is to use vsync, set the display frame rate to 60 Hz, and set the simulation rate to exactly 1/60 second. Consoles can do this a lot, and it would allow you to drop one frame of perceived latency. With Unity, that may be harder, though.
[/quote]

Respond again to this post, I tried implementing this and think I got it right (it's silky smooth and feels very snappy, here's the way the code looks for me, using Unity):




Vector3 oldState;
Vector3 currentState;
float stateTime;

// Physics step
void FixedUpdate() {
oldState = currentState;
currentState = oldState + KeyStateToVector(GetKeyState());
stateTime = Time.time;
}

// Every frame
void Update () {
var t = (Time.time - stateTime) / 0.015f;
onScreenPosition = oldState + ((currentState-oldState) * t);
}



I can configure unity to run the physics step at a set interval, and unity will make sure that interval is kept and FixedUpdate gets run an appropriate amount of times, etc. Anyway, this seems to be working very very nicely and gives me that snappy feeling i want and smooth movement rendering on screen, even though it's lagging 2 frames behind it's not noticable.
[/quote]

If this works then you should be able to do the other version as it is mathematically the same. You move "currentState-oldState" into the FixedUpdate function and assign the result to a variable which we will call "currentVelocity" to match your naming. Replace the "currentState-oldState" with that velocity vector and modify "t" to be the delta between frames instead of an absolute percentage between states. It is not a "better" solution at all, it is simply a variation which allows a little more flexibility later along with maintaining standard non-networked styled math as you write code. But, if you have this working, you might stick to it, this level of detail is likely not that useful to you till later.

This topic is closed to new replies.

Advertisement