Hello! I’ve sort of run out of avenues to explore and it’s been a number of weeks now so I thought I’d reach out here in search of help. I’ve been rapidly learning about implementing arcade vehicle gameplay from scratch that’s networked, with physics that only needs to be very simple, akin to the FloatingMovementComponent’s own logic.
However, before I can start to implement any kind of prediction and correction, the first aspect of implementation is to have my client push its moves to the server and have it recreate them. Unfortunately, I’ve fallen at the first hurdle here, as (presumably because of the slight differences in deltaTime) I see that there are slight discrepancies in the changes of velocity, most notably when it comes to deceleration. This is a huge issue when it comes to the matter of, for example, driving through a narrow arch or doorway and turning. Due to the slight discrepancies, the client happily drives through the gap and carries on, but the server catches the wall, stopping it there, and if I were to implement server-authoritative correction on this, it would lead to a jarring and confusing correction even with zero latency.
This brings me to the CharacterMovementComponent, which somehow navigates around this. I saw a commeont within the code within talking about how it uses timestamps it receives to calculate deltaTime to keep it deterministic, but I can’t make hide nor tail of how exactly its doing that. I’m already sending timestamps and before/after snapshots to the server when I tell it to recreate a move, and think if I can ensure the deltaTime is the same for both client and server, I’ll be able to fix this. But I’m not sure how to go about it. If anyone is able to help or point me in the right direction, I’d be greatly appreciative.
there are multiple ways around that issue but since you are talking about the CMC specifically: they simply subtract the previous timestamp from the current one and use that as the delta time for the move, both on the server and the client so you get the same value on both machines (provided you send the timestamps with full precision). That’s a good approach if you have a variable delta time for the movement logic.
Keep in mind that even with great care the state between server and client will still deviate over time (e.g. due to floating-point error) so corrections always become necessary at some point.
I spotted that part of the code in the CMC, I thought I must be misunderstanding it, since unless the CMC is sending RPCs constantly, when there’s no movement there’d be a huge delta time when the next movement is committed. Perhaps it’s worth sending through movements updated through velocity and such too.
My main worry is correcting in a way that feels like the server is correcting the client constantly in a way that doesn’t feel like it’s not what the player is trying to do. You mention that there are multiple ways around this issue, what would some other ones be, if I might ask, please? You mention that it’s agood approach with variable delta time, is there a way to fix the delta time without having to enforce a fixed frame rate in Unreal Engine?
Thanks for your response. I’ve read all of the CMC docs and have combed through the code too. When I refer to physics, I mostly mean simple velocity and acceleration changes, which I know the CMC also deals with.
And I know some effort is done to ensure inputs and movements remain deterministic as can be seen in FNetworkPredicitonData_Client_Character::UpdateTimeStampAndDeltaTime() which has the following comment some lines in:
// Server uses TimeStamps to derive DeltaTime which introduces some rounding errors.
// Make sure we do the same, so MoveAutonomous uses the same inputs and is deterministic!!
But unfortunately I was struggling to understand fully how it worked, and if I could use this for my solution. If you have any advice that doesn’t involve the CMC too I’d really appreciate it! Thank you!
You got that right, it is sending moves constantly, but it’s also combining multiple moves into one when the input hasn’t changed so it’s not like they are sending every frame.
Other ways to deal with this could be to always use the same delta time for the movement. This can be a good solution if you cap at 30 or 60 fps and you can expect players to always have the processing power to reach that cap (e.g. on consoles).
Another solution would be to essentially calculate the movement logic at fixed intervals that are independent of the frame rate and use interpolation to display the results. This is very difficult to implement though and a bit overkill for kinematic movement.
Using a fixed frame rate doesn’t actually work I think because then the ingame time doesn’t relate to the real-world time anymore. With something as sensitive to timing as client prediction that would break everything.
Word of advice: unless you plan to spend many months on this project I would recommend going with a reasonable cap like 60 fps and just sending every frame with the subtracted timestamps as delta time. Things become a lot more complex past that point.
Thanks very much for the response, I super appreciate it. Yeah, as much as I would love to spend many, many months on this project, I have limited free time, and many projects I’d like to make use of. Part of me is considering dropping the money on the General Movement Component because I’d like this project to be shippable to some form of professional standard.
However, given I’m not planning the game to be supremely intensive, and that I only intend for it to be up to four players, I think I’m going to try your frame-cap suggestion and see if I can make that work first, and if it does then I’ll implement a much more lightweight version of the correction system the CMC uses, since I’ve already done a lot of the heavy lifting to set that in place.
Thank you! Your perspective is very much appreciated. I’ll have to save the much more complex dive into custom replication for another time. I’ll make a not to post again here when I’ve tried this to update any future people finding the thread.
Unreal Engine simulation is not architected with fixed time steps, and the simulation/networking is not architected based on deterministic simulation.
This is one of the weakest points of UE IMO, but it’s a very workable choice for most of the games most people build, so I can’t fault them for that. It solves the “precise location for animation without perceived position jitter” problem in the simplest way, so it has that going for it.
Apologies for asking another question, but just to clarify your suggested solution, you recommend sending an RPC every tick, correct? I’m worried about the cost of that, but I’ll certainly give it a try and profile it if so. Thank you!
Unreal combines movement updates to send them on various schedules depending on how important the object is to the viewer. Objects far away get updates more seldom.
Also, don’t think of movement updates as “sending an RPC” think of them as “posting a message” – it’s a one way, fire-and-forget, use-it-or-lose-it message. And all messages sent in a single network tick, and packed into a single network packet on the network, so the overhead doesn’t have to be all that big.
That’s why you should cap the frame rate. With an uncapped frame rate you would definitely have to resort to other methods but 30 - 60 RPCs/sec is fine especially with just 4 players.
RPCs are the only way to get the client’s inputs to the server. I assume you are talking about simulating other players to the client which is a different problem, OP was specifically asking about client prediction.
The network system already has its own network tick rate, separate from the graphics frame rate.
Actors have a target update rate, which the networking system will try to adhere to, balancing number of packets sent and size of packets.
RPCs are the only way to get the client’s inputs to the server
For replicated properties sure, but RPCs get sent immediately afaik. The only instance where that may not be the case is if they are unreliable multicast. That’s what I’m getting from a quick look at NetDriver.cpp at least, correct me if I’m wrong.
This ends up checking two configurations: a maximum send interval, and a value derived from client net rate.
Anyway: to double down on the answer to the original question up top: CharacterMovementComponent does not keep its physics deterministic, and does not keep a fixed deltaTime, and corrections are only sent and acted on when the movement position delta is “too large.”