Sign in to follow this  
FredrikHolmstr

Somewhat jerky movement when doing client side prediction

Recommended Posts

FredrikHolmstr    262
So, been hacking away at my multiplayer client/server over the weekend, based a lot on what AllEightUp and hplus0603 helped me with in [url="http://www.gamedev.net/topic/608702-best-practices-for-sending-and-receiving-game-state/"]this thread[/url] (thanks again, both of you if you read this post also). I've run into a bit of a problem though, first let me describe my setup:

[list][*]Client runs physics simulation in 15ms ticks (66.66Hz)[*]Client sends commands to server every two ticks (33.33Hz), clients send commands to the client in groups of two[*]Server runs its own physics simulation at 15ms ticks also[*]Server sends result back to clients every 45ms (22.22Hz)[/list]
This is my general setup, and it's working pretty well (even got a decent implementation of entity smoothing and lag compensation working), however I'm having problems with the [b]client input prediction[/b] on a client that owns an entity. It goes like this:

[list=1][*]Client physics step checks input and register a FORWARD movement[*]The client predicts where this FORWARD movement will take it, and puts it in the queue of yet-to-be.verified commands and sends it to the server[*]Here's the problem: I now need to move the client character on the owning machine directly, and this is where I run into problems. I'll explain below.[/list]
So, I now have a queued up command from the physics step that says I need to move forward, it has been sent to the server and everything is dandy. But I just can't get the movement on the client itself to be completely smooth. One physics step is 15ms, so I tried to smooth the movement command generated in the physics step over the next X frames. I also tried to do the on-screen movement itself on in the physics step also by just moving the client character forward as much as the movement command generated, as I figured that the physics simulation is running at almost 67 fps it would be enough to not be noticeable.

I want to say that both of the approaches I've mentioned (smooth the movement over the next X frames, up until 15ms has passed and also doing the movement in the physics step) works decently well, and if I wasn't such a perfectionist I probably would not have noticed that it's a tiny tiny tiny bit jerky compared to doing something like "position += movement * delta" which creates the ultimate smooth movement. [b]So I suspect what I'm looking for his some hints, pseudo code (or real implementation if anyone got) of how to get that really really smooth movement on the client that you're doing the input prediction on.[/b]
[b]
[/b]
[i]Edit for clarification: This is not "jerkyness" coming from corrections by the server, etc. that is handled all well and good, this is jerky movement on the character itself when moving on the commands that have yet to be verified and returned by the server. Basically the predicted movement, which is supposed to be completely smooth but I just can't get it so. [/i]
[i]
[/i]
[i]To explain further, I'm looking to get the exact same smooth movement as "position += movement * delta" would get me if I was making a single player game and put that code in the update function that gets executed once every frame. But now I'm checking the input in the fixed update / physics step so it's synced to the servers 15ms tick rate but then when I'm going to move/draw the character in update based on this, the character jerks a tiny tiny tiny tiny bit.[/i]

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
I'm going to try to expand om my previous post to give a clearer example of what I want, again assume this:
[list][*]Clients physics step / fixed update runs at 66.66Hz (15 ms)[*]Clients update/framerate runs as fast as possible[*]I generated input commands in the physics step[*]I need to move the on screen character in the normal frame based update function[/list]
In pseudo code it it would look something like this:

[code]

// 66.66Hz, or 15ms runtime
void physicsStep() {
byte keystate = 0;

if(keyIsDown(w)) keystate |= 1;
if(keyIsDown(s)) keystate |= 2;
if(keyIsDown(a)) keystate |= 4;
if(keyIsDown(d)) keystate |= 8;

queueStateForSendingToServer(keystate);
queueStateForLocalUpdate(keystate);
}


// normal update, runs as fast as possible based on frame rate
void update() {
if(anyStatesInLocalQueue) {
// this is where I have the problem
// I'm not sure of how to apply the
// states generated in physicsStep()
// in the frame based update function
// to achieve a smooth movement
}
}
[/code]

