Hi everyone!
I our multiplayer game, players can swap their controlled character instantly during gameplay. A new pawn is spawned and instantly possessed. When performing this character swap on a Listen Server as the Host, the transition is perfectly seamless. However, on remote clients, we are experiencing a 1-frame visual glitch where the camera snaps to the PlayerController’s location before correctly snapping to the new Pawn.
Debugging through the engine code, I noticed the following:
-
On the server, PlayerController::OnUnPossess sets the ViewTarget to the PlayerController itself. This fires a ClientSetViewTarget RPC to the client, which calls SetViewTarget.
-
Immediately after, OnPossess fires a ClientRestart(NewPawn) RPC, which causes another SetViewTarget with the new Pawn.
Problem is, first SetViewTarget executes instantly on the client, but the second one seems to be delayed by 1 frame, which causes the camera issue.
We have tested this both in the Editor with Listen Server and in a build with two different PCs having the same behaviour.
One thing to note is this only happens when swapping to a newly spawned pawn, not when swapping to an already spawned pawn.
So my questions:
- Is this an expected behaviour when switching to a newly spawned pawn?
- What would be the best way to fix it?
Any insights would be greatly appreciated. Thanks!
Hi,
This is expected, as I believe what you’re seeing is the client waiting for the newly created pawn to be spawned locally.
It is possible for the client to receive the ClientSetViewTarget RPC for the new pawn before that pawn has been spawned locally. In this case, APlayerController::ClientSetViewTarget_Implementation will call ServerVerifyViewTarget, since the reference to the target actor is currently null. ServerVerifyViewTarget will call ClientSetViewTarget again, and when the client receives this RPC, it will likely have finished spawning the pawn and can set the view target as normal.
If you’re testing in PIE without any network emulation, the process likely takes only a single frame. However, it’s important to note that under realistic network conditions, network latency will cause these calls to take longer to be received, and these RPCs and spawn operations can be received in different orders. We highly recommend testing with network emulation enabled to catch these kinds of situations.
In terms of how to fix it, this will depend on the needs of your project. The most straightforward approach will likely be to maintain the first pawn as the view target on the client until the new pawn has been spawned, probably by preventing the call to ClientSetViewTarget on UnPossess that sets the target to the controller.
Thanks,
Alex
Thank you for the detailed breakdown, it was very helpful.
I tried your recommendation, overriding OnUnPossess to avoid the call to ClientSetViewTarget. However, the SetPawn(nullptr) that happens later ends calling SetViewTarget(this) too, causing the same issue.
I’ve tried a different approach and I’ve overriden the PlayerController CalcCamera, making it use the old pawn ViewInfo while the new one is stil being received. This fixes the issue apparently.
Thanks again!
Hi,
Great, I’m glad you were able to find an approach that works for you! If you have any further questions, please don’t hesitate to reach out.
Thanks,
Alex