2
\$\begingroup\$

How would client-side prediction for movement work? From how I understand it, the player inputs "Move Forward" and the character on the player's computer instantly starts to move forward. It also sends the input to the server along with a framestamp, say frame 100. The input arrives at the server on frame 110. The server knows that the player started movement on frame 100 so the character would've moved forward for 10 frames. On the server, the character moves forward 10 frame's worth then the result is sent. The official state is received and since it nearly matches up with the player's gamestate, the transition is smooth.

My concern is about how this would look on other people's computers who don't have knowledge of the input - only gamestate. How is 10 frame's worth of movement smoothed out when the other computer hasn't started moving the character forward 10 frames earlier?

\$\endgroup\$
2
\$\begingroup\$

Intro

One smooth way to do this is to interpolate for a short period of time if the postions are "near enough" just for a "viewing" perspective, but still have the "proper position" in sync with the server for collisions and so.

Definition of "near enough"

Near enough is "what typically will happen in normal operation" of your servers, and to calculate this, you need to know what units you measure the "near enough" concept, what are the typical movement speeds, and what is the typical de-syncronization time.

For this example let's assume that

  1. The world is in "meters".
  2. The objects are people and cars.
  3. The maximum typical speed of those is 200Km/h which is about 55m/s.
  4. The maximum typical "lag" of the server is 400ms.

Conclusion => Objects due to server lag should not be further than 22 meters (55m/s * 0.4s = 22m) - Let's give a bit of air-room there so we'll round it to "far means 25 meters".

Game state

Then, you shall have two sets of variables, one representing the "real known position" and the other "the visible representation of the objects".

Additionally you'll have to track if you are in the interpolation mode.

Say we have a Car object, with a Vector3 position.

You may have that "model versus view" splitting in your classes, if you are more like a dirty programmer, like in:

Class Car
{
    Vector3 realPosition;
    Vector3 onScreenPosition;
    DateTime interpolationStartTime;
    Vector3 interpolationStartPosition;
    bool interpolating;
}

or you may opt to separate model and view in separate classes, if you consider yourself a more senior and skilled programmer, like in:

Class Car
{
    Vector3 position;
}

Class OnScreenCar : Car
{
    Vector3 onScreenPosition;
    DateTime interpolationStartTime;
    Vector3 interpolationStartPosition;
    bool interpolating;
}

Updating the info from the server from a far distance

When you are receiving a new real position from the server, you check if the new position is "far" from the previous visible position and if so, you just "update" the current position. This will produce a visual glitch but you calculated this to just happen in very few ocasions as normally it will be near.

onServerSendsData()
{
    OnScreenCar c = getCarToEvaluate();
    if( distance( c.position, c.onScreenPosition ) > MyEngine.ServerLagNearPosition )
    {
        c.onScreenPosition = c.position;
    }
[...]

Updating the info from the server from a near distance

When the new real position is "close enough" you may update the visual position by interpolating the position during a period of time. Save the initial position and time.

[...]
    else /*near distance*/
    {
        c.interpolationStartPosition = c.onScreenPosition;
        c.interpolationStartTime = now();
        c.interpolating = true;
    }
}

Update in the game-loop

Then in your normal update, if you are in the interpolating mode, just calculate a point in the middle "from where you were, towards the final position, within a specified time"

onGameLoopUpdate()
{
    OnScreenCar c = getCarToEvaluate();
    if( c.interpolating )
    {
         DateTime n = now();
         float alpha = ( n - c.interpolationStartTime ) / MyEngine.ServerLagInterpolationTime;

         if( alpha < 1 )
         {
             // Really interpolate.
             c.onScreenPosition = interpolate( c.interpolationStartPosition, c.position, alpha );
         }
         else
         {
             // Interpolation time has expired, cache the position and shut down interpolation mode.
             c.onScreenPosition = c.position;
             c.interpolating = false;
         }
    }
    else
    // Your normal non-interpolated code here.
}

Collisions

Of course, all the physics should be done on c.position and not c.onScreenPosition as the latter is only meant for a "visual softness" to the players, but are not representing the real objects but a "phantom" of them that goes to the real object.

Final words

All this is pseudo-code. You should change the events by the ones in your engine. Also you need to change the DateTime types to the ones suitable to your programming language and engine, those types are to give you a verbose idea on what's going on. Of course you must implement some way of interpolation if your engine does not have one.

You can do as many flourishes on this as you want, for example do "easy in-outs" instead of linear, or for example improve the handling of how changes are handled if you receive a new server update while you are still interpolating from the previous one.

Hope this helps!!

\$\endgroup\$
  • \$\begingroup\$ Ah, are you saying that the official state is an interpolation to the predicted state instead of just "jumping" like in my example? \$\endgroup\$ – JPtheK9 Jan 24 '16 at 20:34
  • \$\begingroup\$ No, sorry, maybe I miss-expressed. For example let's use 1 coordinate: The X. And units in pixels (imagine a raster 2D game). Say that at frame 100, the x is 30px and you start moving at Speed = 1px/frame. Imagine at the server this speed arrives at frame 110. So position is now 30px + 1px/f * 10f = 40px. The "official state" is now 40px. Instead, when sending back this to "another player's computer", he still has the object at 30px. So... update "position" to 40 (official), then "onScreenPosition" that was 30, don't set it to 40, just to 32 for example... so it "goes towards the official". \$\endgroup\$ – Xavi Montero Jan 25 '16 at 0:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.