Again, to further elaborate: When I come into the update() function, which will run one or many times in a row depending on my frame rate I have the commands/states locally that have not been applied to the character yet, which is what I need to do, smoothly. I've tried several techniques, for example:

* Take the next state, interpolate over it from 0-15ms, then take the next one, etc. It doesn't seem to achieve smooth results.
* Calculating the positions before hand and smoothing between them, still doesn't give me 100% correct results.

I've also tried things like calculating the expected position from a specific movement in the physicsStep and then smoothing from current position to expected position in update, like this:

[code]
Vector3 lerpFrom;
Vector3 lerpTo;
float lerpTime ;

void physicsStep() {
lerpTime = currentTimeSinceGameStarted;
lerpFrom = currentPosition;
lerpTo = currentPosition + currentCalculatedInputVector;
}

void update() {
currentPosition = Vector3.Lerp(lerpFrom, lerpTo, currentTimeSinceGameStarted - lerpTime / 0.015f);
}
[/code]

But no luck with this approach either. And again, for emphasis: It's not insanely jerky, but it's not as smooth as when doing a single player game and doing the movement in the update function with just [b]"currentPosition += movementVector * frameDelta * 50.0f;*[/b]

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314000735' post='4852174']
[code]
Vector3 lerpFrom;
Vector3 lerpTo;
float lerpTime ;

void physicsStep() {
lerpTime = currentTimeSinceGameStarted;
lerpFrom = currentPosition;
lerpTo = currentPosition + currentCalculatedInputVector;
}

void update() {
currentPosition = Vector3.Lerp(lerpFrom, lerpTo, currentTimeSinceGameStarted - lerpTime / 0.015f);
}
[/code]

But no luck with this approach either. And again, for emphasis: It's not insanely jerky, but it's not as smooth as when doing a single player game and doing the movement in the update function with just [b]"currentPosition += movementVector * frameDelta * 50.0f;*[/b]
[/quote]

My first thought here is that floating point imprecision could be causing problems. Given the standard lerp implementation of "a' = a + (b-a) * t" and the way you are calculating t, there are a couple places you could be hitting problems with precision. I don't think this is the most likely case but you might want to log out the from/to and the time percentage and see what the values are. If you see numbers like 1000.0001f for a position and a number like 0.0001f for the percentage then you are likely hitting problems in this area. The fix, if that is a problem, is to expand out the entire equation and rearrange things to not perform the division until the last possible point. (Division is usually the worst culprit of floating point error.)

Another variation along the same lines is that the timer code is not accurate enough and you are getting inconsistent deltas. Variations of this include having your time sampling done in the wrong locations such that they are not compensating for varying loop times. If the call to physics is part of the main loop, each time it hits it could be throwing off the delta time considerably such that your time stream is 2ms, 2ms, 2ms, 5ms, 2ms, 2ms, 2ms, 5ms, etc. Each time you hit 5ms your interpolation could/would "jump" considerably.

Finally, why are you lerping at all? My thought on this is to go back to the standard "a' = a + v * delta" and simply recalculate v to point at your target point as it changes from physics/network update. There are some downfalls to this but in general the upside is that your game loop code is written without anything unusual, you get interpolation/extrapolation for free (on a linear path at least) and to perform smoothing/correction you simply modify the v vector smoothly instead of fiddling with absolute start/end points. Fiddling with the v vector to do smoothing and compensation is not the most intuitive item in the world but it is generally easier than doing the lerps all over the place since you can localize the code to one place (generally after physics/network but before the client presentation code).

