UE5启动流程与结构解析
一、引擎启动流程详解
1.1 启动入口与平台特化
在 Windows 平台上,Unreal Engine 5 的执行始于标准 GUI 程序入口
WinMain
。该入口函数首先调用平台特化的
LaunchWindowsStartup
(位于
LaunchWindows.cpp
),随后进入通用的跨平台启动函数
GuardedMain
(定义于
Launch.cpp
)。其调用链条如下:
1 | WinMain → LaunchWindowsStartup → GuardedMainWrapper → GuardedMain → EnginePreInit → FEngineLoop::PreInit → FEngineLoop::PreInitPreStartupScreen |
1.2 GuardedMain 函数结构
GuardedMain
函数作为整个引擎生命周期的主控流程,其主要职责包括平台抽象层初始化、引擎子系统注册、核心模块加载、主循环驱动以及资源清理。简化伪代码如下:
1 | GuardedMain(const TCHAR* CmdLine) |
1.3 初始化子系统分层解析
- 命令行与环境配置:通过
FCommandLine
解析参数,初始化全局环境变量如GIsEditor
、GIsClient
。 - 平台与IO子系统:包括
FPlatformFileManager
构建虚拟文件系统层,配置加载器(GConfig
)以及多日志通道日志器(GLog
)。 - 模块加载与引擎实例化:
FEngineLoop::PreInit
会加载核心运行时模块(如 Core、CoreUObject、Engine),随后FEngineLoop::Init
生成UEngine
或UEditorEngine
实例。 - 世界初始化:通过
UEngine::Start()
加载默认世界并生成UWorld
实例,注册GameMode
、PlayerController
等运行时关键对象。
1.4 模式差异化执行路径
- 编辑器模式:实例化
UEditorEngine
,加载 Slate 编辑器模块和各种工具集。 - 独立运行模式:使用
UGameEngine
,仅保留运行时必要模块,避免加载额外编辑器负担。
1.5 原生窗口创建与生命周期管理
Unreal Engine 在 Windows 平台下使用 Win32 原生窗口进行渲染上下文承载和消息收集,其生命周期管理如下:
创建时机:在
FWindowsPlatformApplicationMisc::InitializeWindow()
(位于WindowsApplication.cpp
)被调用时创建原生窗口。该函数在FEngineLoop::Init()
完成渲染器初始化(RHI)后执行。具体过程包括:- 准备窗口类(
WNDCLASSEX
)并注册到系统。 - 调用
CreateWindowEx
或CreateWindowW
创建窗口句柄(HWND
)。
- 准备窗口类(
更新机制:主循环内调用
FWindowsApplication::PumpMessages()
轮询 Win32 消息队列,通过PeekMessage
/GetMessage
接收消息;再在TranslateMessage
与DispatchMessage
后,Win32 调用窗口过程WndProc
,进一步路由到FWindowsApplication::ProcessMessage()
。交换链与渲染上下文:与原生窗口关联的 RHI 交换链(DX11/DX12/Vulkan SwapChain)在窗口创建后初始化,并在每帧通过
SwapBuffers
或Present
交换前后缓冲区。销毁时机:当引擎接收到退出请求后,
FWindowsApplication::DestroyWindows()
会依次遍历所有HWND
,调用DestroyWindow
释放窗口资源;随后在GuardedMain
的退出阶段调用UnregisterClass
注销窗口类。
1.6 原生窗口与渲染管线关联
为了将 Win32 窗口与 GPU 渲染流程绑定,Unreal Engine 在 RHI 初始化阶段执行以下关键步骤:
- 创建渲染设备与命令队列:在
PlatformCreateDynamicRHI()
(如D3D12RHI.cpp
中)调用D3D12CreateDevice
创建ID3D12Device
,并初始化图形和显示命令队列(ID3D12CommandQueue
)。 - 窗口与交换链关联:随后调用
IDXGIFactory::CreateSwapChainForHwnd
(封装在D3D12RHI::CreateSwapChain()
)并传入之前创建的HWND
与命令队列,生成IDXGISwapChain3
对象。该交换链会管理前后缓冲区并负责与窗口表面同步。 - 渲染目标视图(RTV)绑定:在交换链创建后,RHI
会为每个缓冲区调用
CreateRenderTargetView
(RTV),并在每帧RHIPresent
时使用OMSetRenderTargets
将 RTV 绑定到管线输出合并阶段。 - 帧呈现:每次
RHIPresent
调用时,通过IDXGISwapChain3::Present
将渲染完成的后缓冲区呈现到原生窗口。
这样,游戏逻辑和 Slate 渲染均可通过 RHI 层透明地提交命令到与
HWND
绑定的交换链,实现最终画面输出。
二、模块化架构与加载机制
2.1 模块生命周期管理器 FModuleManager
FModuleManager
为模块化架构的核心调度器,负责模块的动态解析、生命周期控制与实例缓存。其提供以下能力:
- 加载:
LoadModule()
,LoadModuleWithFailureReason()
- 卸载:
UnloadModule()
- 访问:
GetModule()
,IsModuleLoaded()
模块以 IMPLEMENT_MODULE
宏形式注册,启动时触发
StartupModule()
,终止时调用
ShutdownModule()
。
2.2 模块元描述与阶段控制
模块的描述由 FModuleDescriptor
承载,定义于
.uproject
或 .uplugin
配置文件中,其关键字段如下:
Name
:模块唯一标识Type
:模块分类(Runtime、Editor、Developer 等)LoadingPhase
:加载时机(PreDefault、Default、PostEngineInit 等)
引擎启动阶段通过 LoadModulesForPhase()
自动分阶段解析和装配模块。
2.3 静态与动态模块机制对比
- 静态模块:构建时链接进可执行文件,通过
StaticallyLinkedModuleInitializers
注册初始化函数。 - 动态模块(DLL):运行时使用
FPlatformProcess::GetDllHandle()
加载,调用GetDllExport()
提取InitializeModule()
等符号以完成动态注册。
模块卸载遵循逆序清理原则,保障依赖顺序的一致性与资源完整回收。
2.4 模块加载与调用流程
在引擎启动阶段,以及运行时需要动态引入或卸载功能时,Unreal Engine 依赖 FModuleManager 结合项目和插件描述完成模块的发现、加载、初始化及调用。
2.4.1 模块发现与注册
- 启动时扫描:引擎启动时,FProjectManager 和
FPluginManager 分别读取
.uproject
、.uplugin
中的 FModuleDescriptor 列表,并将所有声明的模块按 LoadingPhase 分组。 - 静态注册:对于单片(Monolithic)构建模式,所有模块在编译时通过 IMPLEMENT_MODULE 宏将初始化委托注册到 StaticallyLinkedModuleInitializers 映射中;插件和项目模块也以同样方式嵌入可执行文件或主 DLL。
2.4.2 动态加载流程
调用 LoadModulesForPhase(Phase) 时,FModuleManager 枚举本阶段所有 FModuleDescriptor。
对于每个模块名,FModuleManager::LoadModuleWithFailureReason:
- 若为静态模块,直接从 StaticallyLinkedModuleInitializers 调用委托,返回 IModuleInterface 实例。
- 若为动态模块,使用 FPlatformProcess::GetDllHandle 在预定义路径(Engine、Project、Plugin 二进制目录)加载对应 DLL;再通过 GetDllExport 查找符号 "InitializeModule",执行返回的新模块实例。
将生成的 IModuleInterface 指针保存在 ModuleNameToInfo 映射,调用 StartupModule() 完成模块自身初始化逻辑。
广播 ModulesChangedEvent 通知其他子系统,如 UObject 加载器,注册由模块提供的类或服务。
2.4.3 引擎对模块的调用
- 接口查询:运行时代码可通过 FModuleManager::Get().GetModuleChecked(ModuleName) 获得已加载模块的接口引用。
- 服务注入:模块通常在 StartupModule 中向全局子系统注册服务(如渲染模块注册渲染工厂,网络模块注册网络驱动),引擎通过静态或虚函数调用这些接口完成对应功能。
- 生命周期管理:当模块完成其职责或需要热重载时,调用 UnloadModule(ModuleName) 会按逆序调用 ShutdownModule(),并释放 DLL 句柄。
2.4.4 热重载支持
- 编辑器模式和某些运行时插件支持 Hot Reload:在源码或插件代码修改后,调用 LiveCoding 或 RecompileInEditor 可以卸载旧模块并重命名加载新 DLL,FModuleManager 确保在重载前调用所有模块的 ShutdownModule,再重新执行加载和 StartupModule 过程。
三、引擎主循环机制剖析
主循环逻辑位于 FEngineLoop::Tick()
,该函数被
GuardedMain
持续调用,驱动引擎完成一帧游戏更新。其核心逻辑结构如下:
1 | while (!IsEngineExitRequested()) |
3.1 各子阶段功能拆解
- 世界 Tick:依序更新每个
UWorld
实例,包括其内部所有AActor
和组件。 - 子系统更新:物理仿真、动画控制器、AI 系统、音频引擎、GC 系统等依照特定顺序被调度。
在编辑器模式下,还需驱动多视口、多编辑器对象 Tick,进一步增加主循环复杂度。
四、核心运行时对象结构
4.1 UWorld:多子系统集成容器
UWorld
是游戏世界的运行时表示,其核心职责包含:
- 管理
ULevel
和动态加载的 Streaming Levels - 管理所有
AActor
实例的生命周期与调度 - 提供时间流控制(World Time、DeltaTime、Pause 等)
- 持有所有关键子系统的引用,如
UPhysicsScene
、UNavigationSystemV1
、UAIController
等 - 关联
AGameModeBase
、APlayerController
、AGameState
等游戏规则对象
其结构设计支持多个世界并行存在。
4.2 AActor:游戏对象原语单元
AActor
是所有可交互、可放置、可网络同步实体的基类。其设计支持以下关键能力:
- 空间坐标系定义(Transform)
- Tick 生命周期方法:
BeginPlay()
→Tick()
→EndPlay()
- 支持组件组合系统,通过
UActorComponent
实现功能模块化 - 支持网络属性同步与远程函数调用(RPC)机制
- 支持蓝图与 C++ 混合开发与扩展
常见子类如
APawn
、ACharacter
、AStaticMeshActor
等均继承自 AActor
。
4.3 组件化与解耦设计
- UActorComponent 是功能原子单元,可被多个 Actor 复用
- SceneComponent 派生类(如 Mesh、Camera、Light)具备空间信息
- 支持运行时动态添加、编辑器中组合,可提高模块内聚性与解耦能力
组件体系提升了可扩展性、降低逻辑冗余并促进代码复用,是 UE 面向数据驱动设计的重要体现。
五、外部输入处理机制(Windows 平台)
5.1 平台消息获取与分发
在 Windows 平台上,引擎通过 Win32 API 接口获取原生消息队列事件(如
WM_MOUSEMOVE
, WM_LBUTTONDOWN
,
WM_KEYDOWN
, WM_INPUT
等)。FWindowsApplication::PumpMessages()
会在主循环中调用
TranslateMessage
与 DispatchMessage
,并在
WndProc
中将消息转发给
FWindowsApplication::ProcessMessage()
。
1 | MSG Msg; |
5.2 消息处理与转换
ProcessMessage(HWND Window, uint32 Message, WPARAM wParam, LPARAM lParam)
会根据消息类型调用对应的处理函数,例如:
- 鼠标事件:
ProcessMouseButtonDown/Up
,ProcessMouseMove
,ProcessMouseWheel
。 - 键盘事件:
ProcessKeyDown/Up
,ProcessKeyChar
。 - 原始输入(Raw Input):
WM_INPUT
对触摸、手柄等应用多平台统一处理。
每个处理函数会构建对应的 Slate 事件对象(FPointerEvent
,
FKeyEvent
)并调用
MessageHandler->OnMouseButtonDown()
或
OnKeyDown()
等接口。
5.3 Slate 层事件分发
FSlateApplication
作为统一 UI 框架入口,通过
FSlateApplication::ProcessDeferredEvents()
聚合并投递从平台层上报的事件。事件处理流:
- 平台层产生的原始事件通过
FGenericApplicationMessageHandler
回调注册到 Slate。 FSlateApplication::PumpMessages()
中调用ProcessMessageQueue()
,将消息排入 Slate 内部队列。- 在
FSlateApplication::Tick()
阶段,遍历事件队列,调用RoutePointerEvent
或RouteKeyEvent
,将事件分发给焦点窗口和对应 Widget。
5.4 引擎输入子系统
Slate 处理完 UI
输入后,会根据配置将输入路由至引擎输入子系统(UPlayerInput
),生成
FInputKey
, FInputAxis
或
FInputTouch
数据。主要流程:
FSlateApplication
调用FInputProcessorSlate::ProcessKeyDownEvent
或对应方法,将键盘/鼠标事件转为UPlayerInput
的调用。UPlayerInput
根据项目DefaultInput.ini
中的映射,将物理键或控制器按钮映射为游戏内抽象的输入动作(动作)与轴(Axis)。APlayerController::InputKey
或InputAxis
接收这些事件,进一步调用绑定到 Actor 的UInputComponent
中的委托。
5.5 控制器与触摸支持
- 控制器:Windows 使用
XInput(
WindowsApplication.cpp
中的FWindowsControllerInterface
)轮询手柄状态,产生FControllerState
并转为FInputKey
。 - 触摸屏:触摸事件通过 Win32 的触摸输入
API(
WM_TOUCH
),并在ProcessMessage
中解析为FPointerEvent
,最终传递给 Slate。
5.6 整体事件流示意
1 | Win32 Message Queue |
六、主要UE5模块概览
在整体架构中,UE5 由若干基础模块和功能模块组成,每个模块在引擎启动和运行时通过 FModuleManager 驱动加载,并在引擎生命周期内被相应子系统调用。以下为关键模块及其设计结构、核心职责和驱动方式:
6.1 核心基础模块
- Core:提供跨平台底层功能,包括内存管理、字符串与容器模板、文件系统接口。以静态方式链接,最先被加载,由
FEngineLoop::PreInit
驱动。 - CoreUObject:UObject 系统与反射框架实现,管理对象生命周期、序列化与垃圾回收。通过静态注册委托加载,StartupModule 中初始化反射元数据。
6.2 引擎功能模块
- Engine:GameFramework 核心,管理世界(UWorld)、GameMode、Actor 生命周期和场景更新。依赖 CoreUObject,在 PreDefault 阶段加载,StartupModule 注册世界管理器。
- RenderCore:封装渲染流水线基础接口,如命令缓冲、资源管理。作为渲染子系统前置模块,在 Default 阶段加载,渲染线程启动时被绑定。
- RHI:渲染硬件抽象层,提供对 DirectX、Vulkan、Metal
等后端的统一接口。动态模块,根据平台在 Default
阶段载入,渲染初始化流程中调用
CreateRHI
。
6.3 UI 与输入模块
- SlateCore:Defines 基本 UI 树结构、事件处理和布局算法。作为静态模块在 PreDefault 阶段加载,由 FSlateApplication 实例驱动。
- Slate:UI 渲染与绘制实现,依赖 SlateCore 和 RHI,在 PostEngineInit 阶段加载,StartupModule 中注册渲染器。
- UMG (UMGEditor):基于 Slate 的可视化 UI 编辑与运行时模块,Editor 版在 Editor 阶段加载,Runtime 版在 Default 阶段加载,由 WidgetReflector 驱动。
- InputCore:定义键位与轴映射基础数据结构。静态加载,
UPlayerInput
在世界 Tick 前自动初始化并回调映射。
6.4 网络与游戏系统模块
- Networking:核心网络协议与封包实现(Sockets、LowLevelNet)。动态模块,Default
阶段加载,在
NetDriver
初始化时被调用。 - OnlineSubsystem:平台在线服务接口(如 Steam、Epic
Online Services)。插件形态,运行时根据配置载入,对应子系统在
WorldInit
时注册服务。 - GameplayAbility:提供技能(Ability)与效果(Effect)系统框架。模块在
PostDefault 阶段加载,StartupModule 中注册
UAbilitySystemComponent
工厂,GameMode 或 Actor 在构造时创建组件实例。
6.5 工具与扩展模块
- Editor:编辑器核心功能集合,在 Editor 阶段加载,为编辑器注入菜单、工具窗口与自定义命令。
- BlueprintGraph:蓝图可视化脚本支持,Editor 模式下加载,由蓝图编译器和可视化编辑器驱动。
- LiveCoding:支持热重载的模块,运行时监听文件变化,在插件重载流程中被 FModuleManager 调用 ShutdownModule 和 StartupModule。
七、UE5 多线程架构
UE5 在运行时启动时,会创建多个专职线程来处理不同的子系统,以保证性能与资源利用。以下是主要线程的创建、更新与销毁位置及职责说明:
7.1 主要线程列表
- 游戏主线程 (Game Thread)
- 渲染线程 (Render Thread)
- RHI 线程 (RHI Thread)
- 任务图线程 (Task Graph Threads)
- 异步加载线程 (Async Loading Thread)
- 额外子系统线程(物理、音频等)
7.2 游戏主线程
- 创建时机:在
GuardedMain
内启动后,即进入FEngineLoop::Init
后恢复到主线程上下文。 - 更新:每帧由
FEngineLoop::Tick()
驱动,处理游戏逻辑、UWorld Tick、Actor Tick 等。 - 销毁:当
IsEngineExitRequested()
为真退出主循环后,主线程在GuardedMain
中执行EngineExit
清理并终止进程。
7.3 渲染线程
- 创建时机:在 RHI 初始化阶段(如
D3D12DynamicRHI::Init()
)调用FRHICommandContext::InitializeResources()
时通过FRunnableThread::Create
启动。 - 更新:在每帧渲染提交阶段,由
FRenderCommandFence
和FRHICommandList
在渲染线程上下文中提交绘制命令。 - 销毁:在
D3DRHI::Shutdown()
或通用RHIExit()
中调用FRunnableThread::Kill
并释放线程对象。
7.4 RHI 线程
- 创建时机:与渲染线程类似,部分平台(如 Vulkan)在
CreateRHI
后为异步命令提交启动独立 RHI 线程。 - 更新:负责管理底层驱动命令队列、Fence 同步与交换链 Present 调用。
- 销毁:在 RHI 退出流程中依次停止并销毁。
7.5 任务图线程
- 创建时机:在
FTaskGraphInterface::Startup()
中,通过FTaskGraphInterface::Get().Startup()
启动一组后台线程。 - 更新:按需执行
FGraphEvent
调度的任务节点,如资源加载、AI 逻辑、物理仿真子任务。 - 销毁:在引擎退出阶段
FTaskGraphInterface::Shutdown()
中回收所有任务线程。
7.6 异步加载线程
- 创建时机:在引擎初始化阶段
FAsyncLoadingThread::Init()
中,通过FRunnableThread::Create
启动用于包/资源加载。 - 更新:不断读取
FAsyncLoadingThread
的请求队列,在后台加载资产并在完成时通知主线程。 - 销毁:在
FAsyncLoadingThread::Shutdown()
中停止线程并清理队列。
7.7 其他子系统线程
- 物理线程:如
FPhysScene::InitPhysScene()
可创建用于并行物理仿真的线程。 - 音频线程:在
FAudioDevice::Init()
中创建,用于音频混合与解码。
八、反射系统
Unreal Engine 的反射系统(Reflection System,又称 Property System)是 C++ 语言在运行时缺乏本地支持的情况下,为实现运行时类型查询、序列化、垃圾回收、网络复制及蓝图交互而设计的通用机制 。该系统由 Unreal Header Tool(UHT)在编译时生成元数据,并在运行时通过静态注册与 FArchive 等组件提供完整的元信息访问。
8. 1 核心元类:UObject 与 UClass
- UObject 是所有受反射支持对象的基类,定义于Engine/Source/Runtime/CoreUObject/Public/UObject/Object.h。它承载了 GetClass()、Serialize()、垃圾回收标记等核心方法。
- UClass 是 UObject 类的元类,描述一个具体 UObject 派生类型的属性、函数列表和构造器。每个反射类在运行时都对应一个唯一的 UClass 实例,并保存于全局类注册表中。
8. 2 注解与 UHT 生成
- UCLASS():标记一个类使其参与反射,生成对应的 UClass 元数据。
- USTRUCT():标记一个 struct 参与反射,生成 UStruct 元数据。
- UPROPERTY(...):标记成员变量参与属性反射,生成 FProperty 元数据。
- UFUNCTION(...):标记成员函数参与方法反射,生成 UFunction 元数据。
所有宏定义与 UHT 针对这些标记生成的头文件,位于
8. 3 反射元数据对象
- UStruct / UClass:分别保存结构体和类的字段(PropertyLink 链表)、父类指针、元数据(MetaData)等。
- FProperty:所有属性的基类,子类如 FIntProperty、FStructProperty、FArrayProperty 等实现具体序列化与访问接口,定义于 Property.cpp。
- UFunction:保存函数签名、参数列表与可调用指针,用于在蓝图或网络复制时动态调用。
- UEnum:保存枚举类型信息,实现编辑器下枚举面板数据填充。
8. 4 反射注册机制
- 编译时生成
UHT 根据注解宏解析 C++ 源码,生成
1 | static void StaticRegisterNativesUMyClass(); |
这些函数最终注册到 Z_CompiledInDeferFile 数组中,延迟于运行时统一调用。
- 静态初始化
在模块加载(IMPLEMENT_MODULE)时,FModuleManager 调用 StartupModule,触发 Z_CompiledInDeferFile 中的 FRegisterCompiledInInfo,自动注册所有 UClass、UStruct、UEnum 等到全局注册表。
- 运行时访问
- UMyClass::StaticClass() 返回对应的 UClass*,可用于动态创建实例或做类型判断。
- MyObject->GetClass() 返回实例的 UClass*,支持 IsA()、Cast<>() 等运行时安全转换。
8.5 运行时类型信息
- StaticClass / GetClass
1 | UClass* AMyActorClass = AMyActorClass::StaticClass(); |
前者通过模板实现,后者从 UObject 基类获取实例类型指针,二者均依赖全局注册表。
- 类型查询与转换
UObject::IsA(UClass*) 和 Cast
- 动态创建对象
UE5中动态创建对象主要分为两类场景:
- 创建普通UObject派生类(非Actor对象)
使用 NewObject1
2
3// 假设存在一个反射类 UMyObject : public UObject
UClass* MyClass = UMyObject::StaticClass(); // 获取UClass
UMyObject* MyObj = NewObject<UMyObject>(GetTransientPackage(), MyClass);
- 创建Actor派生类(需存在于游戏场景中)
Actor必须通过UWorld::SpawnActor()方法生成,且需要指定位置和旋转信息。
代码示例: 1
2
3
4
5// 假设存在一个反射类 AMyActor : public AActor
UClass* MyActorClass = AMyActor::StaticClass(); // 获取UClass
FVector SpawnLocation = FVector(100.0f, 100.0f, 100.0f);
FRotator SpawnRotation = FRotator(0.0f, 0.0f, 0.0f);
AMyActor* MyActor = GetWorld()->SpawnActor<AMyActor>(MyActorClass, SpawnLocation, SpawnRotation);
- 动态获取UClass的三种方法
若需通过字符串类名动态获取UClass,需结合反射系统:
- 使用 FindClass() 函数
1 | FString ClassName = TEXT("MyProject.MyObject"); |
- 限制:类必须已在内存中加载(如被蓝图引用或代码显式加载)
- 使用 FSoftClassPath(推荐)
1 | FSoftClassPath ClassPath(TEXT("/Game/Blueprints/MyActor.MyActor_C")); // 蓝图类路径 |
- 优势:支持异步加载和热重载,适用于蓝图类。
- 路径格式:/Game/Path/To/Asset.AssetName_C(蓝图类需加_C后缀)。
- 通过静态类名直接获取
1 | UClass* TargetClass = LoadClass<UObject>(nullptr, TEXT("/Script/MyProject.MyObject")); |
- 适用场景:已知类的完整名称(C++原生类的路径格式为/Script/ProjectName.ClassName)。
九、串行化系统
9.1 什么是串行化?
串行化(Serialization)是将对象数据转换为字节流(用于存储或传输)的过程,反串行化(Deserialization)是将字节流还原为对象的过程。在 UE5 中,串行化是构建以下系统的基石:
- 关卡和资源的加载与保存(.uasset、.umap)
- 对象的网络复制(Replication)
- 蓝图与编辑器属性持久化
- SaveGame 系统
- GC 跟踪对象引用
9.2 核心组件概览
模块 | 作用 | 核心类/结构 |
---|---|---|
Archive 系统 | 底层读写抽象 | FArchive(抽象基类) |
Property System | 属性元数据反射与逐成员序列化 | FProperty 及其子类 |
Linker 系统 | 资源级别的加载/保存管理器 | FLinkerLoad, FLinkerSave |
Package 系统 | uasset/umap 资源的封装与版本控制 | UPackage, FPackageFileSummary |
Object Serializer | 对象级别的序列化逻辑 | UObject::Serialize()、FStructuredArchive |
9.3 底层核心类详解
- FArchive - 串行化的抽象基类 定义于:Runtime/Core/Public/Serialization/Archive.h
1 | class CORE_API FArchive |
特点: - 所有读写行为都通过重载 << 操作符完成 - 可被继承形成不同上下文的读写器,如: - FMemoryReader / FMemoryWriter:对内存块操作 - FArchiveFileReader / FArchiveFileWriter:对文件操作 - FStructuredArchive:支持结构化序列化(分组、字段名等)
- FProperty - 元属性序列化
每个 UCLASS / USTRUCT 中声明了 UPROPERTY 的变量,会对应一个 FProperty 对象,自动遍历并串行化。
核心接口:
1 | virtual void SerializeItem(FArchive& Ar, void* Value, void const* Defaults) const; |
派生类示例:
- FIntProperty:int 类型字段
- FStructProperty:嵌套结构体字段
- FArrayProperty:TArray 类型字段
1 | FProperty* Property = ...; |
- UObject::Serialize() - 对象级别的自定义序列化
定义于 Object.cpp: 1
2
3
4
5virtual void UObject::Serialize(FArchive& Ar)
{
// 引擎默认会遍历属性链表并调用 SerializeItem
Super::Serialize(Ar);
}
- FLinkerLoad / FLinkerSave - 资源串行化入口
FLinkerLoad:用于从 .uasset/.umap 文件中加载对象 FLinkerSave:用于保存对象到磁盘文件
关键函数: 1
2void FLinkerLoad::SerializeExport(UObject* Object);
void FLinkerSave::SavePackage();
9.4 结构化序列化格式:FStructuredArchive
UE5 引入了新的结构化序列化 API,替代传统的线性 FArchive,提供更强的稳定性与版本支持。
核心结构:
1 | FStructuredArchive Archive(UnderlyingArchive); |
优势:
- 支持字段名
- 支持嵌套记录
- 更好的人类可读性(对 JSON/YAML 友好)
- 支持版本差异(Field Skipping)
9.5 序列化文件格式:.uasset 与 FPackageFileSummary
所有资源(蓝图、纹理、关卡等)最终都被存储为 .uasset 或 .umap 文件。其头部格式由以下结构描述:
1 | struct FPackageFileSummary |
9.6 实际序列化流程(加载流程图)
1 | LoadPackage() |
9.7 SaveGame 系统
SaveGame 系统 是一套用于将游戏中的状态(如玩家属性、关卡信息、物品等)序列化为磁盘文件,并在需要时恢复(反序列化)这些状态的机制。它提供了一个 高层封装的方式来保存和加载游戏数据,通常用于存档、存盘、断点续玩等场景
使用示例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14USTRUCT(BlueprintType)
struct FPlayerSaveData
{
GENERATED_BODY()
UPROPERTY()
int32 Level;
UPROPERTY()
float Health;
UPROPERTY()
FString PlayerName;
};
9.8 版本控制:FCustomVersion
UE 支持多版本资源兼容,通过 FArchive::CustomVer() 查询:
1 | int32 Version = Ar.CustomVer(FMyPluginVersion::GUID); |
9.10 字节序
- 什么是字节序(Endian)?
- Little Endian(小端):低位字节在前(低地址)
- Big Endian(大端):高位字节在前(低地址)
UE 的主机平台(如 Windows 和 Linux)一般使用 小端 存储,因此 .uasset 文件默认也使用小端格式
- 核心处理类:FArchive
UE 中字节序的读写是通过 FArchive 抽象类处理的。派生类中会根据平台和目标字节序做转换。
关键成员变量:
1 | bool FArchive::ForceByteSwapping; |
核心逻辑:
每个派生类在读写整数等多字节数据时,都会调用如下代码来决定是否做字节翻转:
1 | void FArchive::ByteSwap(void* V, int32 Length) |
- 保存时的字节序
默认行为:
- UE 保存 .uasset 文件时通常 不做字节翻转(即保存为主机平台字节序,小端)。
- 例如 Windows 保存时使用小端格式,且不会设置 ForceByteSwapping 为 true。
可选配置:
UE 支持强制以大端格式保存资源(用于跨平台),但一般只在构建某些平台的资源包时启用,比如:
1 | FArchive& Ar; |
- 加载时的字节序检测
文件头中的魔数(Magic Number) UE 使用 FPackageFileSummary::Tag 字段中的魔数来判断文件是否需要字节翻转。
1 |
当加载 .uasset 时,UE 首先读入前 4 字节作为 Tag,然后判断是否为正常魔数或反转魔数, 这个魔数存在于文件最前面,即 FPackageFileSummary 的开头部分。
十、总结
通过以上模块划分与驱动说明,可以看到 UE5 通过 FModuleManager 实现高度模块化架构,各模块在不同加载阶段注册初始化,并在引擎生命周期中由相应子系统调用,保证了功能隔离与灵活扩展。 通过上述分析,我们梳理了 UE5 在 Windows 平台下从操作系统原生消息到游戏逻辑回调的完整输入处理管道。