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] 下载虚幻引擎源代码

[2] 从零开始手敲次世代游戏引擎(五)

[3] UE5 -- 引擎运行流程(从main到BeginPlay)

[4] The Unreal Engine Game Framework: From int main() to BeginPlay

相关推荐
向宇it2 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
芋芋qwq4 小时前
Unity UI射线检测 道具拖拽
ui·unity·游戏引擎
tealcwu4 小时前
【Unity服务】关于Unity LevelPlay的基本情况
unity·游戏引擎
心怀梦想的咸鱼11 小时前
UE5 第一人称射击项目学习(二)
学习·ue5
暮志未晚Webgl11 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
心怀梦想的咸鱼11 小时前
UE5 第一人称射击项目学习(完结)
学习·ue5
鹿野素材屋11 小时前
Unity Dots下的动画合批工具:GPU ECS Animation Baker
unity·游戏引擎
小春熙子19 小时前
Unity图形学之着色器之间传递参数
unity·游戏引擎·技术美术·着色器
虾球xz20 小时前
游戏引擎学习第15天
学习·游戏引擎
Java Fans1 天前
在Unity中实现电梯升降功能的完整指南
unity·游戏引擎