Okay update time: I’m finally getting somewhere! I won’t directly post code for now because it’s definitely going to have to change and in no way useable or finished… but here’s some idea of how to get it working.
The first step is to sync time-stamps between client and server. In order to do this I use the Player Controller, and send an RPC to the server with nothing in it, all it does is ask the server to call a Client function. It logs the local time (in milliseconds) that it send the packet, and then logs the time when it receives it back (aka, runs the client function). From that, you can work out exactly what the Servers’ time is, on the Client. I’ve tied it into Ping, so as Ping is updated (every 0.5 seconds or so via unreliable RPC), it bounces the timestamp RPC again to keep it accurate, but only if the ping is different to what it was previously. You can find the calculation for working out the offset practically anywhere online, but here’s how you get the different times.
// Local Time on Client
return FMath::RoundToInt(AccumulativeDeltaTime * 1000.0f);
// Estimated time on server
return GetLocalTime() + T_ServerOffsetTime;
Now the tricky part, you create a Buffer of moves on the client which is essentially it’s ‘Move History’ (which comprises of input and the ‘state’ of the object at that time). The key part is that you actually store that history with the ESTIMATED server Timestamp. The idea is that you’re literally ‘predicting’ what the Server is going to say your move was at time X. At the same time, you send the input to the server so that it can process it.
So, the Client processes the input locally and the Server processes it as soon as it receives it. The server then sends the move back to the client with the LOCAL time-stamp for that player controller (aka, actual server time). When the client receives the move, they go through the move buffer and pass over any moves that are stamped before that time (no point erasing them, just move on to the next index). Now they receive the move in the past but check in the history to see if the move was correct at that point.
So yeah… that’s where I’m at. The tricky part now is that the Client receives the corrected move at (Ping / 2) time late, so I now need to work out how to offset it’s current position, based on previous move data. Currently I’m just ‘snapping’ to the corrected move so the Client updates for a while until it gets the move, then ‘snaps’ to an old position, which is of course not right. However, early results are promising (the red outlines are the move history buffer).
This is simulating PktLag of 250Ms (So total 500ms Ping), and only sending packets 5 times a second. Bandwidth useage can definitely be optimized here, but it’s showing promising results. Let’s be honest, if you’re playing on a server with 500ms ping and 95% packet loss, you’re probably not going to stick around for long anyway…
EDIT: Oh and one more thing, I have ‘Replicates Movement’ checked still because I want to update other clients and proxies as well, but I ignore it if I’m the local client. This is kind of lame because it means I’m sending data that I don’t need to to one of the clients, and regularly, so I’ll change the way it’s done eventually. For now though:
if (GetNetMode() == NM_Client && IsLocallyControlled())