Network Interpolation Help

Started by
7 comments, last by Elspin 11 years, 2 months ago

Does anyone have some good resources with solid example code for writing network code for games? I'm fine with socket code, just the actual syncing and interpolation to make movement/animation between two clients look smooth and proper is really not going well for me. I looked through a few books but most were hundreds of pages long and the stuff I'm interested in is drowned in 400+ pages of instructions for doing things in very old depreciated versions of directx. I've had no problem setting it up in things like Unity that handle all the fancy interpolation and timing for you, but I find using things like Unity cause me a lot of grief when I want to start doing more complicated things like hosting multiple rooms on the same server.

Any resources you guys know of that could help? To be more specific I mean real time authoritative server/client setups such as is often used for the source engine. The source engine papers are interesting and helpful, but not easy to reproduce for someone's beginner attempts at networked gaming. No help needed at all with transmission of data really, more just making that data sent useful and smooth looking to the client.

Advertisement

I recommend this read (although there are a lot of Unreal specific things, many things can be simplified):

http://udn.epicgames.com/Three/NetworkingOverview.html

To summarize:

A client does not know about objects until server decides they are relevant to him. Server keeps track of what objects are replicated to client and periodically sends state updates.

You run the same simulation on both client and server, with the exception of server-specific and client-specific things.
For example, server will need to send updates to other clients, client may want to read user input or animate additional eye candy which is not needed on server.

Additional tracked data usually include velocity vectors. For example, if object is moving to X direction, client continues simulating it's movement even if no updates come from the server. Of course, this leads to small inaccuracies, and updates from server usually snaps it back to "reality".

For this to work, you also need to keep track of network latency.

You will know that, for example object P was at position (x, y) with movement direction to (i, j) at speed s, and it happened t time ago. Based on this data, it is easy to calculate it's position on the client.

Probably the simplest interpolation scheme to start off with looks a bit like this:

  • whenever you receive a message saying Entity X is now at position x,y,z
    • store the entity's current position in a LastPosition variable
    • store the new position in a NextPosition variable
    • set a NextPositionTime variable to be some arbitrary time in the future: eg. Now + 200ms.
  • every frame:
    • if the current time is less than NextPositionTime:
      • set the entity's current position to a point linearly interpolated between LastPosition and NextPosition based on the current time's relation to NextPositionTime
      • eg. If Now is 550 and NextPositionTime is 600, that means 50ms remain of the 200. So: position = LinearInterpolate(LastPosition, NextPosition, 0.75f)
    • otherwise:
      • set the entity's current position to NextPosition and consider it stationary.

For most games that aren't based around reflexes, that will serve you fine. The 200ms value can be reduced significantly once you know how well it's working, as long as you send out new movement messages more regularly than that.

Extending the above to extrapolation can be done just by relaxing the currentTime<NextPositionTime check, as long as there is an upper bound that will snap a player back in the event of lag. If you stop sending movement messages when a character ceases moving, you'll need a a separate message (or flag on the last movement message) to tell the recipient that a player has stopped moving at a given point and that no extrapolation past that point should occur.

Once you have all that working well you can extend it to the player's heading or rotation, and you can consider more advanced interpolation/extrapolation schemes that adjust according to latency, attempting to cover up jitter, maybe interpolating velocity instead of position for better continuity, etc., Hplus0603 has sample code here - http://www.mindcontrol.org/~hplus/epic/ - which works well, but I would suggest coding your own very basic linear interpolation system first if it's a true understanding you're after.

Thanks for the responses guys - they do help, but I'm looking more for solid example code. A while ago I tried, but it didn't end up working very well. I've already read a lot of articles on the subject so I understand what I should be doing, but getting it to work well is another matter. EPIC is something I've been looking at for a while as it does an ok job at doing what I want to do, but it's rather cryptic and comes with no documentation other than a few comments.

The source code for the Quake series (Quake 1 .. 3) and Doom 3 is open source (GPL.) You could start reading there. I'd probably start with Quake 3. It's a bit dated, but still very well engineered.
enum Bool { True, False, FileNotFound };

I wondered this same thing in high school when I was working on the PSP homebrew games. You should look into BSD Sockets --they're native to UNIX/Linux, but also used in Windows (using the Winsock API that comes with the Windows SDK). Every platform I've ever come across supports the socket API.

As far as I'm concerned, this is an awesome guide on how socket programming works:

http://beej.us/guide/bgnet/output/print/bgnet_A4.pdf

It gives a great introduction to the TCP and UDP protocols, how data is transferred over a network and gives you an idea on how network programming works at the low-level. Lots of error checking is involved, but learning this will make you a networking digital Jedi.

There's this other library I've seen in a few projects, notably Cube, called enet. It's built on top of the socket API, so I would look into that as well once you're familiar with Beej's guide above.

I wondered this same thing in high school when I was working on the PSP homebrew games. You should look into BSD Sockets --they're native to UNIX/Linux, but also used in Windows (using the Winsock API that comes with the Windows SDK). Every platform I've ever come across supports the socket API.

As far as I'm concerned, this is an awesome guide on how socket programming works:

http://beej.us/guide/bgnet/output/print/bgnet_A4.pdf

