UE5的引擎初始化流程

UE5的引擎初始化流程

首先跟着UE的官方文档[1]获取到UE的源代码,然后在参考GitHub上repo的readme,将UE引擎从源码build出来。以Windows平台为例,先找到引擎的入口函数:

c++ 复制代码
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;
}

可以看到入口函数很简单,就分为startup和shutdown两部分,那么想必引擎的主循环是在这个startup里。startup函数主要负责初始化各种调用环境,解析命令行参数,调用真正的main函数:

c++ 复制代码
LAUNCH_API int32 LaunchWindowsStartup( HINSTANCE hInInstance, HINSTANCE hPrevInstance, char*, int32 nCmdShow, const TCHAR* CmdLine )
{
	// Setup common Windows settings
	SetupWindowsEnvironment();

	int32 ErrorLevel			= 0;
	hInstance				= hInInstance;

	if (!CmdLine)
	{
		CmdLine = ::GetCommandLineW();

		// Attempt to process the command-line arguments using the standard Windows implementation
		// (This ensures behavior parity with other platforms where argc and argv are used.)
		if ( ProcessCommandLine() )
		{
			CmdLine = *GSavedCommandLine;
		}
	}

    ErrorLevel = GuardedMain( CmdLine );

	return ErrorLevel;
}

简化后的main函数如下:

c++ 复制代码
int32 GuardedMain( const TCHAR* CmdLine )
{
    // Super early init code. DO NOT MOVE THIS ANYWHERE ELSE!
	FCoreDelegates::GetPreMainInitDelegate().Broadcast();

	// make sure GEngineLoop::Exit() is always called.
	struct EngineLoopCleanupGuard 
	{ 
		~EngineLoopCleanupGuard()
		{
			// Don't shut down the engine on scope exit when we are running embedded
			// because the outer application will take care of that.
			if (!GUELibraryOverrideSettings.bIsEmbedded)
			{
				EngineExit();
			}
		}
	} CleanupGuard;

	int32 ErrorLevel = EnginePreInit( CmdLine );

	// exit if PreInit failed.
	if ( ErrorLevel != 0 || IsEngineExitRequested() )
	{
		return ErrorLevel;
	}

#if WITH_EDITOR
    if (GIsEditor)
    {
        ErrorLevel = EditorInit(GEngineLoop);
    }
    else
#endif
    {
        ErrorLevel = EngineInit();
    }

	// Don't tick if we're running an embedded engine - we rely on the outer
	// application ticking us instead.
	if (!GUELibraryOverrideSettings.bIsEmbedded)
	{
		while( !IsEngineExitRequested() )
		{
			EngineTick();
		}
	}

#if WITH_EDITOR
	if( GIsEditor )
	{
		EditorExit();
	}
#endif
	return ErrorLevel;
}

梳理一下大体的逻辑,在一切初始化逻辑开始之前,函数会先把这个消息广播出去,如果有更早需要执行的逻辑,可以注册到这个委托上。另外,为了保证引擎的退出逻辑一定会被调用到,UE在这里定义了一个局部的struct,这个struct定义了一个析构函数,析构函数的实现就是调用引擎的退出逻辑。有了这个局部struct,再定义一个局部的对象,这个对象不是动态分配的,那么C++会保证它离开作用域时就会销毁它,从而调用到引擎的退出逻辑。这也是C++ RAII的常见做法了。

再往下,可以发现引擎的初始化分为PreInit和Init两个步骤。Init会根据是否是Editor环境,调用不同的逻辑。在初始化完成后,就进入到了引擎的主循环。

顺便一提,PreInit,Init,Tick,Exit都是简单的包装函数,它们的实现就是调用全局变量GEngineLoop的相应接口。

c++ 复制代码
int32 EngineInit()
{
	int32 ErrorLevel = GEngineLoop.Init();

	return( ErrorLevel );
}

LAUNCH_API void EngineTick( void )
{
	GEngineLoop.Tick();
}

