Advertisement

Entity interpolation not being smooth at higher server send frequency

Started by September 14, 2023 05:05 PM
7 comments, last by frob 1 year, 2 months ago

I am using Unity and Riptide. Riptide is used to send packets between client/server.

What I want to achieve is a smooth transition between the different position states of other players. I don't have any server authority. Clients send their positions and the server redirects them to the other connected clients.

The clients send their positions to the server at a consistent frequency, and the server sends positions of clients to all clients at another, slightly lower, frequency. At the moment it's only really smooth when I use low send frequency for the server, such as 0.5 seconds. When testing with higher frequency, e.g. 0.1s, there is some amount of lag/jitter/bounciness. It makes the player seem to not have constant acceleration, and so makes it not look as it should.

I have read about entity interpolation here, and also here. And below is an implementation for interpolating all the players client side.

// called when a new position packet arrives from the server
void UpdatePlayerPositionBuffer(int id, Vector2 newPosition) {
    Player player = null;
    ClientManager.Instance.OtherPlayers.TryGetValue(id, out player);
    if (player != null) {
        player.PositionBuffer.Add((Time.time, newPosition));
    }
}

// called every frame by unity. Time.time is the total elapsed time
void Update() {
    // interpolationDelay is set to double server send rate as recommended by valve
    float renderingTime = Time.time - interpolationDelay;
    
    foreach (Player p in ClientManager.Instance.OtherPlayers.Values) {
        while (p.PositionBuffer.Count >= 2 && p.PositionBuffer[1].time < renderingTime) {
            p.PositionBuffer.RemoveAt(0);
        }

        if (p.PositionBuffer.Count >= 2) {
            float t0 = p.PositionBuffer[0].time;
            float t1 = p.PositionBuffer[1].time;

            bool inbetween = t0 <= renderingTime && t1 >= renderingTime;
            if (!inbetween) {
                continue;
            }

            Vector2 p0 = p.PositionBuffer[0].position;
            Vector2 p1 = p.PositionBuffer[1].position;

            p.Position = Vector2.Lerp(p0, p1, (renderingTime - t0) / (t1 - t0));
            
            // set the position of the object in the world
            GetPlayerObject(p.Id).transform.position = p.Position;
        }
    }    
}

public class Player {
    public int Id;
    public Vector2 Position;
    public List<(float time, Vector2 position)> PositionBuffer = new();

    public Player(int id, Vector2 position) {
        Id = id;
        Position = position;
    }
}

It is not getting stuck in the “in-between” case, and I always have positions available for interpolation.

Here is a video showcasing the problem: video

The red squares are the players. The one with the white dot is the local player for that instance. As you might be able to see - the local player is moving smoothly while the other is lagging.

If you by any chance think you know what the problem may be please tell.


Original post here: https://stackoverflow.com/questions/77072785/entity-interpolation-not-being-smooth-at-higher-server-send-frequency

None

There are many possible causes for the behavior you describe.

Is it possible that your messages are not delivered regularly?

If you timestamp when you receive the messages with a high-precision timestamp, do you get them at a regular cadence, or do you get them all bunched up?

I'm assuming you already have some mechanism to reconcile server clock with client clocks. Does this mechanism have jitter in it?

You could also put the timestamps into the messages themselves, or, if you use a fixed simulation step size, put the step number into the messages. Currently it looks like you're taking packet arrival time as the “time” for the position, which throws away all the information each sending client, and the server, may have.

You should also detect whether renderingTime is ever less than t0 or greater than t1, and log a message in that case, that might show something.

enum Bool { True, False, FileNotFound };
Advertisement

@hplus0603 Thank you for your feedback!

I have accumulated the data for the time difference between packets recieved in milliseconds. Here is the result when having a server send frequency of 50ms. As you can see it often jumps up to ~70ms. This is probably the cause for the jitter, right? Is it normal for time difference to jump like this at these update rates?

I do not have a mechanism for reconciling server/client clocks. Time.time in the code is the elapsed time for the program running for the client user. I don't ever send packets that have to do with time.

None