It gives a great introduction to the TCP and UDP protocols, how data is transferred over a network and gives you an idea on how network programming works at the low-level. Lots of error checking is involved, but learning this will make you a networking digital Jedi.

There's this other library I've seen in a few projects, notably Cube, called enet. It's built on top of the socket API, so I would look into that as well once you're familiar with Beej's guide above.

My OP states pretty clearly multiple times that I already know the transport layer, and ENet is in fact a library I use. The topic is directed towards the interpolation and display of sent data to make things like positions, firing times, animation states, etc look smooth on a client that is receiving a message stating an event has happened a very significant amount of time after the event has happened.

Here's some C# sample code which I wrote just yesterday for the very simple client-side interpolation that I mentioned above:


public class CharacterMover
{
    private Vector3 actualPosition; // where we render

    private Vector3 latestPosition; // last position reported by server
    private float latestPositionTime; // the time we want to arrive at the last position reported by server
    private Vector3 previousPosition; // the previous point we're interpolating from
    bool isMoving = false;
    
    // Must be longer than the period between movement messages, plus a little extra to account
    // for network jitter. 0.3f is ok for MMOs and strategic RPGs, probably too high for RTSs,
    // MOBAs, and action RPGs. Far too high for FPSs which need a more complex system than this.
    public float InterpolationPeriod = 0.3f; 
   
    // Update is called once per frame
    void Update ()
    {
        if (isMoving)
        {
            float t = 1.0f - ((latestPositionTime - Time.NOW) / InterpolationPeriod);
            actualPosition = Vector3.Lerp(previousPosition, latestPosition, t);
        }
        else
        {
            actualPosition = latestPosition;
        }

        // TODO: move sprite or 3D model to actualPosition and render it there
    }
    
    // Call this whenever you get a message from the server notifying this client of the character's new
    // position and it's quite close to the current position
    public void SetPosition(Vector3 newPosition)
    {
        isMoving = true;
        
        // Set this as a future position to arrive at, and interpolate between the
        // current position and future position in Update()
        latestPosition = newPosition;
        latestPositionTime = Time.NOW + InterpolationPeriod;
        previousPosition = actualPosition;
    }
    
    // Call this whenever you get a message from the server notifying this client of the character's new position and
    // it's NOT close to the current position, OR you need to perform an instant correction, OR you just want to stop the
    // character still in a certain spot.
    public void SnapToPosition(Vector3 newPosition)
    {
        isMoving = false;
        // move directly there, do not pass go, do not collect $200
        previousPosition = latestPosition = newPosition;
        latestPositionTime = Time.NOW;
        actualPosition = latestPosition;
    }

}
 

It's not intended to be AAA quality tested code, and there are some bugs unhandled edge-cases in there, but it does a decent enough job of keeping movement of networked characters smooth at fairly low movement speeds and infrequent changes in velocity/direction.

Here's some C# sample code which I wrote just yesterday for the very simple client-side interpolation that I mentioned above:


public class CharacterMover
{
    private Vector3 actualPosition; // where we render

    private Vector3 latestPosition; // last position reported by server
    private float latestPositionTime; // the time we want to arrive at the last position reported by server
    private Vector3 previousPosition; // the previous point we're interpolating from
    bool isMoving = false;
    
    // Must be longer than the period between movement messages, plus a little extra to account
    // for network jitter. 0.3f is ok for MMOs and strategic RPGs, probably too high for RTSs,
    // MOBAs, and action RPGs. Far too high for FPSs which need a more complex system than this.
    public float InterpolationPeriod = 0.3f; 
   
    // Update is called once per frame
    void Update ()
    {
        if (isMoving)
        {
            float t = 1.0f - ((latestPositionTime - Time.NOW) / InterpolationPeriod);
            actualPosition = Vector3.Lerp(previousPosition, latestPosition, t);
        }
        else
        {
            actualPosition = latestPosition;
        }

        // TODO: move sprite or 3D model to actualPosition and render it there
    }
    
    // Call this whenever you get a message from the server notifying this client of the character's new
    // position and it's quite close to the current position
    public void SetPosition(Vector3 newPosition)
    {
        isMoving = true;
        
        // Set this as a future position to arrive at, and interpolate between the
        // current position and future position in Update()
        latestPosition = newPosition;
        latestPositionTime = Time.NOW + InterpolationPeriod;
        previousPosition = actualPosition;
    }
    
    // Call this whenever you get a message from the server notifying this client of the character's new position and
    // it's NOT close to the current position, OR you need to perform an instant correction, OR you just want to stop the
    // character still in a certain spot.
    public void SnapToPosition(Vector3 newPosition)
    {
        isMoving = false;
        // move directly there, do not pass go, do not collect $200
        previousPosition = latestPosition = newPosition;
        latestPositionTime = Time.NOW;
        actualPosition = latestPosition;
    }

}
 

It's not intended to be AAA quality tested code, and there are some bugs unhandled edge-cases in there, but it does a decent enough job of keeping movement of networked characters smooth at fairly low movement speeds and infrequent changes in velocity/direction.

Thanks, I'll definitely try this out!

This topic is closed to new replies.

Advertisement