在上述文章中简单介绍了一点 UE 的启动流程,大概在什么时候开始挂载 Pak 文件
引擎从 LaunchWindowsStartup 函数开始,在解析完 CmdLine 并根据命令函参数进行属性设置之后,将 CmdLine 传入给 GuardedMain 函数
// 在 LaunchWindowsStartup 根据 CmdLine 参数设置报错类型和全局变量
// If we're running in unattended mode, make sure we never display error dialogs if we crash.
if ( FParse::Param( CmdLine, TEXT("unattended") ) )
{
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
}
if ( FParse::Param( CmdLine,TEXT("crashreports") ) )
{
GAlwaysReportCrash = true;
}
在 GuardedMain 中
CmdLine 是否存在 WaitForDebugger 或者 waitforattach 参数决定是否要等待附加进程#if !(UE_BUILD_SHIPPING)
// If "-waitforattach" or "-WaitForDebugger" was specified, halt startup and wait for a debugger to attach before continuing
if (FParse::Param(CmdLine, TEXT("waitforattach")) || FParse::Param(CmdLine, TEXT("WaitForDebugger")))
{
while (!FPlatformMisc::IsDebuggerPresent())
{
FPlatformProcess::Sleep(0.1f);
}
UE_DEBUG_BREAK();
}
#endif
广播 GetPreMainInitDelegate 事件,通知即将开始 EnginePreInit
创建 EngineLoopCleanupGuard 临时对象,GuardedMain 函数结束时对象被释放从而触发析构函数,进入出发 EngineExit 函数
// 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;
触发 EnginePreInit 进入引擎预先初始化阶段
根据 GIsEditor 判断是否是编辑器,分别进入 EditorInit 或者 EngineInit 真正初始化函数
// 在 PreInit 中通过宏来设定 GIsEditor 的值
#if WITH_EDITORONLY_DATA
GIsEditor = true;
#endif
if (!GUELibraryOverrideSettings.bIsEmbedded)
{
while( !IsEngineExitRequested() )
{
EngineTick();
}
}
GIsEditor 来执行推出编辑器逻辑#if WITH_EDITOR
if( GIsEditor )
{
EditorExit();
}
#endif
int32 EnginePreInit( const TCHAR* CmdLine )
{
int32 ErrorLevel = GEngineLoop.PreInit( CmdLine );
return( ErrorLevel );
}
EnginePreInit 本质就是调用 GEngineLoop.PreInit
int32 FEngineLoop::PreInit(const TCHAR* CmdLine)
{
const int32 rv1 = PreInitPreStartupScreen(CmdLine);
if (rv1 != 0)
{
PreInitContext.Cleanup();
return rv1;
}
const int32 rv2 = PreInitPostStartupScreen(CmdLine);
if (rv2 != 0)
{
PreInitContext.Cleanup();
return rv2;
}
return 0;
}
PreInit 分为两个粒度: PreInitPreStartupScreen 和 PreInitPostStartupScreen,也就是打开开始界面和打开开始界面后
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::StartOfEnginePreInit);
if (GLog)
{
GLog->SetCurrentThreadAsPrimaryThread();
}
#if UE_BUILD_DEVELOPMENT && defined(UE_BUILD_DEVELOPMENT_WITH_DEBUGGAME) && UE_BUILD_DEVELOPMENT_WITH_DEBUGGAME
FApp::SetDebugGame(true);
#endif
#if PLATFORM_WINDOWS
FWindowsPlatformMisc::SetGracefulTerminationHandler();
#endif
#if BUILD_EMBEDDED_APP
FEmbeddedCommunication::Init();
FEmbeddedCommunication::KeepAwake(TEXT("Startup"), false);
#endif
FMemory::SetupTLSCachesOnCurrentThread();
if (FParse::Param(CmdLine, TEXT("UTF8Output")))
{
FPlatformMisc::SetUTF8Output();
}
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
#if !UE_BUILD_SHIPPING && WITH_EDITORONLY_DATA
FString Env = FPlatformMisc::GetEnvironmentVariable(TEXT("UE-CmdLineArgs")).TrimStart();
if (Env.Len())
{
FCommandLine::Append(TEXT(" -EnvAfterHere "));
FCommandLine::Append(*Env);
CmdLine = FCommandLine::Get();
}
#endif
if (LaunchSetGameName(CmdLine, GameProjectFilePathUnnormalized) == false)
{
return 1;
}
FTraceAuxiliary::Initialize(CmdLine);
FTraceAuxiliary::TryAutoConnect();
LLM(FLowLevelMemTracker::Get().ProcessCommandLine(CmdLine));
#if MEMPRO_ENABLED
FMemProProfiler::Init(CmdLine);
#endif
#if WITH_ENGINE
FCoreUObjectDelegates::PostGarbageCollectConditionalBeginDestroy.AddStatic(DeferredPhysResourceCleanup);
#endif
FCoreDelegates::OnSamplingInput.AddStatic(UpdateGInputTime);
#if USE_IO_DISPATCHER
if (FIoStatus Status = FIoDispatcher::Initialize(); !Status.IsOk())
{
return 1;
}
#endif
// platform specific initialization now that the SystemSettings are loaded
FPlatformMisc::PlatformInit();
#if WITH_APPLICATION_CORE
FPlatformApplicationMisc::Init();
#endif
FPlatformMemory::Init();
if (bCreateTaskGraphAndThreadPools)
{
// initialize task graph sub-system with potential multiple threads
SCOPED_BOOT_TIMING("FTaskGraphInterface::Startup");
FTaskGraphInterface::Startup(FPlatformMisc::NumberOfWorkerThreadsToSpawn());
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::GameThread);
}
if (FPlatformProcess::SupportsMultithreading() && bCreateTaskGraphAndThreadPools)
{
// do some thing
}
#if STATS
FThreadStats::StartThread();
#endif
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::StatSystemReady);
FScopeCycleCounter CycleCount_AfterStats(GET_STATID(STAT_FEngineLoop_PreInitPreStartupScreen_AfterStats));
// Load Core modules required for everything else to work (needs to be loaded before InitializeRenderingCVarsCaching)
{
SCOPED_BOOT_TIMING("LoadCoreModules");
if (!LoadCoreModules())
{
UE_LOG(LogInit, Error, TEXT("Failed to load Core modules."));
return 1;
}
}
if (bDumpEarlyConfigReads)
{
UE::ConfigUtilities::RecordConfigReadsFromIni();
}
if (bDumpEarlyPakFileReads)
{
RecordFileReadsFromPaks();
}
if(bWithConfigPatching)
{
UE_LOG(LogInit, Verbose, TEXT("Begin recording CVar changes for config patching."));
UE::ConfigUtilities::RecordApplyCVarSettingsFromIni();
}
UE::ConfigUtilities::ApplyCVarsFromBootHotfix();
#if WITH_ENGINE
extern ENGINE_API void InitializeRenderingCVarsCaching();
InitializeRenderingCVarsCaching();
#endif
// init Oodle here
FOodleDataCompression::StartupPreInit();
{
SCOPED_BOOT_TIMING("LoadPreInitModules");
LoadPreInitModules();
}
Oodle是一套高效的压缩工具集,用于压缩和解压缩游戏数据,以减少存储空间和提高数据传输速度
// Start the application
{
SCOPED_BOOT_TIMING("AppInit");
if (!AppInit())
{
return 1;
}
}
if (FPlatformProcess::SupportsMultithreading())
{
{
SCOPED_BOOT_TIMING("GIOThreadPool->Create");
GIOThreadPool = FQueuedThreadPool::Allocate();
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfIOWorkerThreadsToSpawn();
if (FPlatformProperties::IsServerOnly())
{
NumThreadsInThreadPool = 2;
}
verify(GIOThreadPool->Create(NumThreadsInThreadPool, 96 * 1024, TPri_AboveNormal, TEXT("IOThreadPool")));
}
}
#if WITH_ENGINE
{
SCOPED_BOOT_TIMING("System settings and cvar init");
// Initialize system settings before anyone tries to use it...
GSystemSettings.Initialize(bHasEditorToken);
// Apply renderer settings from console variables stored in the INI.
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.RendererSettings"), *GEngineIni, ECVF_SetByProjectSetting);
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.RendererOverrideSettings"), *GEngineIni, ECVF_SetByProjectSetting);
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.StreamingSettings"), *GEngineIni, ECVF_SetByProjectSetting);
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.GarbageCollectionSettings"), *GEngineIni, ECVF_SetByProjectSetting);
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/Engine.NetworkSettings"), *GEngineIni, ECVF_SetByProjectSetting);
#if WITH_EDITOR
UE::ConfigUtilities::ApplyCVarSettingsFromIni(TEXT("/Script/UnrealEd.CookerSettings"), *GEngineIni, ECVF_SetByProjectSetting);
#endif
#if !UE_SERVER
if (!bIsRunningAsDedicatedServer)
{
if (!bHasCommandletToken)
{
// Note: It is critical that resolution settings are loaded before the movie starts playing so that the window size and fullscreen state is known
UGameUserSettings::PreloadResolutionSettings();
}
}
#endif
}
Slate 应用程序初始化// Are we creating a slate application?
bool bSlateApplication = !IsRunningDedicatedServer() && (bIsRegularClient || bHasEditorToken);
if (bSlateApplication)
{
if (FPlatformProcess::SupportsMultithreading() && !FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")))
{
SCOPED_BOOT_TIMING("FPlatformSplash::Show()");
FPlatformSplash::Show();
}
// Init platform application
SCOPED_BOOT_TIMING("FSlateApplication::Create()");
FSlateApplication::Create();
}
else
{
// If we're not creating the slate application there is some basic initialization
// that it does that still must be done
EKeys::Initialize();
FSlateApplication::InitializeCoreStyle();
}
{
SCOPED_BOOT_TIMING("GetMoviePlayer()->SetupLoadingScreenFromIni");
// allow the movie player to load a sequence from the .inis (a PreLoadingScreen module could have already initialized a sequence, in which case
// it wouldn't have anything in it's .ini file)
GetMoviePlayer()->SetupLoadingScreenFromIni();
}
if (GetMoviePlayer()->HasEarlyStartupMovie())
{
SCOPED_BOOT_TIMING("EarlyStartupMovie");
GetMoviePlayer()->Initialize(SlateRendererSharedRef.Get(), FPreLoadScreenManager::Get() ? FPreLoadScreenManager::Get()->GetRenderWindow() : nullptr);
/**
* Do Something
*/
}
PreEarlyLoadingScreen 模块if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreEarlyLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreEarlyLoadingScreen))
{
return 1;
}
//Now that our EarlyStartupScreen is finished, lets take the necessary steps to mount paks, apply .ini cvars, and open the shader libraries if we installed content we expect to handle
//If using a bundle manager, assume its handling all this stuff and that we don't have to do it.
if (BundleManager == nullptr || BundleManager->IsNullInterface() || !BundleManager->SupportsEarlyStartupPatching())
{
// Mount Paks that were installed during EarlyStartupScreen
if (FCoreDelegates::OnMountAllPakFiles.IsBound() && FPaths::HasProjectPersistentDownloadDir() )
{
// do something
}
// do something
}
InitGameTextLocalization();
FPackageName::RegisterShortPackageNamesForUObjectModules();
FModuleManager::Get().LoadModule("AssetRegistry");
IPackageResourceManager::Initialize();
IBulkDataRegistry::Initialize();
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::PreObjectSystemReady);
ProcessNewlyLoadedUObjects();
#if WITH_EDITOR
if (!UE::Virtualization::ShouldInitializePreSlate())
{
// Explicit initialization of the virtualization system, after slate has initialized and we can show error dialogs.
UE::Virtualization::Initialize(UE::Virtualization::EInitializationFlags::None);
}
#endif //WITH_EDITOR
// Ensure game localization has loaded before we continue
FTextLocalizationManager::Get().WaitForAsyncTasks();
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::ObjectSystemReady);
FPackageLocalizationManager::Get().PerformLazyInitialization();
{
SCOPED_BOOT_TIMING("InitDefaultMaterials etc");
// Default materials may have been loaded due to dependencies when loading
// classes and class default objects. If not, do so now.
UMaterialInterface::InitDefaultMaterials();
UMaterialInterface::AssertDefaultMaterialsExist();
UMaterialInterface::AssertDefaultMaterialsPostLoaded();
}
// Initialize the texture streaming system (needs to happen after RHIInit and ProcessNewlyLoadedUObjects).
IStreamingManager::Get();
SCOPED_BOOT_TIMING("LoadStartupCoreModules");
if (!LoadStartupCoreModules())
{
// At least one startup module failed to load, return 1 to indicate an error
return 1;
}
就是分别加载
Core、Networking和SlateCore等模块
SCOPED_BOOT_TIMING("PostInitRHI etc");
PostInitRHI();
if (GUseThreadedRendering)
{
if (GRHISupportsRHIThread)
{
const bool DefaultUseRHIThread = true;
GUseRHIThread_InternalUseOnly = DefaultUseRHIThread;
if (FParse::Param(FCommandLine::Get(), TEXT("rhithread")))
{
GUseRHIThread_InternalUseOnly = true;
}
else if (FParse::Param(FCommandLine::Get(), TEXT("norhithread")))
{
GUseRHIThread_InternalUseOnly = false;
}
}
StartRenderingThread();
}
if (!LoadStartupModules())
{
// At least one startup module failed to load, return 1 to indicate an error
return 1;
}
这里就是分别加载
PreDefault、Default和PostDefault三个阶段的模块
Commandled 提供支持,仅在非编辑器和 DS 情况下运行if (!bHasEditorToken && !IsRunningDedicatedServer())
{
UClass* CommandletClass = nullptr;
if (!bIsRegularClient)
{
checkf(PRIVATE_GIsRunningCommandlet, TEXT("This should have been set in PreInitPreStartupScreen"));
CommandletClass = Cast<UClass>(StaticFindFirstObject(UClass::StaticClass(), *Token, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("looking for commandlet")));
/**
* Do Something
*/
}
}
#if WITH_EDITOR
if (GIsEditor)
{
ErrorLevel = EditorInit(GEngineLoop);
}
else
#endif
{
ErrorLevel = EngineInit();
}
int32 ErrorLevel = EngineLoop.Init();
if( ErrorLevel != 0 )
{
FPlatformSplash::Hide();
return 0;
}
FActorFolders::Get();
// Initialize the misc editor
FUnrealEdMisc::Get().OnInit();
FCoreDelegates::OnExit.AddLambda([]()
{
// Shutdown the global static mode manager
if (Internal::GetGlobalModeManager().IsValid())
{
GLevelEditorModeTools().SetDefaultMode(FBuiltinEditorModes::EM_Default);
Internal::GetGlobalModeManager().Reset();
}
});
// Prime our array of default directories for loading and saving content files to
FEditorDirectories::Get().LoadLastDirectories();
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
if (!MainFrameModule.IsWindowInitialized())
{
if (FSlateApplication::IsInitialized())
{
MainFrameModule.CreateDefaultMainFrame(bStartImmersive, bStartPIE);
}
else
{
RequestEngineExit(TEXT("Slate Application terminated or not initialized for MainFrame"));
return 1;
}
}
// Prompt to update the game project file to the current version, if necessary
if ( FPaths::IsProjectFilePathSet() )
{
FGameProjectGenerationModule::Get().CheckForOutOfDateGameProjectFile();
FGameProjectGenerationModule::Get().CheckAndWarnProjectFilenameValid();
}
确保项目文件是最新格式。 检查项目文件名是否合法,并在必要时发出警告。
在 PreInitPreStartupScreen 函数中存在着使用 LaunchCheckForFileOverride 方法
SCOPED_BOOT_TIMING("LaunchCheckForFileOverride");
if (LaunchCheckForFileOverride(CmdLine, bFileOverrideFound) == false)
{
// if it failed, we cannot continue
return 1;
}
在 LaunchCheckForFileOverride 中会创建 PakFile 的文件接口
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("PakFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
在 ConditionallyCreateFileWrapper 会根据名称检索对应的文件接口类,并将其初始化 Initialize
static IPlatformFile* ConditionallyCreateFileWrapper(const TCHAR* Name, IPlatformFile* CurrentPlatformFile, const TCHAR* CommandLine, bool* OutFailedToInitialize = nullptr, bool* bOutShouldBeUsed = nullptr )
{
// 做一些条件判断
IPlatformFile* WrapperFile = FPlatformFileManager::Get().GetPlatformFile(Name);
if (WrapperFile != nullptr && WrapperFile->ShouldBeUsed(CurrentPlatformFile, CommandLine))
{
// 做一些条件判断
if (WrapperFile->Initialize(CurrentPlatformFile, CommandLine) == false)
{
// 做一些条件判断 设置 bOutShouldBeUsed 和 OutFailedToInitialize 的值
}
}
// 做一些条件判断
return WrapperFile;
}
对应的类就是 FPakPlatformFile
class FPakPlatformFile : public IPlatformFile
在 FPakPlatformFile::Initialize 中
// Extensions for file types that should only ever be in a pak file. Used to stop unnecessary access to the lower level platform file
ExcludedNonPakExtensions.Add(TEXT("uasset"));
ExcludedNonPakExtensions.Add(TEXT("umap"));
ExcludedNonPakExtensions.Add(TEXT("ubulk"));
ExcludedNonPakExtensions.Add(TEXT("uexp"));
ExcludedNonPakExtensions.Add(TEXT("uptnl"));
ExcludedNonPakExtensions.Add(TEXT("ushaderbytecode"));
IoStore 和 PackageStore) if (ShouldCheckPak())
{
ensure(CheckIoStoreContainerBlockSignatures(*GlobalUTocPath));
}
FIoDispatcher& IoDispatcher = FIoDispatcher::Get();
IoDispatcherFileBackend = CreateIoDispatcherFileBackend();
IoDispatcher.Mount(IoDispatcherFileBackend.ToSharedRef());
PackageStoreBackend = MakeShared<FFilePackageStoreBackend>();
FPackageStore::Get().Mount(PackageStoreBackend.ToSharedRef());
Pak 文件 // Find and mount pak files from the specified directories.
TArray<FString> PakFolders;
GetPakFolders(FCommandLine::Get(), PakFolders);
MountAllPakFiles(PakFolders, *StartupPaksWildcard);
FPakPlatformFile::MountAllPakFilesFCoreDelegates::OnMountAllPakFiles.BindRaw(this, &FPakPlatformFile::MountAllPakFiles);
FCoreDelegates::MountPak.BindRaw(this, &FPakPlatformFile::HandleMountPakDelegate);
FCoreDelegates::OnUnmountPak.BindRaw(this, &FPakPlatformFile::HandleUnmountPakDelegate);
FCoreDelegates::OnOptimizeMemoryUsageForMountedPaks.BindRaw(this, &FPakPlatformFile::OptimizeMemoryUsageForMountedPaks);
FCoreDelegates::OnFEngineLoopInitComplete.AddRaw(this, &FPakPlatformFile::OptimizeMemoryUsageForMountedPaks);
在 PreInitPostStartupScreen 函数中使用 OnMountAllPakFiles 事件来触发挂载 Pak 文件的逻辑
// Mount Paks that were installed during EarlyStartupScreen
if (FCoreDelegates::OnMountAllPakFiles.IsBound() && FPaths::HasProjectPersistentDownloadDir() )
{
SCOPED_BOOT_TIMING("MountPaksAfterEarlyStartupScreen");
FString InstalledGameContentDir = FPaths::Combine(*FPaths::ProjectPersistentDownloadDir(), TEXT("InstalledContent"), FApp::GetProjectName(), TEXT("Content"), TEXT("Paks"));
FPlatformMisc::AddAdditionalRootDirectory(FPaths::Combine(*FPaths::ProjectPersistentDownloadDir(), TEXT("InstalledContent")));
TArray<FString> PakFolders;
PakFolders.Add(InstalledGameContentDir);
FCoreDelegates::OnMountAllPakFiles.Execute(PakFolders);
// Look for any plugins installed during EarlyStartupScreen
IPluginManager::Get().RefreshPluginsList();
IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreEarlyLoadingScreen);
}
通过 FCoreDelegates::MountPak 可以单独挂载一个 Pak 文件,也可以为这个 Pak 设置优先级,如果没有设置优先级会通过 GetPakOrderFromPakFilePath(PakFilename) 计算优先级
int32 FPakPlatformFile::GetPakOrderFromPakFilePath(const FString& PakFilePath)
{
if (PakFilePath.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::ProjectContentDir(), FApp::GetProjectName())))
{
return 4;
}
else if (PakFilePath.StartsWith(FPaths::ProjectContentDir()))
{
return 3;
}
else if (PakFilePath.StartsWith(FPaths::EngineContentDir()))
{
return 2;
}
else if (PakFilePath.StartsWith(FPaths::ProjectSavedDir()))
{
return 1;
}
return 0;
}
换句话说,如果在 FCoreDelegates::MountPak 挂载的时候想要自己的这个 Pak 优先级最高,可以设置优先级为 4 即可
点击按钮, Cook 烘焙资源,在 项目路径/Saved/Cooked/Android_ASTC/ 中会存储本次烘焙的结果,并按照引擎和项目资产进行区分
| 烘焙 | 烘焙结果 |
|---|---|
![]() |
![]() |
随便找个文件夹将烘焙的一些路径拷贝出来,后续测试可用
在上级目录,也就是 E:\UEProj\Empty53\Cooked 中新建文本文件,用于标记那些文件需要被打入到 pak 中
E:\UEProj\Empty53\Cooked\Cooked\*.* 使用 *.* 表示文件夹内所有文件
然后就可以直接使用 UnrealPak.exe 这个 UE 提供的 Pak 工具进行文件打包
E:\UEEngine\UE_5.3\Engine\Binaries\Win64\UnrealPak.exe E:\UEProj\Empty53\Cooked\EmptyPak.pak -create=E:\UEProj\Empty53\Cooked\CookedFilePath.txt
上述命令格式就是:
UnrealPak.exe+Pak文件输出路径 +Cooked文件路径索引文件
使用 E:\UEEngine\UE_5.3\Engine\Binaries\Win64\UnrealPak.exe E:\UEProj\Empty53\Saved\Pak\Characters.pak -list 可以解析出 Pak 中文件列表