Mostly the trick to using the velocity item is if the velocity is to be open ended or targeted. What I mean is that if you don't get an updated location and you go past the target point, do you keep extrapolating down the path or go ahead and stop? Sometimes "stop" is the correct answer, i.e. things on fixed paths, mostly not though. The way I did this last time was using a spring system, I let the motion continue past the target but started slowing down until an update arrives. With very bad packet loss and/or variable latencies, this acted as a smoothing system where the objects were not rock solid continual speeds and such but they tended to adjust automatically to the bad network conditions without a lot of code involved. Under good network conditions though, the slight rubber band effect is imperceptible. Keep in mind that this is just for the client presentation, proper latency hiding and all that are done at higher levels, this just helps out and maintains the code in a manner which is not so different than single player code.

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
[quote name='AllEightUp' timestamp='1314015637' post='4852244']
[quote name='fholm' timestamp='1314000735' post='4852174']
[code]
Vector3 lerpFrom;
Vector3 lerpTo;
float lerpTime ;

void physicsStep() {
lerpTime = currentTimeSinceGameStarted;
lerpFrom = currentPosition;
lerpTo = currentPosition + currentCalculatedInputVector;
}

void update() {
currentPosition = Vector3.Lerp(lerpFrom, lerpTo, currentTimeSinceGameStarted - lerpTime / 0.015f);
}
[/code]

But no luck with this approach either. And again, for emphasis: It's not insanely jerky, but it's not as smooth as when doing a single player game and doing the movement in the update function with just [b]"currentPosition += movementVector * frameDelta * 50.0f;*[/b]
[/quote]

My first thought here is that floating point imprecision could be causing problems. Given the standard lerp implementation of "a' = a + (b-a) * t" and the way you are calculating t, there are a couple places you could be hitting problems with precision. I don't think this is the most likely case but you might want to log out the from/to and the time percentage and see what the values are. If you see numbers like 1000.0001f for a position and a number like 0.0001f for the percentage then you are likely hitting problems in this area. The fix, if that is a problem, is to expand out the entire equation and rearrange things to not perform the division until the last possible point. (Division is usually the worst culprit of floating point error.)

Another variation along the same lines is that the timer code is not accurate enough and you are getting inconsistent deltas. Variations of this include having your time sampling done in the wrong locations such that they are not compensating for varying loop times. If the call to physics is part of the main loop, each time it hits it could be throwing off the delta time considerably such that your time stream is 2ms, 2ms, 2ms, 5ms, 2ms, 2ms, 2ms, 5ms, etc. Each time you hit 5ms your interpolation could/would "jump" considerably.

Finally, why are you lerping at all? My thought on this is to go back to the standard "a' = a + v * delta" and simply recalculate v to point at your target point as it changes from physics/network update. There are some downfalls to this but in general the upside is that your game loop code is written without anything unusual, you get interpolation/extrapolation for free (on a linear path at least) and to perform smoothing/correction you simply modify the v vector smoothly instead of fiddling with absolute start/end points. Fiddling with the v vector to do smoothing and compensation is not the most intuitive item in the world but it is generally easier than doing the lerps all over the place since you can localize the code to one place (generally after physics/network but before the client presentation code).

Mostly the trick to using the velocity item is if the velocity is to be open ended or targeted. What I mean is that if you don't get an updated location and you go past the target point, do you keep extrapolating down the path or go ahead and stop? Sometimes "stop" is the correct answer, i.e. things on fixed paths, mostly not though. The way I did this last time was using a spring system, I let the motion continue past the target but started slowing down until an update arrives. With very bad packet loss and/or variable latencies, this acted as a smoothing system where the objects were not rock solid continual speeds and such but they tended to adjust automatically to the bad network conditions without a lot of code involved. Under good network conditions though, the slight rubber band effect is imperceptible. Keep in mind that this is just for the client presentation, proper latency hiding and all that are done at higher levels, this just helps out and maintains the code in a manner which is not so different than single player code.
[/quote]

Hey, and thanks again for an excellent answer, I owe you a lot!

I've been doing some digging and what you've described here, is what's happening I think:

[quote=AllEightUp]If the call to physics is part of the main loop, each time it hits it could be throwing off the delta time considerably such that your time stream is 2ms, 2ms, 2ms, 5ms, 2ms, 2ms, 2ms, 5ms, etc. Each time you hit 5ms your interpolation could/would "jump" considerably.[/quote]

