Interactions in a Client-Side-Predicted Network-Environment

Hey folks,

In our network environment, Client A (Alpha Wolf) predicts it’s own movement for the time it takes him to send his input to the server, and the server to send the results back.
Therefore, at an example-ping of 50ms, the client lives visually 100ms in the future.

Now we assume, he fires his weapon at his opponent “Client B” (B = Bad Guy). Where is this opponent displayed on the client A? Since it takes the server 50ms to send the position of Client B to Client A, Client B is displayed at the location where it was 50ms ago on the server.

Now assuming Alpha Wolf shoots at Bad Guy, directly aiming at him. His shoot will again take 50ms until it is processed by the server, making the bullet on the server spawn “50ms of movement in the past”, relative to Alpha Wolf’s view (since we actually live 100ms in the future, but the bullet will be already processed by the server in 50ms). Also, Bad guy will have continued to move on the server for 50 more milliseconds.

So, let’s add that up. Okay, that is really easier to imagine with a graphic, so I made one.

The whole graphic is from the perspective of Alpha Wolf, also the Millisecond-Axis is just for the client’s time, representing position.

Green Dot: The position where AlphaWolf (Client A) sees players locally at that moment
**Red Dot: **The position where the players actually are at that moment on the server
The Blue Lines: The movement past between the two timestamps. For the pew pew, the translation from local shooting location to server shooting location.

The numbers (1 and 2) represent the timesteps, each dots with a 1 are from one snapshot, the ones with a 2 from the next snapshot, which are 50ms apart.

As you can see, we have multiple issues here:
1.) The Bullet on the server fires from a different position than on the client, eventually having as a concequence, that objects block the way of the bullet, even though on the client, it looked like the path is clear.
2.) Even though that the Alpha Wolf aimed directly at the Bad Guy, the bullet will miss him by 150ms worth of movement.

My question is now, what are your suggestions to compensate this? Are there any “standarts” or common techniques for this issue?
These differences between Local Simulation and Server might not be very noticeable in some FPS environments, but as travling speed increases, these issues get more and more noticeable.

I am thankful for any help and discussion :slight_smile:

Actually, I figured, it is “just” 100ms difference, because half of the Prediction is the time the server sends the data to the client, were the player on the server already moved. Though, the problem persists.

Yes, a distributed simulation has to deal with the different world views!
There are approximately three ways of doing this, and which way you choose determines the “feel” of your game.

The first way, is to do all the interactions on the server, and display the outcomes on the client. This will make everything 100% consistent, but your client avatar will lag by your round-trip-time. This is often appropriate for games where you can hide the latency through animations (RTS-es with acknowledgement animations, RPG games with slow walking acceleration, click-to-move games, etc.) It is generally too laggy for twitch FPS games. You can build this in Unreal Engine, using only RPC/Remote Events, not using the built-in actor movement replication.

The second way, is to send everything when it happens, and to display remote entities with forward-extrapolated positions. This is approximately what Unreal Engine does (although I don’t know exactly how much forward extrapolation it does.) Because player A is firing on a “forward extrapolated” entity, it’s more likely that the shot will land where it’s supposed to. Similarly, because the command stream to the server for movement has the same delay as the command stream for firing, the server will have the actor “fire” from the same location as it was on the client – the source location will be the same. Depending on how much forward extrapolation you do, you may have to “lead” shots, though. If you use zero forward extrapolation, you get the “Quake” model, where players learned to aim in front of where other players were running.

The third way, is to send all the appropriate information from the client to the server. So, instead of “I clicked to fire weapon X,” send “at position Y, at time Z, I clicked to fire weapon X in direction W.” The server, when it receives this command, will keep a log of previous physics states, and “rewind” the simulation to that point in time, and apply the new command, after checking that the command was consistent with what it knows about the world, to avoid cheating. Also, the amount of time the server allows for rewind cannot be too much, or that, too, would open you up to cheating. This is known as “the counter-strike model” or “the Source engine network model” and works very well for many cases, but also needs significant engine support, which is not available in plain Unreal Engine.

Thanks for your response.
I think 3.) will be the most suitable. I already implemented Physx Client-Side-Prediction on the clients for our Physx Vehicles with Rewind&Replay… Replaying non-complex physics actors by a location history shouldnt be too hard then, I hope. But can’t that be exploited for cheats? When the client says “I shoot at time X” when he infact shoot at time Y? Of course, the server should check if the value is reasonable, but though, feels like this could be a nice entry-point for hackers.

You could dampen hacking by enforcing a maximum limit to how far back the server can rewind to check for a hit (which you’ll need anyway to keep RAM usage from growing indefinitely). Also, I believe the CharacterMovement already keeps track of client time for replication purposes, but such details aren’t exposed to blueprint. You could look into that to detect bogus times or even keep watch of the incoming inputs and their timestamps (which must be sequential) to figure out how much to rewind.

Yes. You have to reject packets that are too far in the past.
Also, you can keep a running estimate of how late incoming movement packets are, and if the “I shot” packet is more than 20% more late than the average of the last 5 seconds of movement packets, then reject it.
(for some value of 20% and 5)