|
发表于 2023-1-17 14:16:20
|
显示全部楼层
大家好,我是iAnimer,一个入门UE4客户端开发,这篇笔记主要记录在无缝切换关卡需求中产生的相关问题
正文如下
注:引擎版本4.26
一.问题的产生
在做某个跨关卡需求时,部分逻辑功能放在PlayerControl中BeginPlay中用于初始化,在实现关卡无缝切换过程中,进入关卡后PlayerController的BeginPlay 函数并未调用?
经调式断点发现,无缝切换关卡过程中PlayerController 销毁生成分两种情况
情况一:如前后关卡中GameMode下PlayerController 配置相同时,上个关卡中PlayerController 不会进行销毁。
情况二:GameMode 下PlayerController 配置不同时,PlayerController重新生成。
笔记主要记录哪些问题?
1.如何设置跨关卡方式 为 Seamless Travel ?
2.无缝跨关卡中 CurrentMap - TransitionMap- DestinationMap?
3.Ue4 无缝切换流程Seamless Travel 函数调用流程 ?
4.无缝切换流程Seamless Travel如何实现服务器不与客户端断开连接?
5.关卡切换中,如何实现服务器将客户端带入DestinationMap?
6.切换关卡过程中,常见游戏框架类生命周期,何时GC,何时生成
7.PlayerController GameState PlayerState GameMode Character Hud 生成顺序
1.如何设置关卡方式为Seamless Travel
class AGameModeBase
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=GameMode)
uint32 bUseSeamlessTravel : 1;

