Remember to unpossess the Pawn if you want to keep it after the Player leaves the game.
Disappearing cars
Have you ever wondered what happens to the Player’s Pawn when Player logs out of the multiplayer game? You are right – it disappears. That’s ok if the Pawn is Player’s character. If the physical player is no longer there, we don’t want to keep his character.
But what if the Player possessed some other Pawn during the gameplay, i.e. a car that is a team resource? When such a Player rage-quits or his game crashes, Unreal will remove his Player Controller together with the possessed Pawn. The team is now robbed of a car.
Fortunately, the fix is very easy: just unpossess the Pawn. You need to do it in a very specific place though – in Player Controller’s overridden Destroyed()
function. Here, the Engine notifies your code that Player Controller is about to be destroyed. You need to unpossess before the call to the parent, otherwise Unreal will mark the Pawn as PendingKill
and the game is over. Even if you unpossess later on, the Garbage Collector will remove the Pawn.
Investigating call stack
Now, let’s see what exactly Unreal does when Player logs out of the game unexpectedly. We will start at the bottom of the Server call stack and go up all the way to the PlayerController::Destroyed()
function:
- At the beginning, Unreal runs the game loop through
UWorld::Tick()
and notifies all his components that they should tick as well. - Among others, the
UNetDriver
andUIpNetDriver
receive the tick and update their state. TheUIpNetDriver
detects that the connection to Client is unresponsive and begins the cleanup process. - The call goes through
UIpConnection
toUNetConnection::CleanUp()
which begins destroying the connection which also includes destroying the owning Actor. - Then Unreal calls the
APlayerController::OnNetCleanup()
that begins the destroying process. If you want to do something before that, Unreal allows you to override this function. AActor::Destroy()
andUWorld::DestroyActor()
(located in LevelActor.cpp) are called. The latter does the main removal processing, marking the Player Controller asPendingKill
for the Garbage Collector.- Fortunately, in the middle of this process, we have a call to
PlayerController::Destroyed()
which we can override in our custom Player Controller. It’s here where we have last chance to unpossess the Pawn before the Player Controller will vanish. Why? - When Server goes further to the
PlayerController::Destroyed()
it executes theAPlayerController::PawnLeavingGame()
, and this function marks the possessed Pawn asPendingKill
. It’s too late to disconnect Pawn from the Controller after that – it will be destroyed by the Garbage Collector anyway.
Please note that unpossessing the Pawn using the Destroyed() function or OnDistroyed() dispatcher in BP is too late as well since there is a call at the very end in the Actor base class in AActor::Destroyed(). The PendingKill mark has already been attached to the PlayerController and Pawn at this point.
If you unpossess the Pawn at the right moment, the car stays in the game:
Final thoughts
In this article I used Epic’s City Sample project. It’s not prepared for multiplayer so I had to hack it a little.
I had to make ACitySamplePlayerController::TryToInteract() an RPC, because I need to pass the CurrentVisibleInteractionComponent to Server for the interaction to work.
I had to remove MassAgent Component in the Player Character and the car because it crashes the game on end play:
Lastly, I had to go to the BP_CitySamplePlayerCharacter -> CapsuleComponent and disable all collisions related to Vehicle:
Otherwise, I got something like this:
I ran my tests in the Listen Server mode, but this solution will work on Dedicated Servers without any issues.
I hope this little article helped you understand what happens when the Player leaves during the game.