关键class object的构建顺序-1
UE4 Version: 源码编译4.26.2
Windows Platform
不考虑Editor相关代码
注:下面的内容在UE4 C++中是很基础的内容,也不涉及到具体C++实现,如果你对UE4 C++很了解,完全没有是必要往下看的。
概述
上一篇《UE4引擎入口与主循环》简要的介绍UE4引擎在C++层面的入口、初始化流程的函数调用顺序和主循环;
这篇文章来分享一下几个关键class object的构建顺序,以便从C++对象结构上有一个大体的认识,它们是下面这些class;
- UGameEngine
- UGameInstance
- UWorld
- ULevel
- AGameMode
- AGameState
- APlayerController
- AHUD
- ACharactor
在UE4编辑器中,【Project Settings】中,可以设定一个Default GameMode;
或者在【World Settings】面板中可以给当前Map指定一个具体的GameMode;
在【World Settings】中,一个指定的GameModel中,可以指定:
- Default Pawn Class
- HUD Class
- Player Controller Class
- Game State Class
- Player State Class
- Spectator Class
如果你的AGameMode Class是一个蓝图,或者是一个C++ Class,还可以设定一些其他的class;
这里不展开,可以自行定义一个继承自AGameMode的蓝图类,或者直接查看class AGameModeBase和class AGameMode的C++源码;
你是否曾有过这样的疑问,在UE4编辑器后面,一个class AGameMode object是在什么时候构建的?(相比其他object)又是以怎样的先后顺序构建的?
关键class object的构建顺序
承接上一篇《UE4引擎入口与主循环》,现在来梳理一下关键class object的构建顺序;
下面的内容是以Game模式启动,VS中的设定可以参考一下这里VS中UE4工程以Game模式启动
class UGameEngine object的构建
FEngienLoop::Init()中,在非Editor模式下,会根据配置表找到EngineClass,进而调用NewObject函数构造class UGameEngine object;返回值赋值给GEngine这个全局变量;
Init()函数后续的代码会依次调用GEngine->Init(this), GEngine->Start();
涉及的代码和函数:
- Engine/Source/Runtime/Launch/Private/LaunchEngineLoop.cpp
- int32 FEngineLoop::Init()
class UGameInstance object的构建
在void UGameEngine::Init()函数中,根据设定的或默认的UClass* GameInstanceClass构建GameInstance这个object;
留意:
这里UGameInstanc* GameInstance是class UGameEngine的一个data member;
GameInstance这个object的Outer是GameEngine;
随后,调用GameInstance->InitializeStandalone()函数,转到该函数内部,一个class UWorldContext object以及一个class UWorld object被构建出来;
void UGameInstance::InitializeStandalone(const FName InPackageName, UPackage* InWorldPackage)
{
// Creates the world context. This should be the only WorldContext that ever gets created for this GameInstance.
WorldContext = &GetEngine()->CreateNewWorldContext(EWorldType::Game);
WorldContext->OwningGameInstance = this; // (1)
// In standalone create a dummy world from the beginning to avoid issues of not having a world until LoadMap gets us our real world
UWorld* DummyWorld = UWorld::CreateWorld(EWorldType::Game, false, InPackageName, InWorldPackage);
DummyWorld->SetGameInstance(this); //(2)
WorldContext->SetCurrentWorld(DummyWorld); //(3)
Init();
}
以上代码可以留意一下注释的(1), (2), (3)三处;
还有以上代码中的UWorld* DummyWorld这个object并不是最终的UWorld object, 具体看下面的流程;
真正的class UWorld object登场
接下来到void UGameEngine::Start()函数:
void UGameEngine::Start()
{
UE_LOG(LogInit, Display, TEXT("Starting Game."));
GameInstance->StartGameInstance();
}
该函数内部只是转调用void UGameInstance->StartGameInstance();
StartGameInstance()函数内部,核心代码是:
FURL URL(&DefaultURL, *PackageName, TRAVEL_Partial);
if (URL.Valid)
{
BrowseRet = Engine->Browse(*WorldContext, URL, Error);
}
上面的Engine是一个class UGameEngine object;
class UGameEngine是class UEngine的派生类,Browse函数定义在class UEngine中;
在Browse函数中,可以看到一系列的对URL测试代码,因为当前是添加了-game启动参数,调用会落到URL.IsLocalInternal()测试中,如下
// EBrowseReturnVal::Type UEengine::Browse(...)函数的部分截取
// ...
if( URL.IsLocalInternal() )
{
// Local map file.
return LoadMap( WorldContext, URL, NULL, Error ) ? EBrowseReturnVal::Success : EBrowseReturnVal::Failure;
}
// ...
bool UEngine::LoadMap(...)太长了,我现在用的版本源文件中该函数超过了500行;
class UEngine在Engine/Source/Runtime/Engine/Classed/Engine/Engine.h文件中;
class Uengine的大多数member function定义在:
Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp
文件中;
可以搜索关键字NewWorld;
会发现NewWorld边上伴随着一个UPackage* WorldPackage指针,并且发现一个函数LoadPackage(...);
而关于UWorld* NewWorld这个指针有如下代码:
// bool UEngine::LoadMap(...)函数的部分代码节选
// ...省略了好多代码
NewWorld = UWorld::FindWorldInPackage(WorldPackage);
// ...省略了一些代码
NewWorld->PersistentLevel->HandleLegacyMapBuildData();
// ...这里也省略了好多代码
你可以打开World.h文件,看看class UWorld的data member中有class ULevel* PersistentLevel;
结合上面的节选代码和源码文件,LoadPackage(...)之后,UWorld::FindWorldInPackage(WorldPackage)返回的class UWorld object已经有值;并且class ULevel object都创建出来了;
为了突出本篇这主题,这里不在展开探索,再说UPackage也是另外一个超大主题了;
到此,就认为class UWorld object和class ULevel object被构建好了吧;
class AGameMode object的构建
终于轮到A开头的class了
上节中探索到class UWorld object和class ULevel object的是在UEngine::LoadMap函数中构建好的(姑且这么认为吧)
在LoadMap函数中,对新构建出来的NewWorld这个object,有诸多设置;
当中你一定不会错过:
WorldContext.World()->SetGameMode(URL);
这行代码;
bool UWorld::SetGameMode(...)函数内部调用UGameInstance::CreateGameModeForURL(...)函数,以返回一个class AGameModeBase*指针;
转到GameInstance.cpp源文件的CreateGameModeForURL函数定义,看似挺长,其实都是在找TSubclassOf
总结
写到class AGameMode object的构建后,我发现这篇文章已经挺长了;
而这篇文章的主题,本就是想着梳理一下UE4的关键class object的构建流程;
不想文章又臭又长,自己回顾起来都不愿意看,更别说刚开始接触UE4的初学者;
那么剩下的就在下篇文章再来探索吧;
学习UE4的程序员,我想应该都看过官方文档的游戏流程总览
老实讲,刚接触UE4时,看到这个图也是不明所以;
加上UE4的源码量实在庞大,源码内又夹杂者Editor,PIE,DS相关的宏隔离代码,源码探索过程中容易淹没在细节中;
一个清晰的、粗粒度维度的C++ class和object分析,有助于对整个引擎有一个宏观上的OOP认知;
扩展
以上只是以Client Standalone形式来探索各个关键对象的构建顺序;
因为UE4可以直接构建DS服务器,有兴趣的可以往这个方向探索一下;
另外,在探索源码的过程中,可以关注一下各个class中的回调(或事件)函数,以及一些委托对象的触发点,个人私认为这些对于项目工程的生命周期管理会大有帮助。

浙公网安备 33010602011771号