Rotation interpolation in realtime MMO game

Started by
8 comments, last by caymanbruce 6 years, 10 months ago

In my realtime MMO game. On the server, the player can rotate to an angle facing to my mouse with an easing effect. So it has a current rotation (CR) and a future rotation (FR), the CR will interpolate to the FR during an update on the server. This works perfectly if I put the code on client side without a network environment. I can interpolate the angle from current rotation to the future rotation of the player.

However in my current settings, when I do an interpolation based on the information received from the server, I am interpolating from the player's previous received rotation to its latest received rotation which both are CRs. The interpolation works fine but it doesn't reflect accurately what the server is doing because this interpolation doesn't use FR at all. It is just grabbing the data from the network and interpolating the two CRs. In reality it has easing effect but it is responding to the rotation event very slowly then suddenly change its direction. I want to implement a client-side interpolation with future rotation data coming from the server but not sure how to do it.

My current working code is like this:


shortestAngle = (((latestCR - previousCR) % (Math.PI * 2) + Math.PI * 3) % (Math.PI * 2)) - Math.PI;

progress = shortestAngle * ratio;

interpolateRotation = progress + previousCR;

How can the future rotation fit in?

Advertisement
You need to send the "future rotation" as a piece of input data from the player to the server, and from the server to each client.
Then you need to use the same algorithm to interpolate as you do for the player input.
As "current rotation" you can use whatever rotation the entity has on the local machine, and you can snapshot that each time you receive a new "future rotation."
enum Bool { True, False, FileNotFound };

You need to send the "future rotation" as a piece of input data from the player to the server, and from the server to each client.
Then you need to use the same algorithm to interpolate as you do for the player input.
As "current rotation" you can use whatever rotation the entity has on the local machine, and you can snapshot that each time you receive a new "future rotation."

I kind of get what you mean but still want to clarify. I am sending the rotation from player to the server and use it as the "future rotation".

As for the algorithm, on the server I use it to interpolate from CR to FR, on the client side I use it to interpolate CRs from previous state to latest state.

If I send both rotations over the network, on the client side I will get both CR and FR for previous state and current state. Do I do the interpolation between the CR of previous state and FR of current state? I have tried this but then my player will always face to the same direction.

Actually my code seems to work fine. But I am just annoyed by the sudden change of direction and slow response to the mouse event. If the player keeps receiving the latest state from the server and interpolate between them it is actually doing what the server is doing at realtime. I wonder if the problem is at the server side.

slow response to the mouse event


Is this for the local player? Why do you wait for the server's message before you start showing interpolated rotation on the local client?

I would imagine that you only need to send the "target rotation" from the client (this is what you call "future rotation" I think -- what the player says they want with the input.)
When the local player moves the mouse, you set a new "target rotation." For each update frame, you interpolate the entity towards the target rotation. Every X times a second, you send the "target rotation" (and any other state/events) to the server, and it sends it to other clients watching your player.
Because you turn directly on the client, this will be smooth on the screen and have instant feedback.
When your client receives a "target rotation" for another entity, it marks that as the target rotation, and each update frame, it interpolates the remote entities towards the target rotation. That way, they will move smoothly, too.

If that's not how you want to do it, then what is it that makes you want to do it another way?
enum Bool { True, False, FileNotFound };

slow response to the mouse event


Is this for the local player? Why do you wait for the server's message before you start showing interpolated rotation on the local client?

I would imagine that you only need to send the "target rotation" from the client (this is what you call "future rotation" I think -- what the player says they want with the input.)
When the local player moves the mouse, you set a new "target rotation." For each update frame, you interpolate the entity towards the target rotation. Every X times a second, you send the "target rotation" (and any other state/events) to the server, and it sends it to other clients watching your player.
Because you turn directly on the client, this will be smooth on the screen and have instant feedback.
When your client receives a "target rotation" for another entity, it marks that as the target rotation, and each update frame, it interpolates the remote entities towards the target rotation. That way, they will move smoothly, too.

If that's not how you want to do it, then what is it that makes you want to do it another way?

Yes you are right. I am testing it for local player now. I haven't tested other players' rotation yet. In my interpolation all the states of all players are received from the server. So I interpolate from previous state to latest state, as you suggested the other day. The received states include local player state.

