Did this already for my Hovertank Movement Component:
The system is really simple, what I do is first check ‘Replicated Movement’, which will first ensure that both vehicles remain in-sync with one another from Server-Side Movement. What I then do is create a Struct which has all of my input states. I later plan on quantizing these for optimization, since in reality I only care about the first decimal point in terms of granularity:
float ThrustInput;
float StrafeInput;
float SteerInput;
float PitchValue;
bool bIsJumping;
I create two variables of that struct in the class, ONE is replicated and the other isn’t. The non-replicated struct is used in the movement component to actually move the craft and perform all calculations, and ultimately is what updates the physics of the craft. That Struct is ALSO the one that is updated via the keyboard/input component - so clients simulate their own movement locally and instantly.
I then send this movement data, on-tick, via an unreliable RPC to the server. This is the unfortunate downside as it requires me to send the RPC on tick, though for now it’s fine since I’m only doing this for one vehicle per-client and therefore the overhead is pretty small. As it’s also unreliable, the engine can choose when to send and not send packets depending on bandwidth. I might be able to get away with sending this on a Timer in future to reduce the amount of sends too, since Tick isn’t always going to have the same Delta time, and therefore bandwidth usage will fluctuate up and down depending on framerate.
The server then processes the movement on the craft from the same Input, and sends it back via the ‘ReplicatedInput’ struct. When this struct is received on the client, they update their local un-replicated copy of the struct, so that their input level matches that on the Server at that time. Additionally, the ReplicatedMovement from the Server (since it also processed the input) is sent back with it, and the position is updated on the client from that automatically.
There are two caveats to this approach at the moment. The first is that unless the Server is sending back Replicated Movement constantly (I.e, calculating it) - there’s the potential for a Client-Side collision to knock the two out-of-sync. I don’t think this is an issue really, since I’m fairly certain that this is what the CharacterMovementComponent does anyway.
The other issue (that is caused by the one above), which only became apparent in high-latency situations - is that the clients input gets simulated, but it gets overridden by the ‘Replicated Movement’. Effectively what happens is the Client waits for the Server to simulate the movement and then moves accordingly. If I can get some finer control over the order of operations here, I can work around this though. For LAN play, the controller latency is practically nothing anyway.
EDIT: I also forgot, I haven’t yet got my implementation working with AI / pathfinding, since it’s based on Input from the keyboard, and the current AI system in engine quite literally just 'set’s the velocity of an object. That’s fine, if you’re working with characters. For anything else it pretty much sucks. I’ll have to write my own interface between the two eventually, which talks directly to the input of the craft rather than essentially hacking it.