关键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 GameClass, 最后调用World->SpawnActor(GameClass, SpawnInfo);

总结

写到class AGameMode object的构建后,我发现这篇文章已经挺长了;

而这篇文章的主题,本就是想着梳理一下UE4的关键class object的构建流程;

不想文章又臭又长,自己回顾起来都不愿意看,更别说刚开始接触UE4的初学者;

那么剩下的就在下篇文章再来探索吧;

学习UE4的程序员,我想应该都看过官方文档的游戏流程总览

老实讲,刚接触UE4时,看到这个图也是不明所以;

加上UE4的源码量实在庞大,源码内又夹杂者Editor,PIE,DS相关的宏隔离代码,源码探索过程中容易淹没在细节中;

一个清晰的、粗粒度维度的C++ class和object分析,有助于对整个引擎有一个宏观上的OOP认知;

扩展

以上只是以Client Standalone形式来探索各个关键对象的构建顺序;

因为UE4可以直接构建DS服务器,有兴趣的可以往这个方向探索一下;

另外,在探索源码的过程中,可以关注一下各个class中的回调(或事件)函数,以及一些委托对象的触发点,个人私认为这些对于项目工程的生命周期管理会大有帮助。

posted @ 2022-03-03 02:50  阿佑001  阅读(118)  评论(0)    收藏  举报