It's not exactly the same, but basically the [b][i]t[/i][/b] i use for input to my lerp in the update function never reaches 1.0, so when I go to the physics step there is some movement that still hasn't happened on screen since it only got to 0.8 or 0.75, etc. and then the new physics step will happen and it will jump the position forward and start lerping towards the next one, etc.

I've also learned that using Unity (which is what I am), you shouldn't read input in the physics step (FixedUpdate in unity) as the input state is only refreshed once every frame, which could also be part of my problem. And that using Unity you really should be reading input inside of your Update function, so I did work on a solution that did the input reading and quantification to 15ms steps inside my Update function, and it seems to be working properly.

Basically what I did with the Update based version (instead of FixedUpdate/physics step) was that quantify the commands to 15ms by using delta time and counting upwards in a variable to 15ms and using that to calculate movement and creating commands that need to go to the server, this is how it looks without the server parts:

[code]


byte pstate;
float ptime;
Vector3 pvector;
Vector3 cpos;

void MoveCmd(float delta) {
var state = GetKeyState();

// Most uncommon path
if(pstate != state) {
cpos += pvector;
ptime = 0;
pvector = Vector3.zero;

// Most common path
} else if(ptime+delta >= 0.015f) {
delta -= (0.015f-ptime);
cpos += KeyStateToVector(state);
pvector = Vector3.zero;
ptime = 0;
}

while (delta >= 0.015f) {
cpos += KeyStateToVector(state);
delta -= 0.015f;
}

ptime += delta;
pstate = state;
pvector = KeyStateToVector(state) * (ptime / 0.015f);

transform.position = cpos + pvector;
}[/code]


It does give me silky smooth movement and seems to be working on both the client and server properly (this is just the client part), i'm just not sure "it's right" (the correct way to do it)? But I saw now other way, especially since unity doesn't update the Input between physics step (only once per frame).

[b]Also one questions, about:[i] [/i][/b][color="#1C2837"][size="2"][i]a' = a + v * delta[/i][/size][/color]
[color="#1C2837"][size="2"][i]
[/i][/size][/color]
[color="#1C2837"][size="2"]How would an implementation of this look, I'm assuming something like:[/size][/color]

[list][*][size="2"][color="#1c2837"]a = current position[/color][/size][*][size="2"][color="#1c2837"]a' = new position[/color][/size][*][size="2"][color="#1c2837"]v = vector from a to valid position[/color][/size][*][size="2"][color="#1c2837"]delta = frame delta[/color][/size][/list]

[size="2"][color="#1c2837"]so in the physics step I would set something like:[/color][/size]

[size="2"][color="#1c2837"][code]p = serverposition + allUnverifiedCommandsReapplied
v = p - a // from a to p[/code][/color][/size]




[size="2"][color="#1c2837"]and then in the update/frame step I would do something like this:[/color][/size]


[size="2"][color="#1c2837"][code]a' = a + (v * delta)
a = 'a[/code][/color][/size]


[size="2"][color="#1c2837"]but wouldn't this keep me moving "over" my [i][b]p [/b]? [/i]maybe I'm missunderstanding. [/color][/size]

[size="2"][color="#1c2837"]Again, thanks for all your answers and the time you've taken out of your day to help me![/color][/size]

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
Ok, so I tried implementing the[i][b] 'a = a + (v * delta); [/b][/i]stuff but I just can't seem to get it right, here's the code (only client side, no network integration now just making sure the smoothing works properly):

[code]

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() {
// KeyStateToVector and just return a vector from a keystate, which is returned
// from GetKeyState which returns a byte with flags set depending on keys down
tpos += KeyStateToVector(GetKeyState());
tvelocity = (tpos - transform.position).normalized;
}

// Every frame
void Update () {
transform.position = transform.position + (tvelocity * Time.deltaTime * 5.0f);
}
[/code]


