UE4 运行流程源码浅析(3——从Start到BeginPlay)

本文详细解析了虚幻引擎中从UGameEngine::Start函数出发,经过一系列函数调用,最终到达LoadMap函数的过程,重点讲解了LoadMap函数中的关键步骤,如卸载旧World、加载新World、初始化Actor和Components等,以及BeginPlay函数的调用路径。
摘要由CSDN通过智能技术生成

本文章只是我个人在学习虚幻引擎过程中的一些理解,不一定正确,若有说的不对的地方,欢迎指正。

前面我们介绍了引擎的执行流程和各个阶段(预初始化PreInit、初始化Init、循环Tick、退出Exit)函数调用的情况,但我们好像还没看见我们熟悉的BeginPlay是在哪里被调用的。其实所有BeginPlay的源头就是我们上一篇没有进一步介绍的UGameEngine::Start函数,本篇会从Start函数出发详细介绍这其中的调用过程。

一.从Start到LoadMap:

这部分很简单,从UGameEngine::Start函数开始到最终调用的LoadMap函数,中间经过UGameInstance::StartGameInstanceEngine::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的相关资源,之后会从磁盘找到新WorldPackage,从中加载和初始化World并把新World加入根集,加载新World所需Actor、初始化导航网格、AI,最后调用WorldBeginPlay,详细解释已经写在代码注释中,简略源码如下:

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中的几个函数:InitializeActorsForPlayBeginPlayBeginPlay是触发所有我们开发中常见的开始事件的地方,而在开始事件被调用之前,我们需要先让World中的Actor们动起来,因此我们先调用InitializeActorsForPlay

1.InitializeActorsForPlay:

第一步让世界动起来,InitializeActorsForPlay可以理解成让World中的Actor们活过来。它主要作用是注册并初始化每个Actor和每个Actor的所有Component,设置WorldGameSession,通知一些模块初始化,详细解释在源码中:

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);

	//……
}

下面讲两个比较重要的函数:UpdateWorldComponentsRouteActorInitialize,它们分别代表了组件的注册和初始化。InitializeActorsForPlay先调用UpdateWorldComponents注册World中的组件,然后调用World中各个Level的RouteActorInitializeLevel下的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,调用每个LevelUpdateLevelComponents接口,该接口只是做转发,转发到真正更新组件的接口: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通知GameModeGameMode通知GameStateGameState通知WorldSettingsWorldSettings在通知每个Actor。最终所有BeginPlay被调用,至此开始执行我们写入BeginPlay的代码。
最后付一张完整的启动流程图:
在这里插入图片描述

三.运行流程源码浅析小结:

这几篇我们从平台的启动函数(WinMain)出发,讲了虚幻引擎启动的整个流程,包括虚幻引擎的预初始化、初始化、循环和最后的退出,每个阶段都选了几个比较重要的函数展开讲解,希望各位看完后有所收获。作者还是在校生,水平有限,如果有错误欢迎指正,我们下篇见。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值