0%

虚幻引擎启动阶段做了什么

UE引擎开始游戏做了什么

MAIN

程序入口LaunchWindows.cpp

int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* pCmdLine, _In_ int32 nCmdShow)
{
    int32 Result = LaunchWindowsStartup(hInInstance, hPrevInstance, pCmdLine, nCmdShow, nullptr);
    LaunchWindowsShutdown();
    return Result;
}

UE引擎的游戏入口就各种各样的Launch,这里举的是一个Windows平台的例子,那么直接进入LaunchWindowsStartup。 忽视一大堆平台支持相关预处理宏标记等,直接来到比较核心的部分GuardedMain。

FEngineLoop	GEngineLoop;
int32 GuardedMain(const TCHAR* CmdLine){
    int32 ErrorLevel = EnginePreInit( CmdLine );
    if ( ErrorLevel != 0 || IsEngineExitRequested() )
    {
    return ErrorLevel;
    }
    ErrorLevel = EditorInit(GEngineLoop);
    while( !IsEngineExitRequested() )
    {
    EngineTick();
    }
    EditorExit();
}

总而言之就是四大块:预初始化、初始化、tick、退出

模块注册

首先来看PreInit——UE模块加载的地方。 在一开始会加载引擎核心模块CoreUObject、Engine、Renderer。。。 之后会进入Default阶段加载项目和插件默认模块比如C++游戏代码等。

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"

#include "SomeActor.generated.h"

UCLASS()
class MyClass : public AActor{
GENERATED_BODY()
public:
MyClass(const FObjectInitializer& ObjectInitializer);
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
}

在每一个模块加载的第一步,都是将这个模块内定义的所有UObject类注册一遍提供给反射系统,之后生成CDO(类默认对象),记录这个类的默认状态,还能用于子类继承。

在类构造时,引擎会将CDO传给这个类的构造函数用于构造,就像例子中的一样。 所以对于UObject来说构造函数最好不要放和逻辑相关的东西例如为一个特定的对象初始化属性。

所有的UObject注册完之后,就是经典的StartupModule()和ShutdownModule()生命周期了

virtual void StartupModule() override;
virtual void ShutdownModule() override;

INIT

int32 FEngineLoop::Init(){
    FString GameEngineClassName;
    GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
    EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName);
    GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
    check( GEngine );

//Engine核心事件
GEngine-&gt;ParseCommandline();
GEngine-&gt;Init(this);

//加载PostEngineInit阶段加载的插件模块
IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit);
IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit);

GEngine-&gt;Start();
return 0;

}

Engine类 如果看过InsideUE5 gameplay部分或者我写的阅读笔记,就明白这个类主要负责游戏的核心内容编辑。 比如Browse和LoadMap。

bool UEngine::LoadMap( FWorldContext& WorldContext, FURL URL, class UPendingNetGame* Pending, FString& Error )

一个Engine对象,可以浏览到一个URL。这个URL代表服务器,也可以用于加载一个本地的地图。 在DefaultEngine.ini文件中设置默认地图时将默认地图本地位置设置上去,也可以在启动游戏时使用命令行输入URL来覆盖默认的地图。

Engine对象的初始化出现了一些比较属性的东西


void UGameEngine::Init(IEngineLoop* InEngineLoop)
{
    // Call base.
    UEngine::Init(InEngineLoop);

//GameInstance对象
FSoftClassPath GameInstanceClassName = GetDefault&lt;UGameMapsSettings&gt;()-&gt;GameInstanceClass;
UClass* GameInstanceClass = (GameInstanceClassName.IsValid() ? LoadObject&lt;UClass&gt;(NULL, *GameInstanceClassName.ToString()) : UGameInstance::StaticClass());
GameInstance = NewObject&lt;UGameInstance&gt;(this, GameInstanceClass);
GameInstance-&gt;InitializeStandalone();

//GameViewportClient对象
UGameViewportClient* ViewportClient = NewObject&lt;UGameViewportClient&gt;(this, GameViewportClientClass);
ViewportClient-&gt;Init(*GameInstance-&gt;GetWorldContext(), GameInstance);
GameViewport = ViewportClient;
GameInstance-&gt;GetWorldContext()-&gt;GameViewport = ViewportClient;

//LocalPlayer对象
FString Error;
ViewportClient-&gt;SetupInitialLocalPlayer(Error) == NULL;
{
    UE_LOG(LogEngine, Fatal,TEXT(&quot;%s&quot;),*Error);
}

UGameViewportClient::OnViewportCreated().Broadcast();
bIsInitialized = true;

}