GameMode
2.关卡切换中 CurrentMap(当前关卡) - TransitionMap (过渡关卡) - DestinationMap(目标关卡) ?
进行无缝关卡过渡的过程中,会产生一个临时过渡关卡为TransitionMap ,从CurrentMap先过渡到TransitionMap 中,再从TransitionMap 中到DestinationMap中。
3.切换关卡中函数调用顺序
函数调用主要如下几个过程
3.1 GetWorld()->ServerTravel()
3.2 AGameModeBase::ProcessServerTravel()
3.3 UWorld::SeamlessTravel()
3.4 SeamlessTravelHandler.StartTravel
3.5 FSeamlessTravelHandler::Tick()
3.1 GetWorld()->ServerTravel()
//FURL 为切换的关卡名
//bShouldSkipGameNotify 是否跳过通知客户端
UWorld::ServerTravel(const FString& FURL, bool bAbsolute, bool bShouldSkipGameNotify)
{
GameMode->ProcessServerTravel(FURL, bAbsolute);
}
3.2 AGameModeBase::ProcessServerTravel()
//通过RPC方式通知客户端Travel
void AGameModeBase::ProcessServerTravel(const FString& URL, bool bAbsolute)
{
//获取NextURL NextMapGuid
FURL NextURL = FURL(&WorldContext.LastURL, *URL, bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative);
FGuid NextMapGuid = UEngine::GetPackageGuid(FName(*NextURL.Map), GetWorld()->IsPlayInEditor());
// Notify clients we're switching level and give them time to receive.
FString URLMod = NextURL.ToString();
APlayerController* LocalPlayer = ProcessClientTravel(URLMod, NextMapGuid, bSeamless, bAbsolute);
if (bSeamless)
{
World->SeamlessTravel(World->NextURL, bAbsolute);
}
}
3.3 UWorld::SeamlessTravel()
void UWorld::SeamlessTravel(const FString& SeamlessTravelURL, bool bAbsolute, FGuid MapPackageGuid)
{
FSeamlessTravelHandler &SeamlessTravelHandler = GEngine->SeamlessTravelHandlerForWorld( this );
SeamlessTravelHandler.StartTravel(this, NewURL, MapPackageGuid)
}
3.4 FSeamlessTravelHandler::StartTravel()
//修改 PendingTravelURL / bTransitionInProgress /CurrentWorld 创建临时Dummy world
//bTransitionInProgress = true 后 进入TickWorldTravel 中 Context.SeamlessTravelHandler.Tick();
bool FSeamlessTravelHandler::StartTravel(UWorld* InCurrentWorld, const FURL& InURL, const FGuid& InGuid)
{
//设置CurrentWorld
CurrentWorld = InCurrentWorld;
//设置PendingTravelURL Guid
PendingTravelURL = InURL;
PendingTravelGuid = InGuid;
//bTransitionInProgress = true 当前为关卡过渡
bSwitchedToDefaultMap = false;
bTransitionInProgress = true;
//通过GameMapsSettings 获取是否设置过渡关卡
FString TransitionMap = GetDefault<UGameMapsSettings>()->TransitionMap.GetLongPackageName();
//如果未设置生成一个dummy world,将临时生成dummy world,
// LoadedWorld = dummy world
SetHandlerLoadedData(NULL, UWorld::CreateWorld(EWorldType::None, false));
}
3.5 FSeamlessTravelHandler::Tick()
UEngine::TickWorldTravel()
void UEngine::TickWorldTravel(FWorldContext& Context, float DeltaSeconds)
{
//Tick 每帧进行检测
if (Context.SeamlessTravelHandler.IsInTransition())
{
//StartTravel 中 设置bTransitionInProgress = true
// Note: SeamlessTravelHandler.Tick may automatically update Context.World and GWorld internally
Context.SeamlessTravelHandler.Tick();
}
}
UWorld* FSeamlessTravelHandler::Tick()
主要处理 CurrentMap- TransitionMap 与 TransitionMap -DestinationMap 两个阶段
第一阶段 CurrentMap- TransitionMap过程
UWorld* FSeamlessTravelHandler::Tick()
{
//第一阶段 CurrentMap - TransMap
//在StartTravel中 设置LoadedWorld 为临时创建的dummyWorld
//第二阶段 TransMap - DesMap
//在SeamlessTravelLoadCallback 回调函数中设置 LoadedWorld
if ( ( LoadedPackage != nullptr || LoadedWorld != nullptr ))
{
//服务器保留Actor方法
//CurrentMap - TransMap 中//保留GameMode GameState PlayerState GameSession
AuthGameMode->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
//客户端保留Actor方法
//CurrentMap - TransMap 中保留 Hud 与PlayerCameraManager
It->PlayerController->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
//在CurrentMap - TransMap过程中,拷贝当前的GameMode 与GameState 至LoadedWorld
LoadedWorld->CopyGameState(KeptGameMode, KeptGameState);
//拷贝CurrentWorld中的netDriver 到LoadedWorld 中
//SeamlessTravel 服务器与客户端不会断开
CopyWorldData();
//接下来进行垃圾回收
CurrentWorld->RemoveFromRoot();
//将LoadedWorld AddToRoot防止垃圾回收
LoadedWorld->AddToRoot();
//设置当前world为LoadedWorld
CurrentContext.SetCurrentWorld(LoadedWorld);
//进行垃圾回收
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS, true );
//第一阶段中 CurrentMap - TransMap
bSwitchedToDefaultMap = true;
CurrentWorld = LoadedWorld;
//在StartLoadingDestination()方法中设置加载完成回调
FLoadPackageAsyncDelegate::CreateRaw(this, &FSeamlessTravelHandler::SeamlessTravelLoadCallback)
//将LoadedPackage 与 LoadedWorld 置为Nullptr
LoadedPackage = nullptr;
LoadedWorld = nullptr;
//Tick中每次检测LoadedWorld ,
//直至在DesMap加载完成回调函数中SeamlessTravelLoadCallback 设置 LoadedWorld
}
}
第二阶段 TransitionMap- DesMap过程
该过程发生在SeamlessTravelLoadCallback后
此时CurrentWorld为TransitionMap
UWorld* FSeamlessTravelHandler::Tick()
{
//在TransMap中到DesMap 过程中
//在TransMap - DesMap 过程中 KeepActors 仅记录GameState下保存的PlayerState
AuthGameMode->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
//即TransMap - DesMap 过程中 客户端保留Actor不发生变化
//如不进行重写,默认为Hud 与PlayerCameraManager
It->PlayerController->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors)
for (AActor* const TheActor : ActuallyKeptActors)
{
//会看到如下LoadedWorld添加被保留的PlayerControl
LoadedWorld->AddController(static_cast<AController*>(TheActor));
//在GameState 与 GameMode在TransMap过程中不进行保留
KeptGameMode = static_cast<AGameModeBase*>(TheActor);
KeptGameState = static_cast<AGameStateBase*>(TheActor);
//CurrentMap- TransMap 与 TransMap到DesMap过程中 不同的是
//GameState 与GameMode 在CurrentMap到TransMap过程中保留,并通过CopyGameState方法
//进行GameState 与GameMode拷贝
if (KeptGameMode)
{
LoadedWorld->CopyGameState(KeptGameMode, KeptGameState);
bCreateNewGameMode = false;
}
}
//该过程两个阶段不会发生变化,仍然是拷贝NetDriver
CopyWorldData();
//垃圾回收部分
CurrentWorld->RemoveFromRoot();
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS, true );
//在InitWorld方法中
//初始化Subsystems /CreatePhysicsScene/CreateAISystem /InitializeRenderingResources ...
LoadedWorld->InitWorld();
//如GameMode未进行保留则重新创建GameMode
if (bCreateNewGameMode)
{
LoadedWorld->SetGameMode(PendingTravelURL);
}
}
4.无缝切换流程Seamless Travel如何实现服务器不与客户端断开连接?
在上述跨关卡流程中 UWorld* FSeamlessTravelHandler::Tick() 中 CopyWorldData() 进行NetDriver拷贝
5.关卡切换中,如何实现服务器将客户端带入DestinationMap?
通过RPC方式通知客户端调用 SeamlessTravel
void AGameModeBase::ProcessServerTravel(const FString& URL, bool bAbsolute)
{
APlayerController* LocalPlayer = ProcessClientTravel(URLMod, NextMapGuid, bSeamless, bAbsolute);
}
通过RPC使客户端进行关卡切换 ,服务器 存在一个 客户端的UNetConnection 对象
//通关判断PlayerController 下Player,判断当前为Remote 或localplayer
//关于PlayerController 下的Player
//UPlayer associated with this PlayerController. Could be a local player or a net connection. */
APlayerController* AGameModeBase::ProcessClientTravel(FString& FURL, FGuid NextMapGuid, bool bSeamless, bool bAbsolute)
{
if (APlayerController* PlayerController = Iterator->Get())
{
if (Cast<UNetConnection>(PlayerController->Player) != nullptr)
{
// Remote player
PlayerController->ClientTravel(FURL, TRAVEL_Relative, bSeamless, NextMapGuid);
}
else
{
// Local player
LocalPlayerController = PlayerController;
PlayerController->PreClientTravel(FURL, bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative, bSeamless);
}
}
}
客户端进行调用ClientTravelInternal
void APlayerController::ClientTravel(const FString& URL, ETravelType TravelType, bool bSeamless, FGuid MapPackageGuid)
{
ClientTravelInternal(URL, TravelType, bSeamless, MapPackageGuid);
}
void APlayerController::ClientTravelInternal_Implementation(const FString& URL, ETravelType TravelType, bool bSeamless, FGuid MapPackageGuid)
{
UWorld* World = GetWorld();
// Warn the client.
PreClientTravel(URL, TravelType, bSeamless);
if (bSeamless && TravelType == TRAVEL_Relative)
{
World->SeamlessTravel(URL);
}
}
二.切换关卡过程中,常见游戏框架类生命周期,何时GC,何时生成,生成顺序等等?
1.PlayerController
PlayerController 在CurrentMap - TransitionMap 过程中被保留
CurrentMap - TransitionMap 过程中通过GameMode下的PlayerController进行判断
如果不同,主动Destory当前PlayerController,重新生成PlayerController
相同,则PlayerController保留至下一个关卡中
//Current - TransMap 过程中 保留至ActuallyKeptActors 列表中
//TransMap - 过程中会PC->GetClass() != PCClassToSpawn 判断,前后Map中的GameMode下PlayerController是否相同
//相同则不创建,不相同则旧PlayerControl销毁,生成新的PlayerController
UWorld* FSeamlessTravelHandler::Tick()
{
for( FConstControllerIterator Iterator = CurrentWorld->GetControllerIterator(); Iterator; ++Iterator )
{
KeepAnnotation.Set(Player);
}
for (FConstPlayerControllerIterator Iterator = CurrentWorld->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
if (APlayerController* Player = Iterator->Get())
{
ProcessActor(Player);
}
}
}
void AGameMode::HandleSeamlessTravelPlayer(AController*& C)
{
//判断前后关卡GameMode下的PlayerController是否相同
//如不同,则重新创建
if (PC && PC->GetClass() != PCClassToSpawn)
{
APlayerController* const NewPC = SpawnPlayerControllerCommon(PC->IsLocalPlayerController() ? ROLE_SimulatedProxy : ROLE_AutonomousProxy, PC->GetFocalLocation(), PC->GetControlRotation(), PCClassToSpawn);
PC->SeamlessTravelTo(NewPC);
NewPC->SeamlessTravelFrom(PC);
SwapPlayerControllers(PC, NewPC);
PC = NewPC;
C = NewPC;
}
//CurrentMap与DesMap GameMode 下关卡不同时
//该过程不进行PlayerController 重新生成
else
{
//该过程将PlayerState 进行Reset
//首先保留一份OldPlayerState
C->PlayerState->Reset();
// create a new PlayerState and copy over info; this is necessary because the old GameMode may have used a different PlayerState class
//创建一个新的PlayerState 进行函数重写进行数据拷贝
APlayerState* OldPlayerState = C->PlayerState;
C->InitPlayerState()
OldPlayerState->Destroy();
}
}
void AController::InitPlayerState()
{
//创建PlayerState
PlayerState = World->SpawnActor<APlayerState>(PlayerStateClassToSpawn, SpawnInfo);
}
2.PlayerState 生成/销毁
CurrentMap - TransMap 中过程中 保留Actor方法进行PlayerState 保留
TransMap - DesMap 中,主动销毁并重新创建PlayerState
//PlayerState
//Current - TransMap 与TransMap - DesMap PlayerState 通过 GetSeamlessTravelActorList 保留
//Current - TransMap 过程中 PlayerState保留
//TransMap - DesMap 过程中 PlayerState 主动进行Destory销毁并重新创建
UWorld* FSeamlessTravelHandler::Tick()
{
AuthGameMode->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
//仅在TransMap - DesMap 过程中执行以下逻辑
if (bSwitchedToDefaultMap)
{
AGameModeBase* const GameMode = LoadedWorld->GetAuthGameMode();
if (GameMode)
{
// inform the new GameMode so it can handle players that persisted
GameMode->PostSeamlessTravel();
}
}
}
//通过GameState保留
// 这里可能有一个疑问GameState在Trans - DesMap 过程会被垃圾回收处理,如何将PlayerState保留
// 调用顺序为 先调用GetSeamlessTravelActorList 先PlayerState加入后保留Actor列表
// 后调用 CollectGarbage ,GameState被GC
void AGameModeBase::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
{
ActorList.Append(GameState->PlayerArray);
}
//通过PostSeamlessTravel 进行销毁并重新生成
void AGameModeBase::PostSeamlessTravel()
{
for (auto It = GetWorld()->GetControllerIterator(); It; ++It)
{
OldControllerList.Add(It->Get());
}
HandleSeamlessTravelPlayer(Controller);
}
void AGameMode::HandleSeamlessTravelPlayer(AController*& C)
{
if(PC && PC->GetClass() != PCClassToSpawn)
{
NewPC->SeamlessTravelFrom(PC);
}
else
{
C->InitPlayerState();
OldPlayerState->Destroy();
}
}
void APlayerController::SeamlessTravelFrom(APlayerController* OldPC)
{
OldPC->PlayerState->Destroy();
OldPC->PlayerState = NULL;
}
3.GameMode 生成/销毁
GameMode 生成与销毁主要与GetSeamlessTravelActorList 方法有关
默认情况下Current -TransMap 该过程GameMode保留,并CopyGameState 拷贝至TransMap 中
TransMap - DesMap过程中,GameMode 不进行保留,被GC销毁,销毁后重新创建GameMode
//CurrentMap - TransMap
//GetSeamlessTravelActorList 保留至LoadedWorld ,并设置LoadedWorld 中的GameState 与GameMode
//该过程GameMode 不会销毁
//关于GameMode生成与销毁主要受如下
//在GetSeamlessTravelActorList 中是否将GameMode 保留
//如GameMode保留,则GameMode不重新创建
//在默认情况下 CurrentMap - TransMap 过程中进行GameMode保留仅CopyGameState
//TransMap - DesMap 过程中 GameMode不进行保留 ,通过TheActor->IsA<AGameModeBase>() 方法判断
//当GameMode为空后,bCreateNewGameMode True, 后重新创建GameMode
UWorld* FSeamlessTravelHandler::Tick()
{
AuthGameMode->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
bool bCreateNewGameMode = !bIsClient;
for (AActor* const TheActor : ActuallyKeptActors)
{
else if (TheActor->IsA<AGameModeBase>())
{
KeptGameMode = static_cast<AGameModeBase*>(TheActor);
}
}
if (KeptGameMode)
{
LoadedWorld->CopyGameState(KeptGameMode, KeptGameState);
bCreateNewGameMode = false;
}
if (bCreateNewGameMode)
{
LoadedWorld->SetGameMode(PendingTravelURL);
}
}
4.GameState 生成/销毁
CurrentMap - TransMap 过程中,通过GetSeamlessTravelActorList 方法保留 并调用CopyGameState
TransMap - DesMap 过程中 ,GameState 被GC后,重新进行Spawn Actor
//CurrentMap - TransMap 过程中
//通过该方法进行保留至TransMap中
AuthGameMode->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
//TransMap - DesMap过程中
//当发起GC的过程中
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS, true );
GameState类进行销毁
//Trans -DesMap 中 GameState 生成过程
//UWorld* FSeamlessTravelHandler::Tick()中
LoadedWorld->InitializeActorsForPlay(PendingTravelURL, false);
Level->RouteActorInitialize();
void AGameModeBase::PreInitializeComponents()
{
GameState = World->SpawnActor<AGameStateBase>(GameStateClass, SpawnInfo);
}
5.HUD 生成/销毁
CurrentMap - TransMap 过程中 通过PlayerController 下GetSeamlessTravelActorList 保留至TransMap
如不想保留至TransMap 可对GetSeamlessTravelActorList 进行重写,该方法主要处理客户端的Actor 保留
在TransMap - CurrentMap 过程中 ,Hud主动销毁,并重新创建
//CurrentMap - TransMap 过程中
//在默认情况,HUD 会被保留带入TransMap
It->PlayerController->GetSeamlessTravelActorList(!bSwitchedToDefaultMap, KeepActors);
//第二阶段 TransMap - DesMap 过程中
//函数调用顺序
void AGameMode::PostSeamlessTravel()
void AGameMode::HandleSeamlessTravelPlayer(AController*& C)
virtual void GenericPlayerInitialization(AController* C);
void InitializeHUDForPlayer(APlayerController* NewPlayer);
void ClientSetHUD(TSubclassOf<AHUD> NewHUDClass);
//在PlayerController 中ClientSetHUD 实现可以看到如下
void APlayerController::ClientSetHUD_Implementation(TSubclassOf<AHUD> NewHUDClass)
{
//Hud 在TransMap 保留至DesMap
//在 GetSeamlessTravelActorList方法中,是否保留至DesMap中
//Hud都会被销毁,重新生成Hud类
if ( MyHUD != NULL )
{
MyHUD->Destroy();
MyHUD = NULL;
}
FActorSpawnParameters SpawnInfo;
SpawnInfo.Owner = this;
SpawnInfo.Instigator = GetInstigator();
SpawnInfo.ObjectFlags |= RF_Transient; // We never want to save HUDs into a map
MyHUD = GetWorld()->SpawnActor<AHUD>(NewHUDClass, SpawnInfo );
}
6.PlayerCharacter 生成/销毁
PlayerCharacter 在CurrentMap - TransMap 过程中 被GC 销毁
在TransMap - DesMap 过程中 在生成HUD后生成PlayerCharacter
//Current - TransMap
//PlayerCharacter 被GC 回收
//TransMap - DesMap 重新生成
void AGameMode::HandleSeamlessTravelPlayer(AController*& C)
{
//首先先生成Hud
GenericPlayerInitialization(C)
//生成CharacterPlayer
HandleStartingNewPlayer(PC);
}
void AGameModeBase::RestartPlayerAtPlayerStart(AController* NewPlayer, AActor* StartSpot)
{
NewPlayer->SetPawn(SpawnDefaultPawnFor(NewPlayer, StartSpot));
}
7.AGameSession 生成与销毁
第一阶段 CurrentMap - TransMap 中
//通过AuthGameMode->GetSeamlessTravelActorList 保留至TransMap中
第二阶段 TransMap - DesMap 过程中 在垃圾回收后进行销毁并重新创建
//GameSession 类创建
//在FSeamlessTravelHandler::Tick()
LoadedWorld->InitializeActorsForPlay(PendingTravelURL, false);
AuthorityGameMode->InitGame
//GameSession Spawn过程
void AGameModeBase::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
GameSession = World->SpawnActor<AGameSession>(GetGameSessionClass(), SpawnInfo);
}
8.GameInstance
仅拷贝GameInstance指针
void FSeamlessTravelHandler::CopyWorldData()
{
LoadedWorld->SetGameInstance(CurrentWorld->GetGameInstance());
}
9.PlayerController GameState PlayerState GameMode Character Hud 默认生成顺序?
注: TransMap - DesMap过程
按照函数调用顺序GameMode GameState PLayerController PlayerState HUD PlayerCharacter 依次生成
//相关类生成顺序
UWorld* FSeamlessTravelHandler::Tick()
{
//GameMode 生成
if (bCreateNewGameMode)
{
LoadedWorld->SetGameMode(PendingTravelURL);
}
//GameState 生成
LoadedWorld->InitializeActorsForPlay(PendingTravelURL, false);
//生成PlayerController PlayerState HUD PlayerCharacter
GameMode->PostSeamlessTravel();
}
三.参考文档:
UE4 Server无缝地图切换_南京周润发
流关卡与无缝地图切换_Jerish
多人游戏中的关卡切换 | 虚幻引擎文档 (unrealengine.com) |
|