I looked over the Unreal Engine source code.
The APlayerState::CopyProperties
function is also called when APlayerState::Duplicate
is called, even if seamless travel is not being performed.
And the APlayerState::Duplicate
function is called in the AGameMode::AddInactivePlayer
function and is called by the AGameMode::Logout function
when PIE window is closing. AddInactivePlayer
function is only implemented in AGameMode
, not inherited by AGameModeBase
. So this is why the problem occurs using the AGameMode
class.
AddInactivePlayer
calls APlayerState::Duplicate
if the Player State is not from previous level, Game Mode is not must spectate and World is not tearing down.
// Unreal Engine Player State Source Code
void AGameMode::AddInactivePlayer(APlayerState* PlayerState, APlayerController* PC)
{
check(PlayerState)
UWorld* LocalWorld = GetWorld();
// don't store if it's an old PlayerState from the previous level or if it's a spectator... or if we are shutting down
if (!PlayerState->IsFromPreviousLevel() && !MustSpectate(PC) && !LocalWorld->bIsTearingDown)
{
APlayerState* const NewPlayerState = PlayerState->Duplicate();
When I tested it, bIsTearingDown was false. So I think the problem is that order is twisted. The BeginTearingDown
function of UWorld function must be called before calling game mode Logout
function, but currently, it is the opposite.
When we are closing PIE window, UEditorEngine::EndPlayMap
function is called. This function calls UEditorEngine::TeardownPlaySession
, which in turn calls UWorld::BeginTearingDown
. However UEditorEngine::EndPlayMap
also calls UEngine::CleanupGameViewport
before calling UEditorEngine::TeardownPlaySession
. UEngine::CleanupGameViewport
calls UGameInstance::CleanupGameViewport
, which calls UGameInstance::RemoveLocalPlayer
, which calls APlayerController::Destory
. Finally AController::Destroyed
is called, and AGameMode::Logout
is called.
// UEditorEngine::EndPlayMap function (Unreal Engine Source Code)
// let the editor know
FEditorDelegates::EndPIE.Broadcast(bIsSimulatingInEditor);
// clean up any previous Play From Here sessions
if ( GameViewport != NULL && GameViewport->Viewport != NULL )
{
// Remove debugger commands handler binding
GameViewport->OnGameViewportInputKey().Unbind();
// Remove close handler binding
GameViewport->OnCloseRequested().Remove(ViewportCloseRequestedDelegateHandle);
GameViewport->CloseRequested(GameViewport->Viewport);
}
CleanupGameViewport();
// Clean up each world individually
TArray<FName> OnlineIdentifiers;
TArray<UWorld*> WorldsBeingCleanedUp;
bool bSeamlessTravelActive = false;
for (int32 WorldIdx = WorldList.Num()-1; WorldIdx >= 0; --WorldIdx)
{
FWorldContext &ThisContext = WorldList[WorldIdx];
if (ThisContext.WorldType == EWorldType::PIE)
{
if (ThisContext.World())
{
WorldsBeingCleanedUp.Add(ThisContext.World());
}
if (ThisContext.SeamlessTravelHandler.IsInTransition())
{
bSeamlessTravelActive = true;
}
if (ThisContext.World())
{
TeardownPlaySession(ThisContext);
ShutdownWorldNetDriver(ThisContext.World());
Let’s look at the UGameEngine
source code instead of UEditorEngine
for PIE. The UGameEngine::PreExit
function, called when we close game, calls BeginTearingDown
function first and then Player Controller is destroyed with other actors and shuts down game instance.
// UGameEngine::PreExit function (Unreal Engine Source Code)
// Clean up all worlds
for (int32 WorldIndex = 0; WorldIndex < WorldList.Num(); ++WorldIndex)
{
UWorld* const World = WorldList[WorldIndex].World();
if ( World != NULL )
{
World->BeginTearingDown();
// Cancel any pending connection to a server
CancelPending(World);
// Shut down any existing game connections
ShutdownWorldNetDriver(World);
for (FActorIterator ActorIt(World); ActorIt; ++ActorIt)
{
ActorIt->RouteEndPlay(EEndPlayReason::Quit);
}
if (World->GetGameInstance() != nullptr)
{
World->GetGameInstance()->Shutdown();
}
World->FlushLevelStreaming(EFlushLevelStreamingType::Visibility);
World->CleanupWorld();
}
}
So maybe, if it is not PIE, it doesn’t work like that.
Who do you help to make sure it is bug?
If it is bug, how do I send a bug report to the Unreal Engine developer?