GameInstanceGameViewportClientLocalPlayer三者在UEngine的初始化过程中诞生,完全可以将他们理解成游戏地图加载之前生成的对象。 与之相对的UWorld、ULevel、AActor、UActorComponent等等这些对象都是在“地图加载之后”生成的,与地图生命周期全权挂钩。如果游戏退出地图,或者说游戏退出到主菜单、掉线,那么这些对象都会被销毁。 这些对象都和场景挂钩,那么就直接来看看加载地图的LoadMap,他在引擎对象初始化后运作

bool UEngine::LoadMap( FWorldContext& WorldContext, FURL URL, class UPendingNetGame* Pending, FString& Error ){
    //原代码2k多行实在太长,这里直接写伪代码
    WorldContext.OwningGameInstance->PreloadContextForURL(URL);
    UPackage* WorldPackage = LoadPackage(...);//UWorld世界包含ULevel,然后ULevel中含所有的AActor,AActor包含Component,这些所有都打成Pack包存在内存中,需要重新打开事件的时候重新加载他
    UWorld* NewWorld = UWorld::FindWorldInPackage(...);
    NewWorld->SetGameInstance(WorldContext.OwningGameIntance);//WorldContext由GameInstance创建,用于跟踪当前UWorld

WorldContext.SetCurrentWorld(NewWorld); //设置当前世界
WorldContext.World()-&gt;WorldType = WorldContext.WorldType; //世界类似设置为Game
WorldContext.World()-&gt;AddToRoot();//防止垃圾回收
WorldContext.World()-&gt;InitWorld();//初始化世界的物理、AI、声音等设置
WorldCOntext.World()-&gt;SetGameMode(URL)//设置GameMode

//顾名思义初始化Actor
{
    FRegisterComponentContext Context(WorldContext.World());
    WorldContext.World()-&gt;InitializeActorsForPlay(URL, true, &amp;Context);
    Context.Process();
}

}

void UWorld::InitializeActorsForPlay(const FURL& InURL, bool bResetTime, FRegisterComponentContext* Context){
    遍历Actor
    遍历Actor中的Component进行注册(比如将本World的引用设置在Component内)
    遍历Actor进行Actor自身的游戏化构造。。。
}

网络游戏生成/登录流程

//todo

UClass与UObject

在C++中定义一个Class类,用它实例化一个对象obj,那么仅凭这个obj,没有办法直接获取到这个Class本身的属性和方法。 但是UE中的对象可以做到。 很明显,UClass继承与UObject,经常说UObject作为虚幻引擎中几乎所有类的基类,他做的一个较重要的工作就是实现反射功能。也就是说UClass也能使用反射功能比如GetClass、StaticClass方法等。 UE中创建对象的方法不是new,而是**NewObject<>**方法,这个方法同时为目标对象生成了一个伴随对象,这个伴随对象的类型就是UClass,直接存放在目标对象的变量内。

除了UClass用于描述UObject,还有UEnum描述枚举、UFunction描述函数、FFIeld描述基础类型等

UClass功能

  • 实现反射系统
  • 记录类的继承关系
  • 记录类元数据
  • 生成CDO并保留引用
  • 生成对象间引用关系用于垃圾回收

UObject功能

  • 提供通用属性接口GetClass、GetName
  • 序列化反序列化
  • 内存管理、对象内存分配
  • 垃圾回收机制
  • NetWork
  • 蓝图虚拟机