LAUNCH_API void EngineExit( void )
{
	// Make sure this is set
	RequestEngineExit(TEXT("EngineExit() was called"));

	GEngineLoop.Exit();
}

PreInit初始化了各种底层模块[4],非常繁多。

除此之外,还完成了许多模块的加载:

Init阶段,UE会根据是否为editor环境调用EditorInit或者EngineInit。而EditorInit本身的实现里就是包含EngineInit的。Init简化后的版本如下。

c++ 复制代码
int32 FEngineLoop::Init()
{
	UClass* EngineClass = nullptr;
	if( !GIsEditor )
	{
		// We're the game.
		FString GameEngineClassName;
		GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
		EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName);
		GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
	}
	else
	{
#if WITH_EDITOR
		// We're UnrealEd.
		FString UnrealEdEngineClassName;
		GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), UnrealEdEngineClassName, GEngineIni);
		EngineClass = StaticLoadClass(UUnrealEdEngine::StaticClass(), nullptr, *UnrealEdEngineClassName);
		GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);
#endif
	}

    GEngine->ParseCommandline();
    GEngine->Init(this);
    FCoreDelegates::OnPostEngineInit.Broadcast();

    if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
    {
        RequestEngineExit(TEXT("One or more modules failed PostEngineInit"));
        return 1;
    }

	// Call module loading phases completion callbacks
	SetEngineStartupModuleLoadingComplete();
    GEngine->Start();

	GIsRunning = true;

    FCoreDelegates::OnFEngineLoopInitComplete.Broadcast();
    return 0;
}

Init阶段主要做了如下的事情[3]:

  1. 根据当前是否为编辑器环境,检查引擎配置文件以确定应该使用哪个GameEngine类;
  2. 然后创建该类的一个实例并将其作为全局UEngine实例。也就是GEngine
  3. 创建之后,进行初始化,完成时会触发一个全局委托;
  4. 加载已配置为延迟加载的任何项目或插件模块;
  5. 加载完成后,再触发一个完成的回调;
  6. 正式启动引擎;
  7. 发送引擎启动的委托。

自此,引擎的初始化流程就宣告结束了,后面就进入到了tick的阶段。

Reference

1\] [下载虚幻引擎源代码](https://dev.epicgames.com/documentation/en-us/unreal-engine/downloading-unreal-engine-source-code) \[2\] [从零开始手敲次世代游戏引擎(五)](https://zhuanlan.zhihu.com/p/28619982) \[3\] [UE5 -- 引擎运行流程(从main到BeginPlay)](https://zhuanlan.zhihu.com/p/577433224) \[4\] [The Unreal Engine Game Framework: From int main() to BeginPlay](https://www.youtube.com/watch?v=IaU2Hue-ApI)

相关推荐
hashiqimiya43 分钟前
unity配置外部编辑器rider
unity·编辑器·游戏引擎
jtymyxmz4 小时前
《Unity Shader》10.1.3 反射
unity·游戏引擎
一线灵2 天前
跨平台游戏引擎 Axmol-2.10.0 发布
游戏引擎
沉默金鱼2 天前
Unity实用技能-格式化format文字
ui·unity·游戏引擎
qq_205279053 天前
Unity TileMap 使用经验
unity·游戏引擎
心灵宝贝3 天前
Mac Unity 2018.dmg游戏工具 安装步骤 简单易懂教程(附安装包)
macos·unity·游戏引擎
5***o5003 天前
PHP在电商中的支付集成
sql·ue5·rizomuv
TO_ZRG3 天前
Unity SDK 通过 Registry 分发及第三方依赖处理指南
unity·游戏引擎
7***n753 天前
C++在游戏中的Cocos2d-x
游戏·游戏引擎·cocos2d
龙智DevSecOps解决方案3 天前
Perforce《2025游戏技术现状报告》Part 1:游戏引擎技术的广泛影响以及生成式AI的成熟之路
人工智能·unity·游戏引擎·游戏开发·perforce