You will likely have to add some code to make sure that the clients and the servers agree on what point in the game simulated time specific requests should take effect. And, once you have that, you also need for each client (and the server) to establish the relationship between “local wallclock time” and “simulated game time.”

20ms jitter may or may not cause display lag, depending on how much you smooth and how far back you look in time. It will typically be kind-of subtle, not big enough to throw the character all over the place, but if the player camera jerks back and forth a little bit, it could still be quite noticeable. If you see large jerks with 20 ms jitter, then something else is the matter in your interpolation/extrapolation.

Also: WiFi is generally terrible, and may easily add ten times that in jitter for a normal player. Be prepared to deal with a fair amount of jitter, if you want to support people on WiFi!

enum Bool { True, False, FileNotFound };

@hplus0603 After some testing I found out that the server was not sending packets at a steady rate. I was using unitys coroutines, and they are apparently not perfect when it comes to waiting for an amount of time. I instead chose Monobehaviour.InvokeRepeating.

Anyway, this fixed the time difference between packets received on the client, now it's pretty much always 50 ± 1 ms. BUT the jitter is still there.

I do not understand what I would do with a synchronized clock as you are recommending. In this article I don't think they mention something like that for the interpolation. Could you elaborate?

Thank you so much for all the help 🙂

None

lomstfer said:
@hplus0603 After some testing I found out that the server was not sending packets at a steady rate. I was using unitys coroutines, and they are apparently not perfect when it comes to waiting for an amount of time. I instead chose Monobehaviour.InvokeRepeating. Anyway, this fixed the time difference between packets received on the client, now it's pretty much always 50 ± 1 ms. BUT the jitter is still there. I do not understand what I would do with a synchronized clock as you are recommending. In this article I don't think they mention something like that for the interpolation. Could you elaborate? Thank you so much for all the help 🙂

@hplus0603 If you can answer please do :D

None

Advertisement

The whole subject of “virtual time” in distributed interactive simulations is pretty large.

Typically, games will do one of two things. They will either simulate a fixed timestep size, and count the “current simulation time” in number of steps since “the beginning” (whatever that is.) Or they will simulate elapsed time, with a varying step size, and count time in fractional seconds since “the beginning.”

When the screen runs at a refresh rate that is not the same as the simulation rate (when using time steps,) games will typically interpolate between the last two simulation steps, based on wallclock time and some fixed anchor point. In a networked game, everyone will at least simulate the same time steps; it's possible to be 100% deterministic in this model, but it's not necessary always required.

When the simulation runs at a variable rate, games will take one (or more, if the frame time is long) step per refresh, but these steps are different size on different clients. It is not possible to be deterministic here, but there is zero interpolation lag between local simulation state and display state. Simulation is non-deterministic, because different frame rates may lead to different outcomes (such as jumping higher at higher frame rates, common in older games.)

The state you want to transmit between clients is “at time X since start of game, player P was at position Q, moving in direction/velocity W."

Once a client receives this, they can choose to either forward extrapolate the displayed position of remote entities to the current wallclock time, OR they can choose to display remote entities some amount of time behind, and interpolate between known positions. In either case, you will likely also interpolate between previously displayed/assumed positions, to make movement always be smooth. Check out https://github.com/jwatte/EPIC​ for an example of doing this.

When you have clear determination of which entities are where (and moving how fast/in what direction) at each time, it is now possible to compensate for dropped packets, jittery packets, and also it's possible to debug if you have mistakes in your code, because all the time stamps would be in the same frame of reference, and thus make sense when you log them or check them in the debugger.

To achieve this, you need every client to establish their own offset from “local clock time” to “game time.” Typically you do this by sending timestamps from the server to the client, and the client will adjust their offset if they find that the received server time is different from what they locally calculate by more than some acceptable delta.

enum Bool { True, False, FileNotFound };

I have also worked on games where the precision wasn't needed. It was low bandwidth, potentially high latency. Characters over the wire were issued “move to” commands if they were far enough out of sync, and interaction commands for the things in the world.The game design didn't require anything more precise.

Design varies by game. Shooter games have a design that needs continuous updates with precision enough for players to agree about combat. They are quite demanding technically for a great game experience. Other games may have different design needs.

This topic is closed to new replies.

Advertisement