Maybe I misunderstood something from your implementation, but I get a very "springy"-feel to it when I move and it's not possible to make twitch reactions like changing direction, etc. There's also some problems with overshooting and stuff but that can be fixed by clamping the transform.position.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314025186' post='4852324']
Ok, so I tried implementing the[i][b] 'a = a + (v * delta); [/b][/i]stuff but I just can't seem to get it right, here's the code (only client side, no network integration now just making sure the smoothing works properly):

[code]

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() {
// KeyStateToVector and just return a vector from a keystate, which is returned
// from GetKeyState which returns a byte with flags set depending on keys down
tpos += KeyStateToVector(GetKeyState());
tvelocity = (tpos - transform.position).normalized;
}

// Every frame
void Update () {
transform.position = transform.position + (tvelocity * Time.deltaTime * 5.0f);
}
[/code]


Maybe I misunderstood something from your implementation, but I get a very "springy"-feel to it when I move and it's not possible to make twitch reactions like changing direction, etc. There's also some problems with overshooting and stuff but that can be fixed by clamping the transform.position.
[/quote]

You can't normalize the tvelocity in the above or you loose the approximation of "velocity" and only get direction. Otherwise, it looks like everything is correct though I'm still curious about the "* 5.0f" unless somehow deltaTime is not in seconds.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314019443' post='4852278']
[quote name='AllEightUp' timestamp='1314015637' post='4852244']
My first thought here is that floating point imprecision could be causing problems. Given the standard lerp implementation of "a' = a + (b-a) * t" and the way you are calculating t, there are a couple places you could be hitting problems with precision. I don't think this is the most likely case but you might want to log out the from/to and the time percentage and see what the values are. If you see numbers like 1000.0001f for a position and a number like 0.0001f for the percentage then you are likely hitting problems in this area. The fix, if that is a problem, is to expand out the entire equation and rearrange things to not perform the division until the last possible point. (Division is usually the worst culprit of floating point error.)
[/quote]

Hey, and thanks again for an excellent answer, I owe you a lot!

I've been doing some digging and what you've described here, is what's happening I think:

[quote=AllEightUp]If the call to physics is part of the main loop, each time it hits it could be throwing off the delta time considerably such that your time stream is 2ms, 2ms, 2ms, 5ms, 2ms, 2ms, 2ms, 5ms, etc. Each time you hit 5ms your interpolation could/would "jump" considerably.[/quote]

It's not exactly the same, but basically the [b][i]t[/i][/b] i use for input to my lerp in the update function never reaches 1.0, so when I go to the physics step there is some movement that still hasn't happened on screen since it only got to 0.8 or 0.75, etc. and then the new physics step will happen and it will jump the position forward and start lerping towards the next one, etc.
[/quote]

I should have considered that, it's one of the reasons I didn't like lerping in the first place because dealing with the fractional times is somewhat of a pain. What I mean by fractional times is that typically you will always enter and perform your physics a bit later than whatever rate you specify. So, if for instance you want to hit your physics every 15ms, it is more likely that you will hit at 16, 17 or 18 and never actually hit at exactly 15. So, you have to take into account for this time delta or you end up not properly using the full proper range of the lerp.

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
Ok so I changed the implementation to be like this:

[code] 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);
}[/code]



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?

Share this post


Link to post
Share on other sites
hplus0603    11356
[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling
[/quote]


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:

[code]
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
}

[/code]

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.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314034082' post='4852407']
Ok so I changed the implementation to be like this:

[code] 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);
}[/code]



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?
[/quote]

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.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='hplus0603' timestamp='1314037514' post='4852433']
[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling
[/quote]


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:

[code]
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
}

[/code]

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.

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
[quote name='AllEightUp' timestamp='1314040288' post='4852456']
[quote name='fholm' timestamp='1314034082' post='4852407']
Ok so I changed the implementation to be like this:

[code] 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);
}[/code]



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?
[/quote]

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):

