本文章只是我个人在学习虚幻引擎过程中的一些理解,不一定正确,若有说的不对的地方,欢迎指正。
前面我们介绍了引擎的执行流程和各个阶段(预初始化PreInit、初始化Init、循环Tick、退出Exit)函数调用的情况,但我们好像还没看见我们熟悉的BeginPlay是在哪里被调用的。其实所有BeginPlay的源头就是我们上一篇没有进一步介绍的UGameEngine::Start函数,本篇会从Start函数出发详细介绍这其中的调用过程。
一.从Start到LoadMap:
这部分很简单,从UGameEngine::Start函数开始到最终调用的LoadMap函数,中间经过UGameInstance::StartGameInstance、Engine::Browse,这些函数的代码都比较短,都可以看做是转发到下一层,简略源码如下:
//Init/UGameEngine::Start(非编辑器模式)
void UGameEngine::Start()
{
//转发到GameInstance
GameInstance->StartGameInstance();
}
//Init/Start/StartGameInstance
void UGameInstance::StartGameInstance()
{
//获取引擎实例
UEngine* const Engine = GetEngine();
//……
//调用Browse函数
Engine->Browse(*WorldContext, URL, Error);
//……
}
//Init/Start/StartGameInstance/Browse
EBrowseReturnVal::Type UEngine::Browse(FWorldContext& WorldContext, FURL URL, FString& Error)
{
//……
//调用LoadMap函数接在地图
return LoadMap(WorldContext, URL, NULL, Error);
}
二.LoadMap:
LoadMap函数主要用来卸载旧的World和该World的相关资源,之后会从磁盘找到新World的Package,从中加载和初始化World并把新World加入根集,加载新World所需Actor、初始化导航网格、AI,最后调用World的BeginPlay,详细解释已经写在代码注释中,简略源码如下:
bool UEngine::LoadMap(FWorldContext& WorldContext, FURL URL, class UPendingNetGame* Pending, FString& Error)
{
//……
//标记关卡流正常
WorldContext.World()->bIsLevelStreamingFrozen = false;
//广播一个代理,指示地图即将要修改
FCoreUObjectDelegates::PreLoadMap.Broadcast(URL.Map);
//……
//取消所有Texture的加载请求,以达到加快加载地图的延时
UTexture2D::CancelPendingTextureStreaming();
//……
//清除即将离开的地图所需的正在加载的资源包
CleanupPackagesToFullyLoad(WorldContext, FULLYLOAD_Map, WorldContext.World()->PersistentLevel->GetOutermost()->GetName());
//清除正在退出的资源包
CleanupPackagesToFullyLoad(WorldContext, FULLYLOAD_Game_PreLoadClass, TEXT(""));
CleanupPackagesToFullyLoad(WorldContext, FULLYLOAD_Game_PostLoadClass, TEXT(""));
CleanupPackagesToFullyLoad(WorldContext, FULLYLOAD_Mutator, TEXT(""));
//在地图改变之前刷新异步加载,避免加载过程中对地图的引用为空的问题,一般在垃圾回收之后会隐式的在此刷新异步加载
FlushAsyncLoading();
CancelPendingMapChange(WorldContext);
WorldContext.SeamlessTravelHandler.CancelTravel();
//……
//开始卸载当前的World
if (WorldContext.World())
{
WorldContext.World()->BeginTearingDown();
//清除当前World的网络
ShutdownWorldNetDriver(WorldContext.World());
//清除关卡流请求
WorldContext.World()->FlushLevelStreaming(EFlushLevelStreamingType::Visibility);
//广播一个代理,通知该World被清除
FWorldDelegates::LevelRemovedFromWorld.Broadcast(nullptr, WorldContext.World());
//销毁玩家(Player)和控制器(Controller)的连接
if (WorldContext.OwningGameInstance != nullptr)
{
for (auto It = WorldContext.OwningGameInstance->GetLocalPlayerIterator(); It; ++It)
{
ULocalPlayer* Player = *It;
if (Player->PlayerController && Player->PlayerController->GetWorld() == WorldContext.World())
{
//销毁游戏角色(PlayerPawn)
WorldContext.World()->DestroyActor(Player->PlayerController->GetPawn(), true);
//销毁角色控制器(PlayerController)
WorldContext.World()->DestroyActor(Player->PlayerController, true);
}
//设置未加入小地图标记
Player->bSentSplitJoin = false;
//清除一些Actor附带的东西,方便我们干净的清除地图
Player->CleanupViewState();
}
}
//调用World中的Actor的被清除的事件
for (FActorIterator ActorIt(WorldContext.World()); ActorIt; ++ActorIt)
{
ActorIt->RouteEndPlay(EEndPlayReason::LevelTransition);
}
//调用世界将被清除的事件,防止在清除期间生成Actor
WorldContext.World()->CleanupWorld();
if (GEngine)
{
//清除对将清除World属性的引用
ClearDebugDisplayProperties();
//从引擎中清除该World
GEngine->WorldDestroyed(WorldContext.World());
}
//从存对象根集(object root set)中移除该World
WorldContext.World()->RemoveFromRoot();
//为World中所有已加载的Level设置清除标记
for (auto LevelIt(WorldContext.World()->GetLevelIterator()); LevelIt; ++LevelIt)
{
const ULevel* Level = *LevelIt;
CastChecked<UWorld>(Level->GetOuter())->MarkObjectsPendingKill();
}
//为World关卡流中还未加载的Level设置清除标记
for (ULevelStreaming* LevelStreaming : WorldContext.World()->GetStreamingLevels())
{
CastChecked<UWorld>(LevelStreaming->GetLoadedLevel()->GetOuter())->MarkObjectsPendingKill();
}
//清除所有音效对World的引用
if (FAudioDevice * AudioDevice = WorldContext.World()->GetAudioDeviceRaw())
{
AudioDevice->Flush(WorldContext.World());
AudioDevice->SetTransientMasterVolume(1.0f);
}
//……
}
//……
//通知新World的GameInstance,在World被加载之前是否需要加载别的资源
WorldContext.OwningGameInstance->PreloadContentForURL(URL);
//……
// Normal map loading
if (NewWorld == NULL)
{
//设置新World类型
const FName URLMapFName = FName(*URL.Map);
UWorld::WorldTypePreLoadMap.FindOrAdd(URLMapFName) = WorldContext.WorldType;
//如果该World的Package还没被加载到内存,从磁盘中加载该World的Package到内存
//注意:一般World会以后缀为.umap的文件存储在磁盘
WorldPackage = FindPackage(nullptr, *URL.Map);
//如果该World的Package已经在内存,从内存中找到该Package
WorldPackage = LoadPackage(nullptr, *URL.Map, (WorldContext.WorldType == EWorldType::PIE ? LOAD_PackageForPIE : LOAD_None));
//……
//从该Package中读取World
NewWorld = UWorld::FindWorldInPackage(WorldPackage);
//此处省略根据新World类型处理该新World的代码……
}
//设置新World的GameInstance
NewWorld->SetGameInstance(WorldContext.OwningGameInstance);
//设置全局World变量
GWorld = NewWorld;
//设置WorldContext的当前World(CurWorld)为新World
WorldContext.SetCurrentWorld(NewWorld);
//设置World类型
WorldContext.World()->WorldType = WorldContext.WorldType;
//……
if (WorldContext.WorldType == EWorldType::PIE)
{
//如果是PIE Context的时候需要设置新World的PackageFlags为RF_Standalone
WorldContext.World()->ClearFlags(RF_Standalone);
}
else
{
//如果是Game Context的时候,把新World添加到根集,防止被垃圾回收(GC)
WorldContext.World()->AddToRoot();
}
//初始化新World
WorldContext.World()->InitWorld();
//根据需要处理一些Level
MovePendingLevel(WorldContext);
//初始化新World的GameMode
WorldContext.World()->SetGameMode(URL);
//……
//监听客户端
WorldContext.World()->Listen(URL);
//加载地图Package
LoadPackagesFully(WorldContext.World(), FULLYLOAD_Mutator, Mutators[MutatorIndex]);
//加载地图Package
LoadPackagesFully(WorldContext.World(), FULLYLOAD_Map, WorldContext.World()->PersistentLevel->GetOutermost()->GetName());
//刷新关卡流,确保各子关卡已经被鸡杂
WorldContext.World()->FlushLevelStreaming(EFlushLevelStreamingType::Visibility);
//为新World创建AI系统
WorldContext.World()->CreateAISystem();
//加载并初始化World中所以的Actor
WorldContext.World()->InitializeActorsForPlay(URL, true, &Context);
//为World添加导航网格
FNavigationSystem::AddNavigationSystemToWorld(*WorldContext.World(), FNavigationSystemRunMode::GameMode);
//……
//到此我们已经有了许多Actor,当卸载才会为本地玩家生成GamePlay框架的一些类的实例
for (auto It = WorldContext.OwningGameInstance->GetLocalPlayerIterator(); It; ++It)
{
(*It)->SpawnPlayActor(URL.ToString(1), Error2, WorldContext.World())
}
//触发World的BeginPlay函数,这是引擎所以BeginPlay的源头,会在后面展开讲
WorldContext.World()->BeginPlay();
//……
}
接下来要详细展开的是LoadMap中的几个函数:InitializeActorsForPlay、BeginPlay。BeginPlay是触发所有我们开发中常见的开始事件的地方,而在开始事件被调用之前,我们需要先让World中的Actor们动起来,因此我们先调用InitializeActorsForPlay。
1.InitializeActorsForPlay:
第一步让世界动起来,InitializeActorsForPlay可以理解成让World中的Actor们活过来。它主要作用是注册并初始化每个Actor和每个Actor的所有Component,设置World的GameSession,通知一些模块初始化,详细解释在源码中:
void UWorld::InitializeActorsForPlay(const FURL& InURL, bool bResetTime, FRegisterComponentContext* Context)
{
//……
//更新World中的所有组件(Component)
UpdateWorldComponents(bRerunConstructionScript, true, Context);
//初始化World的GamePlay信息
if (!AreActorsInitialized())
{
//……
//初始化需要网络同步的Actor
for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); LevelIndex++)
{
Level->InitializeNetworkActors();
}
//标记Actors已经初始化
bActorsInitialized = true;
//生成服务Actor
GEngine->SpawnServerActors(this);
//初始化GameMode的GameSession
AuthorityGameMode->InitGame(FPaths::GetBaseFilename(InURL.Map), Options, Error);
//对每个关卡做进一步处理
for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); LevelIndex++)
{
Level->RouteActorInitialize();
}
//让服务器获取客户端的可见性状态
{
for (FLocalPlayerIterator It(GEngine, this); It; ++It)
{
if (APlayerController * LocalPlayerController = It->GetPlayerController(this))
{
TArray<FUpdateLevelVisibilityLevelInfo> LevelVisibilities;
for (int32 LevelIndex = 1; LevelIndex < Levels.Num(); LevelIndex++)
{
ULevel* SubLevel = Levels[LevelIndex];
FUpdateLevelVisibilityLevelInfo& LevelVisibility = *new (LevelVisibilities) FUpdateLevelVisibilityLevelInfo(SubLevel, SubLevel->bIsVisible);
LevelVisibility.PackageName = LocalPlayerController->NetworkRemapPath(LevelVisibility.PackageName, false);
}
LocalPlayerController->ServerUpdateMultipleLevelsVisibility(LevelVisibilities);
}
}
}
}
//……
//触发一个代理,通知引擎World中的所有Actor初始化完毕
OnActorsInitialized.Broadcast(OnActorInitParams);
FWorldDelegates::OnWorldInitializedActors.Broadcast(OnActorInitParams);
//通知NavigationSystem模块Actors初始化完毕
NavigationSystem->OnInitializeActors();
//通知AISystem模块Actors初始化完毕
AISystem->InitializeActorsForPlay(bResetTime);
//……
}
下面讲两个比较重要的函数:UpdateWorldComponents和RouteActorInitialize,它们分别代表了组件的注册和初始化。InitializeActorsForPlay先调用UpdateWorldComponents注册World中的组件,然后调用World中各个Level的RouteActorInitialize对Level下的Actor进行初始化。
a.UpdateWorldComponents:
下面是UpdateWorldComponents和相关函数的简略源码:
//Init/Start/StartGameInstance/Browse/LoadMap/InitializeActorsForPlay/UpdateWorldComponents
void UWorld::UpdateWorldComponents(bool bRerunConstructionScripts, bool bCurrentLevelOnly, FRegisterComponentContext* Context)
{
//……
//更新World中各个关卡中Actor的组件
for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); LevelIndex++)
{
ULevel* Level = Levels[LevelIndex];
ULevelStreaming* StreamingLevel = FLevelUtils::FindStreamingLevel(Level);
//只有当关卡可见时才会更新
if (!StreamingLevel || Level->bIsVisible)
{
//调用更新关卡组件接口
Level->UpdateLevelComponents(bRerunConstructionScripts, Context);
}
}
//……
}
//Init/Start/StartGameInstance/Browse/LoadMap/InitializeActorsForPlay/UpdateWorldComponents/UpdateLevelComponents
void ULevel::UpdateLevelComponents(bool bRerunConstructionScripts, FRegisterComponentContext* Context)
{
//调用更新组件接口
IncrementalUpdateComponents(0, bRerunConstructionScripts, Context);
}
//Init/Start/StartGameInstance/Browse/LoadMap/InitializeActorsForPlay/UpdateWorldComponents/UpdateLevelComponents/IncrementalUpdateComponents
void ULevel::IncrementalUpdateComponents(int32 NumComponentsToUpdate, bool bRerunConstructionScripts, FRegisterComponentContext* Context)
{
//……
//所有组件中的前面部分是BSP模块组件,要先更新
if (CurrentActorIndexForUpdateComponents == 0)
{
//更新BSP模块的组件
UpdateModelComponents();
//对Actor进行排序,确保父Actor在子Actor之前完成注册
SortActorsHierarchy(Actors, this);
}
//……
while (CurrentActorIndexForUpdateComponents < Actors.Num())
{
//获取此次要注册的Actor
AActor* Actor = Actors[CurrentActorIndexForUpdateComponents];
if (Actor && !Actor->IsPendingKill())
{
//预注册此Actor的所有组件
Actor->PreRegisterAllComponents();
//注册此Actor的所有组件
bAllComponentsRegistered = Actor->IncrementalRegisterComponents(NumComponentsToUpdate, Context);
}
//此Actor的Component都已被注册,迭代下一个
CurrentActorIndexForUpdateComponents++;
//判断是提前跳出
if (NumComponentsToUpdate != 0)
{
break;
}
}
if (CurrentActorIndexForUpdateComponents >= Actors.Num())
{
//……
//对子Acto做些处理
for (int32 ActorIndex = 0; ActorIndex < Actors.Num(); ++ActorIndex)
{
if (!Actor->IsChildActor())
{
Actor->RerunConstructionScripts();
}
}
//……
}
//……
//根据需要为一些组件创建用来处理物理碰撞物理体
if (FBodyInstance::UseDeferredPhysicsBodyCreation())
{
//……
FBodyInstance* BodyInstance = Cast<UPrimitiveComponent>(PrimtiveComponent)->GetBodyInstance();
BodyInstance->InitAllBodies(GetWorld()->GetPhysicsScene());
//……
}
}
首先UpdateWorldComponents遍历所有Level,调用每个Level的UpdateLevelComponents接口,该接口只是做转发,转发到真正更新组件的接口:IncrementalUpdateComponents。
IncrementalUpdateComponents把各组件的注册工作分为几个部分:先更新BSP模块的组件(UpdateModelComponents)并排序,接着调用剩下Actor的预注册(PreRegisterAllComponents)和注册接口(IncrementalRegisterComponents),然后做子Actor的处理,最后根据需要创建物理碰撞体。
b.RouteActorInitialize:
//Init/Start/StartGameInstance/Browse/LoadMap/InitializeActorsForPlay/RouteActorInitialize
void ULevel::RouteActorInitialize()
{
//……
//预初始化Actor组件
//GameMode也是一个Actor,所以这个循环也会调用GameMode的PreInitializeComponents函数,该函数会初始化GameState和GameNetworkManager
for (int32 Index = 0; Index < Actors.Num(); ++Index)
{
Actor->PreInitializeComponents();
}
//……
//调用组件的初始化和有初始化接口
for (int32 Index = 0; Index < Actors.Num(); ++Index)
{
Actor->InitializeComponents();
Actor->PostInitializeComponents();
}
//此次循环只是为了确保所有Actor都初始化完成
for (int32 ActorIndex = 0; ActorIndex < ActorsToBeginPlay.Num(); ActorIndex++)
{
Actor->DispatchBeginPlay(/*bFromLevelStreaming*/ true);
}
}
//Init/Start/StartGameInstance/Browse/LoadMap/InitializeActorsForPlay/RouteActorInitialize/InitializeComponents
void AActor::InitializeComponents()
{
//……
//遍历Actor的所有组件
for (UActorComponent* ActorComp : Components)
{
//根据需要设置是否激活组件
ActorComp->Activate(true);
//调用组件的初始化
ActorComp->InitializeComponent();
}
}
RouteActorInitialize主要有三个循环,第一个循环遍历Actor并调用每个Actor的预初始化组件接口(PreInitializeComponents),第二个循环遍历Actor并调用每个Actor的初始化组件(InitializeComponents)和后初始化组件(PostInitializeComponents)接口。
正式初始化组件InitializeComponents会遍历Actor内的所有组件,根据需要设置组件的激活状态,在调用组件初始化接口InitializeComponent。
2.BeginPlay:
第二步向世界打第一声招呼,下面展示BeginPlay的调用链:
//Init/Start/StartGameInstance/Browse/LoadMap/BeginPlay
void UWorld::BeginPlay()
{
//World调用GameMode开始函数
GameMode->StartPlay();
//UWorld调用AI系统开始函数
GetAISystem()->StartPlay();
//广播World的BeginPlay被调用的代理函数
OnWorldBeginPlay.Broadcast();
//……
}
//Init/Start/StartGameInstance/Browse/LoadMap/BeginPlay/StartPlay
void AGameModeBase::StartPlay()
{
//GameMode调用GameState的HandleBeginPlay接口
GameState->HandleBeginPlay();
}
//Init/Start/StartGameInstance/Browse/LoadMap/BeginPlay/HandleBeginPlay
void AGameStateBase::HandleBeginPlay()
{
//GameState调用WorldSettings的NotifyBeginPlay和NotifyMatchStarted接口
GetWorldSettings()->NotifyBeginPlay();
GetWorldSettings()->NotifyMatchStarted();
}
//Init/Start/StartGameInstance/Browse/LoadMap/BeginPlay/HandleBeginPlay/NotifyBeginPlay
void AWorldSettings::NotifyBeginPlay()
{
//WorldSettings遍历所有Actor,调用每个Actor的DispatchBeginPlay接口
for (FActorIterator It(World); It; ++It)
{
It->DispatchBeginPlay(bFromLevelLoad);
}
}
//Init/Start/StartGameInstance/Browse/LoadMap/BeginPlay/HandleBeginPlay/NotifyBeginPlay/DispatchBeginPlay
void AActor::DispatchBeginPlay(bool bFromLevelStreaming)
{
//……
//调用Actor的BeginPlayer接口
BeginPlay();
//……
}
//Init/Start/StartGameInstance/Browse/LoadMap/BeginPlay/HandleBeginPlay/NotifyBeginPlay/DispatchBeginPlay/BeginPlay
void AActor::BeginPlay()
{
//……
//遍历Actor包含的组件,调用每个组件的BeginPlay函数
for (UActorComponent* Component : Components)
{
Component->BeginPlay();
}
//……
}
源码很简单,从World开始,World通知GameMode,GameMode通知GameState,GameState通知WorldSettings,WorldSettings在通知每个Actor。最终所有BeginPlay被调用,至此开始执行我们写入BeginPlay的代码。
最后付一张完整的启动流程图:
三.运行流程源码浅析小结:
这几篇我们从平台的启动函数(WinMain)出发,讲了虚幻引擎启动的整个流程,包括虚幻引擎的预初始化、初始化、循环和最后的退出,每个阶段都选了几个比较重要的函数展开讲解,希望各位看完后有所收获。作者还是在校生,水平有限,如果有错误欢迎指正,我们下篇见。