I am doing the rotation thing like this:

when local player moves the mouse. It will calculate the future rotation and send it to the server. The server receives the rotation value and do the interpolation from its current rotation to the future rotation. The server sends out rotation value to every client at an interval. I can see the clear difference between what I am doing and your approach. If I skip the easing effect it looks OK. If I add the easing effect, on sending a mousemove event, the local player will wait for some time before it rotates to the future rotation. In fact I want smooth rotation when I move the mouse. Using local interpolation will probably do better. But does it work well with my player's position interpolation? I am concerned with this because the positions updates are from the server, but the rotation values will be local. They are in different timestamp.

Games that want to show immediate feedback to user commands do not run the local player based on server feedback; they run the local player baeed on local input, and show remote players based on server data.
The model where everything is just shown based on the server data stream is robust, easy to understand, and easy to implement, but it does have the cost of waiting for server roundtrip before command feedback is shown.
Often, 30-40% of the trick of network game programming is in how to treat the local player different from everything else, to hide this latency.
enum Bool { True, False, FileNotFound };

Games that want to show immediate feedback to user commands do not run the local player based on server feedback; they run the local player baeed on local input, and show remote players based on server data.
The model where everything is just shown based on the server data stream is robust, easy to understand, and easy to implement, but it does have the cost of waiting for server roundtrip before command feedback is shown.
Often, 30-40% of the trick of network game programming is in how to treat the local player different from everything else, to hide this latency.

I used to run the local player based on local input, and remote players based on server data. But then I found that I also run the same physics update of every player on the server, the two states (server and local) hardly match each other. I am not very sure what kind of data is used remotely and what are needed to run on local machine. Like you said, running everything on the server and based on the server data is easy to implement so I follow this path until I hit the wall.

I can have two copies of positions and rotations both on server and local. But the local player still needs to know when it is dead if someone kills it. Would it be possible that the player is killed on the server but the local player is still alive and can respond to local input? Or maybe the local player gets killed but actually it is still alive on the server? How to tackle with this kind of situation?

How to fix this depends on your physics model. Most 'MMO' games have a very trivial physics model where objects move quite predictably and aren't expected to interact much. This means you can steer your local player around without having much effect on anyone else, and vice versa. This in turn means there's no divergence between the various simulations, apart from each client's local player object.

If you require a more rigorous physics simulation, but also want a responsive local player object, it can be tricky, because there's fundamentally no way to guarantee that the 2 simulations won't diverge at times due to the delay in getting information from one computer to the other. And yes, it's possible - and standard in most games, when you think about it - that a player can be dead on the server but alive on the client, at least for some number of milliseconds until the server message arrives on the client to tell it that the player died.

The common solution is simply to avoid trying to run a full physics simulation on each client and server, instead running simplified physics - especially on the client - which can be eased (or snapped) back to a position when the server demands it. And if the client remembers its state in the recent past, it can ignore any data from the server which is just replaying those past positions, only snapping or easing if the two simulations diverge significantly.

the two states (server and local) hardly match each other


Assuming you use a fixed time step, and assuming you time-stamp all inputs when sending them to the server, the states should match, as long as two players don't interact with each other.
Once players interact with each other, you will get discrepancies on the clients, and breaking those ties is where the "art" of game network programming comes in.
There are many, many, ways to solve this, and they all depend on what your end goal is. For example, saying "players can't collide with each other" and "player to player (magic) effects always have at least half a second casting time" will make it much easier to stay in sync.
Even if players are allowed to intersect each other, you don't have to render them like that -- on the local client, you can make player avatars step out of the way of each other on the screen, even though they are simulated close to each other.
enum Bool { True, False, FileNotFound };

the two states (server and local) hardly match each other


Assuming you use a fixed time step, and assuming you time-stamp all inputs when sending them to the server, the states should match, as long as two players don't interact with each other.

I haven't time-stamped all inputs. How does that work? I only time-stamp all data sent from the server. Maybe that is the reason my local player positions differently on two sides. For example, I have some bots on the server that wander around the map, they will move away when the player gets close to them. However when I use local rotation and local positions if they get close they just intersect. But that is expectable because the states of the server are about 100ms behind current time.

This topic is closed to new replies.

Advertisement