I’ve been reading the CharacterMovementComponent class with the goal of extending some functionality of it.
I would like to get some explanation on how is the server performing the actual movement. As I understood from the code, as the autonomous clients send their inputs via ServerMove the server updates his version of the position of the character using the received inputs and if the resulting position differs from the what the client said it should be after the move, the server corrects it.
If I’m correct, the only posible way corrections can be triggered are one of the following:
- Packet loss, the client performed local moves and the server never found out.
- If something crossed the path of the character in the server without the character’s client knowledge allowing such client to perform an illegal move (through another character for instance).
- Accumulation of rounding errors and timestamp calculations.
My conclusion is that the server is not running its own simulation of the character.
So, if let’s say I have 400ms with no packet loss and I’m alone in the server running around and I do sharp turns I shouldn’t be corrected since I’m only being updated on the server based on the moves I send. Am I correct?
Thank you for taking the time to read
The Server is running the simulation of the character every frame, by calling ‘PerformMovement’ in the TickComponent function (Line 1143 in 4.18 Source).
Whenever possible, the server does this using the clients’ provided input. I believe it also uses the most-recent input it received from the player in order to keep simulating between packets (if it doesn’t, that’s what I do in my custom components and it works well). So when the client has huge lag, the client and server simulations tend to diverge a lot more which results in more corrections.
Rotation is a bit different I believe, as the Character movement component uses Control Rotation from the player controller. I believe that the client has full authority over that. As the character is just a capsule, that doesn’t really matter most of the time (and you typically want it to be super-responsive, for aiming and such like).
You are always being corrected on the client even if you don’t notice it - via ClientUpdatePositionAfterServerUpdate(). If the client receives a correction from the Server, then the corresponding saved move with that timestamp is found in the clients move list and re-simulated starting from the servers’ position - and all subsequent moves are re-simulated as well to update them. If the server isn’t receiving your packets, then the server simulation won’t move the player and you’ll constantly be snapped back to the start position.
Thanks for the answer. I tried debugging and that line never runs. I think that is for when you are the host of the listen server since it check IsLocallyControlled first.
I’ve also tried massive lag simluation with no packet loss and I’ve never been corrected to a different position. I only get snapped back when I add packet loss.
Ah my mistake. It’s definitely running the simulation every frame either way. It’s probably actually calling SimulatedTick which calls MoveAutonomous. I forget the flow…
With Lag Simulation that’s too high, it’s probably just overflowing at which point things will start to break (but this rarely if ever happens in a real world scenario anyway)
Just checked - yeah it’s simulate movement.
I’ve found SimulatedTick(DeltaTime). Is that what you mean? because I only get that one running in simulated characters in non owning clients. In my example of just one client connected it never runs.
I’m trying to find where simulation happens since that’s where I need to add some behaviour for my crouching implementation.
Odd, it’s definitely running for me on the Server (for remote clients only of course). Should be line 1171.
Just tested again and line 1171: SimulatedTick(DeltaTime) only runs on simulated clients. MoveAutonomous is only being called upon receiving a ServerMove.
Also did the ping test again with 300ms and I never got corrected to a different position.
Update after some more testing:
Line 1171 executes on the server only when set as listen server as every character actor except the listen server’s is a simulated proxy to the listen server.
Ugh I keep confusing Character Movement with my own one - so yeah for characters it doesn’t look like the server moves a client until they get an update from them. This perplexed me at first but I guess for character movement you won’t notice it so long as the connection is decent.
So what should happen is, client will call ServerMove - which runs on the Server and calls MoveAutonomous with the input provided by the client, and then calls ServerMoveHandleClientError, which determines whether or not to send corrections.
Corrections are dispatched via SendCorrections() (called from the player controller of all places, part of the INetworkPredictionInterface) - which then either acks a good move or sends the adjustment. At 300 ping acks and adjustments will likely always fail and especially at high framerates, because the move buffer will be overflowing on the client anyway (default is 96 moves) and that move you sent will have expired - so they won’t be able to find and replay subsequent moves:
// Ack move if not expired
int32 MoveIndex = ClientData->GetSavedMoveIndex(TimeStamp);
if (MoveIndex == INDEX_NONE)
UE_LOG(LogOrbMovement, Warning, TEXT("ClientAdjustPosition could not find Move for TimeStamp: %f, LastAckedTimeStamp: %f, CurrentTimeStamp: %f"), TimeStamp, ClientData->LastAckedMove->TimeStamp, ClientData->CurrentTimeStamp);
You could hard-set the position here if you wanted to, but I could have sworn however that this was handled somewhere else… if not, there’s probably a reason for it. One issue would be that any move you already sent to the server would be out of date before it even arrives.
If you turn on verbose logging for character movement, I’m betting you’ll see log-spam regarding expired moves and timestamps.
Yes, that does indeed happen but that only affects the client. I do get the moves applied on the server, problem is that if the character needed to be corrected it wouldn’t be done correctly since it can’t replay the moves.
I also tried to create my own component but couldn’t even get to compile it because of some weird PerfCounterModule.h error. I did include “PerfCounters” as a module in my Build.cs tho.
What I wanted to do was to PerformMovement on the server every tick and then correct by modifying the logic on ServerMove such that if the simulated server position differs significantly from the stated by the client it would get corrected but the movement would be actually simulated by the server every tick, not every time the server receives a move.
I’m bumping the thread with a very specific question:
The Simulated Proxies are being extrapolated (dead reckoning) or does the CMC interpolates between the last 2 known positions?
I don’t think there’s any extrapolation on the remote proxies - there’s just smoothing between the “current position” and most recently received position. I also can’t seem to include perfcounters no matter what I try, so I would just skip it!
Unfortunately calling ‘PerformMovement’ every tick on the server would be difficult to manage because there would be delta time discrepancies. If the server and client are running at even slightly different frame-rates, then the corrections will be constant. You’d have to change a fair bit of the way it works to do that.
Thanks very much for the time you are taking to help me out. I can’t figure out where it does the interpolation but I do find something relative to some kind of extrapolation going on. I did manage to include PerfCounters, only thing it was lacking was to add the dependency in the projects build.cs.
Also it’s worth noting Photon doesn’t work on consoles (that i’m aware of).
Also, do the ExitGames guys have any numbers comparing the network traffic/performance of UE4 built-in vs Photon?
I have to correct you. Photon does support all major games consoles (PS4, XB One, Switch and PS Vita) and I am aware of several teams that use Photon with UE4 on one or multiple of these consoles.
No, we don’t offer such numbers as they are pretty worthless anyway. For any halfway decent network solution one can easily set up comparisons in a way optimized for this specific solution so that it outperforms everything else. Also the numbers for an actual game are highly dependent on the specific game and numbers that are achieved artificial test cases won’t give you any hint at all about what to expect for your game. Hence we don not provide such comparisons.
Fair enough, thanks for the clarification.