[list=1][*]I enter the fixed update (simulation/physics step) and notice a FORWARD command (W is down), which creates my movement vector: [i][b]mv[/b][/i][*]I take the currently wished position ([b][i]wp[/i][/b]) and add the FORWARD commands movement vector, as: [b][i]wp' = wp + mv[/i][/b][*]I calculate the velocity vector ([i][b]vv[/b][/i]) from the screen position ([i][b]sp[/b][/i]) to the [i]wp [/i]like this: [b][i]vv = wp - sp[/i][/b][*]I'm now inside my update function (as the simulation/physics step is done), I update my screen position by doing this: [i][b]sp' = sp + (vv * dt)[/b][/i][*]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[*]I again register a FORWARD command, but I have not yet arrived at my previously wished position ([i][b]wp[/b]) [/i]since since I took [b]vv [/b](which points from my on screen position to my wished position)[b] [/b]and multiplied it by the time delta[b] dt[/b], assuming the velocity vector [i](vv) [/i]between my screen position [i](sp) [/i]and my previously wished position was (0, 0, 1) and the time delta [i]dt[/i] 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 [i](wp)[/i] 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.[/list]
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!

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
[quote name='hplus0603' timestamp='1314037514' post='4852433']
[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling
[/quote]


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:

[code]
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
}

[/code]

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".

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314043899' post='4852486']
[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.
[/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):

[list=1][*]I enter the fixed update (simulation/physics step) and notice a FORWARD command (W is down), which creates my movement vector: [i][b]mv[/b][/i][*]I take the currently wished position ([b][i]wp[/i][/b]) and add the FORWARD commands movement vector, as: [b][i]wp' = wp + mv[/i][/b][*]I calculate the velocity vector ([i][b]vv[/b][/i]) from the screen position ([i][b]sp[/b][/i]) to the [i]wp [/i]like this: [b][i]vv = wp - sp[/i][/b][*]I'm now inside my update function (as the simulation/physics step is done), I update my screen position by doing this: [i][b]sp' = sp + (vv * dt)[/b][/i][*]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[*]I again register a FORWARD command, but I have not yet arrived at my previously wished position ([i][b]wp[/b]) [/i]since since I took [b]vv [/b](which points from my on screen position to my wished position)[b] [/b]and multiplied it by the time delta[b] dt[/b], assuming the velocity vector [i](vv) [/i]between my screen position [i](sp) [/i]and my previously wished position was (0, 0, 1) and the time delta [i]dt[/i] 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 [i](wp)[/i] 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.[/list]
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.

Share this post


Link to post
Share on other sites
sprite_hound    461
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).

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
[quote name='__sprite' timestamp='1314053931' post='4852564']
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).
[/quote]
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).

Share this post


Link to post
Share on other sites
FredrikHolmstr    262
[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.)
[/quote]

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:

[code]

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);
}
[/code]


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 [b]if it changes state[/b].[/quote]

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?



Share this post


Link to post
Share on other sites
FredrikHolmstr    262
[quote name='hplus0603' timestamp='1314037514' post='4852433']
[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling
[/quote]


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:

[code]
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
}

[/code]

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):


[code]

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);
}
[/code]


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.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314093481' post='4852718']
[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.)
[/quote]

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:

[code]

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);
}
[/code]


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 [b]if it changes state[/b].[/quote]

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]

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.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314102496' post='4852755']
[quote name='hplus0603' timestamp='1314037514' post='4852433']
[quote name='fholm' timestamp='1314034082' post='4852407']
But it's still sort of a "springy" feeling
[/quote]


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:

[code]
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
}

[/code]

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):


[code]

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);
}
[/code]


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.

Share this post


Link to post
Share on other sites
Hiwas    5807
[quote name='fholm' timestamp='1314261224' post='4853557']
[b]AllEightUp: [/b]Yes you are correct, I got your solution working also! Thanks so much both of you for your patience with my dumb questions :)
[/quote]

I truly believe in the old saying: "The only dumb question is the one you don't ask." :) This holds amazingly true with programming.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this