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?