Posted onInUnrealEngineViews: Symbols count in article: 2.1kReading time ≈5 mins.
Because Unreal Engine destroys all objects in the current level when switching levels (OpenLevel), we often need to retain certain objects for the next level. Today, I read some relevant code, and this article will explain how to achieve this. Unreal’s documentation does mention this (Travelling in Multiplayer), and it’s not too complicated to implement. However, the UE documentation is consistently lacking in detail, especially in Chinese, where resources are very scarce (mostly machine-translated and outdated). I couldn’t find any reliable information during my search, so I took some notes while reading the code implementation.
UE provides these functionalities in C++. You need to enable bUseSeamlessTravel=true in GameMode, and then use GetSeamlessTravelActorList to obtain the list of Actors to be saved. However, please note that directly using UGameplayStatics::OpenLevel will not work because OpenLevel calls GEngine->SetClientTravel(World,*Cmd,TravelType), so it won’t execute AGameMode::GetSeamlessTravelActorList to get the Actors that need to persist to the next level. In the UE documentation’s Travelling in Multiplayer, under Persisting Actors across Seamless Travel, it mentions that only ServerOnly GameModes will call AGameMode::GetSeamlessTravelActorList, so you should use UWorld::ServerTravel for level switching. However, UE does not expose UWorld::ServerTravel to Blueprints, so I added a wrapping function exposed to Blueprints in the test code called ACppGameMode::Z_ServerTravel, and similarly, there is a wrapping function ACppGameMode::GetSaveToNextLevelActors for AGameMode::GetSeamlessTravelActorList.
After reading the code for UWorld::ServerTravel, its call stack is:
// always keep Controllers that belong to players if (bIsClient) { for (FLocalPlayerIterator It(GEngine, CurrentWorld); It; ++It) { if (It->PlayerController != nullptr) { KeepAnnotation.Set(It->PlayerController); } } } else { for( FConstControllerIterator Iterator = CurrentWorld->GetControllerIterator(); Iterator; ++Iterator ) { AController* Player = Iterator->Get(); if (Player->PlayerState || Cast<APlayerController>(Player) != nullptr) { KeepAnnotation.Set(Player); } } }
// ask players what else we should keep for (FLocalPlayerIterator It(GEngine, CurrentWorld); It; ++It) { if (It->PlayerController != nullptr) { It->PlayerController->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors); } } // mark all valid actors specified for (AActor* KeepActor : KeepActors) { if (KeepActor != nullptr) { KeepAnnotation.Set(KeepActor); } }
// Rename dynamic actors in the old world's PersistentLevel that we want to keep into the new world auto ProcessActor = [this, &KeepAnnotation, &ActuallyKeptActors, NetDriver](AActor* TheActor) -> bool { const FNetworkObjectInfo* NetworkObjectInfo = NetDriver ? NetDriver->GetNetworkObjectInfo(TheActor) : nullptr;
// Keep if it's in the current level AND it isn't specifically excluded AND it was either marked as should keep OR we don't own this actor if (bIsInCurrentLevel && !bForceExcludeActor && (bManuallyMarkedKeep || bKeepNonOwnedActor)) { ActuallyKeptActors.Add(TheActor); returntrue; } else { if (bManuallyMarkedKeep) { UE_LOG(LogWorld, Warning, TEXT("Actor '%s' was indicated to be kept but exists in level '%s', not the persistent level. Actor will not travel."), *TheActor->GetName(), *TheActor->GetLevel()->GetOutermost()->GetName()); }
// otherwise, set to be deleted KeepAnnotation.Clear(TheActor); // close any channels for this actor if (NetDriver != nullptr) { NetDriver->NotifyActorLevelUnloaded(TheActor); } returnfalse; } };
Then you can override the GetSaveToNextLevelActors function in the Blueprint inheriting from ACppGameMode to specify which Actors can be retained for the next level. Make sure to select the GameMode that inherits and implements GetSaveToNextLevelActors in the original level (the level to switch from).
Finally, you can use Z_ServerTravel in Blueprints to replace OpenLevel for level switching (as shown in the image above), thus achieving the transfer of Actors to the target level when switching levels in UE.