中EN
概念页

资产管理与异步加载

Asset Management & Async Loading

UE 资产默认按引用自动加载:硬引用 (Hard Reference) 会让被引用资产随引用者一起加载,不加控制就可能在启动时加载全部内容。软引用 (Soft Reference,TSoftObjectPtr/FSoftObjectPath) 只存路径,配合流式管理器 (Streamable Manager) 异步按需加载;资产管理器 (Asset Manager) 进一步把内容划分为主资产 (Primary Asset) 和次资产 (Secondary Asset),用资产包 (Asset Bundle) 管理加载、卸载、烘焙分块与内存审计。

硬引用与软引用

方式写法加载时机风险
直接属性引用(硬)UPROPERTY(EditDefaultsOnly) USoundCue* Sound;引用者加载时自动加载内存占用膨胀
构造期引用(硬)ConstructorHelpers::FObjectFinder构造时查找并加载失败则为 nullptr,后续访问崩溃,应断言
间接引用(软)TSoftObjectPtr<UStaticMesh> / TSoftClassPtr需要时手动加载忘记加载即为空
运行时字符串FindObject<>(只找已加载)/ LoadObject<>(找不到则加载)/ LoadClass<>(顺带校验类型)调用时同步加载可能卡帧

(Referencing Assets)

官方原话的警示是:硬指针属性引用的资产会在包含它的对象加载时被加载,如果不小心,可能在游戏启动时加载 100% 的资产。FSoftObjectPath 是一个存完整资产路径的结构体,在编辑器中显示得像普通对象指针,并正确处理烘焙与重定向;TSoftObjectPtr 本质上是包着 FSoftObjectPath 的 TWeakObjectPtr,模板参数限制编辑器里可选的类型。资产已在内存时 Get() 返回它,否则用 ToSoftObjectPath() 拿到路径去加载。(Asynchronous Asset Loading)

软引用与 GC 的弱引用 (TWeakObjectPtr) 是同一族概念:都不阻止对象被回收、都需要判空,见 垃圾回收与对象生命周期。

异步加载:FStreamableManager

下图展示一次异步加载的生命周期:请求发出后主线程继续运行,完成时回调委托;在回调之前管理器持有硬引用保证资产不被 GC,回调之后释放。

sequenceDiagram
  participant G as 游戏代码
  participant SM as FStreamableManager
  participant GC as 垃圾回收
  G->>SM: RequestAsyncLoad(路径数组, 委托)
  Note over G: 主线程继续,不阻塞
  SM->>SM: 后台加载资产
  SM-->>G: 全部加载(或缺失)后调用委托
  Note over SM,GC: 回调前持有硬引用,资产不会被回收
  G->>G: 在委托中 Get() 并自己保存硬引用
  SM-->>GC: 回调后释放引用

要点(Asynchronous Asset Loading):

  • SynchronousLoad 是简单的阻塞加载,对小对象可以,但可能让主线程卡顿过久;RequestAsyncLoad 异步加载一组资产,完成后调用委托(委托机制见 委托与 Tick 机制)。
  • 官方建议把 FStreamableManager 放在全局单例里(如 GameSingletonClassName 指定的对象)。
  • 坑:管理器只在回调前持有引用,回调后释放——想让资产常驻,必须在别处保存硬引用。
  • 同一套方法在开发版和烘焙后的设备上表现一致,不需维护两条代码路径。

资产注册表与对象库

想“找出满足条件的资产”而不加载它们时,用资产注册表 (Asset Registry):它存储资产元数据,给属性加 AssetRegistrySearchable 即可被查询,结果是 FAssetData。对象库 (ObjectLibrary) 按路径和基类收集一组已加载对象或未加载的 FAssetData,美术/策划往目录里加资产就会被发现,无需维护手工总表;查询后把 FAssetData 转成软路径再异步加载。(Asynchronous Asset Loading)

资产管理器 (Asset Manager)

Asset Manager 是编辑器和打包游戏中都存在的唯一全局对象,可被项目重写;它在保留 UE 松散包架构优点的同时,把内容划分为有意义的分块 (Chunk),并提供审计磁盘与内存占用的工具,服务于烘焙 (Cooking) 与分块 (Chunking)。(Asset Management)

概念说明
主资产 (Primary Asset)可由 Asset Manager 通过 FPrimaryAssetId(类型 + 名称,如 MyGameZoneTheme:Forest)直接操作;类需重写 GetPrimaryAssetId。默认只有关卡 (UWorld) 是主资产
次资产 (Secondary Asset)不直接管理,被主资产引用时由引擎自动加载
蓝图类主资产用 GetPrimaryAssetObjectClass 等带 “Class” 的函数取得类,可继承、可生成实例
非蓝图主资产(Data Asset)是类的实例而非类,访问更简单、更省内存,但不能被继承
资产包 (Asset Bundle)与主资产关联的命名资产列表,如在 TSoftObjectPtr 属性上写 meta=(AssetBundles="TestBundle");也可运行时用 FAssetBundleData 注册
Streamable ManagerAsset Manager 内部真正执行异步加载、用 Streamable Handle 保持对象在内存中;引擎中有多个

(Asset Management)

使用流程(Asset Management):

  1. 如需特殊功能,在 DefaultEngine.ini 的 [/Script/Engine.Engine] 下设 AssetManagerClassName 指向自定义类。
  2. 注册主资产:项目设置 Asset Manager 页的 Primary Asset Types to Scan、Directories to Exclude(排除测试资产)、Primary Asset Rules、Only Cook Production Assets(发售包里出现 DevelopmentCook 资产会报错);或在 StartInitialLoading 中调用 ScanPathsForPrimaryAssets,官方建议同类主资产放同一子目录以加快发现。
  3. 用 LoadPrimaryAsset(s) / LoadPrimaryAssetsWithType 加载并指定资产包,用 Unload* 卸载。
  4. 运行时动态数据可用 ExtractSoftObjectPaths 从结构体提取软引用,RecursivelyExpandBundleData 递归展开依赖。

继承 UPrimaryDataAsset 是最省事的起点,它已实现 GetPrimaryAssetId 与资产包数据的保存。(Asset Management)

实际项目中的用法与取舍

场景推荐
总会用到的小资产(UI 图标、默认材质)硬引用即可,简单可靠
只在特定情况出现的大资产(某把武器的高模、某关的 Boss)软引用 + 异步加载
道具、角色、地图等“目录型”数据主资产 + Data Asset + 资产包,按 UI/游戏中等状态分包加载
需要按条件查找资产注册表 / 对象库,先查元数据再加载
开放世界的关卡内容交给 世界分区 按距离流送

官方承认把直接访问内存的玩法代码改成异步加载需要时间,但回报是“卡顿少得多、内存低得多”。(Asynchronous Asset Loading)常见坑:在构造函数里大量 FObjectFinder 造成连锁加载;对软引用 Get() 前不检查是否已加载;在热路径上用 LoadSynchronous(增强输入 的官方示例即用同步加载映射上下文,对小资产可接受)。资产所在模块/插件的打包与依赖见 模块、插件与构建系统;对象路径与 UClass 的关系见 UObject 与反射系统。按需加载与渲染侧 细节层次 一样,都是用“只为当前需要的内容付费”换取内存与时间。

PBRT 的视角:解析期间异步创建场景对象(PBRT 4e C.2、C.3)

  • 并行解析:场景格式的 Import 指令表示被导入的文件可以与当前文件并行解析——导入文件结束时丢弃它对图形状态的修改,所以当前文件不必等待。每个导入文件有自己的 BasicSceneBuilder(复制当时的图形状态),共享同一个 BasicScene,因此 BasicScene 的方法都要加互斥锁。
  • 边解析边创建:WorldBegin 之后相机、胶片、采样器等不再改变,SetOptions() 用 RunAsync() 启动多个任务创建它们并立即返回,解析继续。理由:RealisticCamera 计算出瞳边界约需 1 秒,HaltonSampler 初始化随机排列约 0.1 秒。介质为每个介质维护一个任务表;取结果用 TryGetResult(),结果未就绪时会解锁、去做别的并行工作再加锁,从而避免持锁等待造成的死锁。
  • 图像纹理:读盘常是瓶颈,所以尽早异步加载;同一图像被多个纹理使用时只由一个任务读取,其余等待后从缓存获取。
  • 效果:作者称复杂场景的渲染开始时间常常比上一版快 4 倍。调用方应尽量推迟取结果,先做独立工作,减少 GetResult() 阻塞。
  1. Asset Management UE 5.8 文档
  2. Referencing Assets UE 5.8 文档
  3. Asynchronous Asset Loading UE 5.8 文档
  4. PBRT 4e source excerpt: chapter C (Processing the Scene Description)
  5. Pharr, Jakob, Humphreys《Physically Based Rendering: From Theory to Implementation》第 4 版(MIT Press, 2023)附录 C C.1–C.4 节与延伸阅读,pbr-book.org/4ed/Processing_the_Scene_Description

相关页面

游戏开发 · UE5垃圾回收与对象生命周期Garbage Collection & Object LifecycleUE 用标记-清除式的可达性分析管理 UObject 内存:从根集合 (Root Set) 出发沿反射可见的强引用遍历对象图,找不到的对象在下次垃圾回收 (Garbage Collection, GC) 时被删除。因此“谁持有 UPROPERTY 引用”决定了对象活多久;Actor 调用 Destroy() 只是请求销毁,真正释放仍取决于引用是否全部放开。游戏开发 · UE5UObject 与反射系统UObject & Reflection System虚幻引擎 (Unreal Engine) 的对象基类是 UObject。在 C++ 类、属性、函数上加 UCLASS/UPROPERTY/UFUNCTION 等宏,再由构建前的预处理工具虚幻头文件工具 (Unreal Header Tool, UHT) 扫描头文件生成代码,引擎就能在运行时“看见”这些类型——这就是反射系统 (Reflection System)。垃圾回收、序列化、编辑器面板、蓝图、网络复制都建立在它之上。游戏开发 · UE5模块、插件与构建系统Modules, Plugins & Build SystemUE 的代码由模块 (Module) 组成:每个模块是一个独立编译单元,用 [模块名].Build.cs 声明依赖。插件 (Plugin) 是可按项目启用/禁用的代码与内容集合,用 .uplugin 描述,可包含多个模块。虚幻构建工具 (UnrealBuildTool, UBT) 根据 .Target.cs 和 .Build.cs(而不是 IDE 的 .sln)编译,默认把模块编成 DLL 由一个可执行文件加载,也可整体链接成单体 (Monolithic) 可执行文件。游戏开发 · UE5增强输入Enhanced Input增强输入 (Enhanced Input) 是 UE5 默认启用的输入插件,把输入拆成四层:输入动作 (Input Action) 表示“玩家想做什么”,输入映射上下文 (Input Mapping Context) 规定“哪些按键在什么情境下触发它”,输入修饰器 (Input Modifier) 预处理原始值,输入触发器 (Input Trigger) 判定是否触发。上下文可在运行时按优先级增删,从而用数据资产而非代码处理情境切换、按键冲突与改键。游戏开发 · UE5世界分区World Partition世界分区 (World Partition) 是 UE5 的自动数据管理与基于距离的关卡流送 (Level Streaming) 系统:整个世界存为一个持久关卡,按可配置的运行时网格划分为单元格 (Cell),由流送源 (Streaming Source,默认是玩家控制器) 按距离加载/卸载。它配合一 Actor 一文件 (One File Per Actor, OFPA) 支持多人协作编辑,用数据层 (Data Layers) 按玩法状态切换内容,用分层细节层次 (Hierarchical Level of Detail, HLOD) 在远处以代理网格显示未加载的单元格。图形学细节层次Level of Detail细节层次 (LOD) 在物体对图像贡献变小时改用更简单的表示。LOD 系统由三部分组成:生成(简化或手工制作多个版本)、选择(按距离、投影面积等收益函数挑选)、切换(尽量减少跳变 popping)。LOD 主要减少顶点处理,也通过减少 quad 过度着色降低像素开销;进一步还可在预算约束下全局选择 LOD,实现时间关键(恒定帧率)渲染。游戏开发 · UE5烘焙、打包与 Zen 加载器Cooking, Packaging & Zen Loader打包是一组构建操作:编译 (Build) → 烘焙 (Cook,把资产转成目标平台格式) → 暂存 (Stage) → 打包 (Package) → 可选的部署 (Deploy)。UE5 的运行时加载器 Zen Loader 会在暂存阶段离线算好包与对象的依赖图,并把数据装进 .utoc/.ucas 容器(IoStore),由 I/O 分发器以很低的 CPU 开销定位数据块;.pak 只用于不常访问的零散文件。游戏开发类型对象Type Object类型对象 (Type Object) 用“一个类的不同实例”来表示不同的逻辑类型:例如不为每种怪物写一个子类,而是一个 Monster 类加一个 Breed(种族)类,每个 Breed 实例描述一种怪物,怪物只持有指向自己 Breed 的引用。这样新增或修改种类只需要改数据,不必重新编译;代价是要自己管理这些类型对象,而且给每种类型定义不同的“行为”会变难。游戏开发资产管线与工具链Asset Conditioning Pipeline & Tools游戏引擎的输入数据来自美术使用的数字内容创作 (DCC) 工具,但 DCC 格式几乎从不直接用于游戏,必须经过资产调理管线 (Asset Conditioning Pipeline, ACP) 导出、处理并按平台打包。围绕它还有世界编辑器、记录资产元数据的资源数据库,以及几种工具架构(独立工具、与引擎共享底层、编辑器内嵌于引擎、Web 界面),各有取舍。游戏开发资源管理器Resource Manager游戏由网格、材质、纹理、着色器、动画、音频、关卡布局、碰撞与物理参数等大量资源(也叫资产、媒体)构成。内存有限,所以引擎要保证每个资源在内存中只有一份(五个网格共用一张纹理时只载入一份)。每个引擎都有某种资源管理器:有的是统一集中的子系统,有的散布在多年间由不同人写的各个子系统中,但职责和要解决的问题是一样的。本页讲运行时部分:职责、文件组织与归档、文件格式、资源 GUID、资源注册表、生命周期(引用计数)和资源的内存管理;交叉引用与加载后初始化见 资源交叉引用与加载后初始化,离线工具链见 资产管线与工具链。游戏开发游戏世界的加载与流送Loading & Streaming Game Worlds世界加载系统负责两件事:把世界块和所需资源从磁盘读进内存的文件 I/O,以及这些资源的内存分配与释放;另外还要管理游戏对象的生成与销毁。GEA 按演进顺序介绍:一次只载一个关卡的栈式加载(LSR 资源压栈底),用小块气闸 (air lock) 区域实现基本无缝的过渡,再到把所有资产切成等大块、用块池分配器按关卡加载区域流送的细粒度方案(Naughty Dog);对象生成的内存问题有"不许运行时分配"、按尺寸分池、小内存分配器和内存重定位四种对策;存档则分检查点和随处存档(GEA4 卷 II 17.4)。游戏开发 · UE5UMG、Slate 与 Common UIUMG, Slate & Common UISlate 是 UE 平台无关的 C++ 声明式 UI 框架(编辑器即用它构建),UMG 是在其之上的可视化控件蓝图层(UE 5.8 API:UWidget 是所有暴露给 UObject 的被包装 Slate 控件的基类),Common UI 插件再在 UMG 之上补充多层界面、输入路由、手柄导航、平台按键图标和样式资产。官方 UMG 最佳实践强调事件驱动更新而非绑定/Tick、固定分辨率与 DPI 制作、用 Scale Box 而非 Render Transform 做永久缩放。综合游戏编程模式选型:解耦与性能Choosing Game Programming Patterns本页把《Game Programming Patterns》里的 19 个模式放到同一张取舍图上:大多数模式要么为了解耦(降低改代码时需要装进脑子里的知识量),要么为了性能(利用对数据和硬件的具体假设),而这两个目标经常互相拉扯。下面先讲全书的定位和 Nystrom 的架构观,再给出按问题选模式的路线和与 UE 的对照。游戏开发 · UE5委托与 Tick 机制Delegates & TickUE 有两种驱动游戏逻辑的方式:事件驱动——委托 (Delegate) 在某事发生时回调绑定的函数;轮询驱动——Tick 每帧(或按最小间隔)调用 Actor/组件的更新函数,并按 Tick 组 (Tick Group) 与物理模拟排序。官方建议能用定时器、委托就不用 Tick,必须 Tick 时用 Tick 组和 Tick 依赖保证顺序,避免“差一帧”问题。游戏开发多线程渲染与任务系统Multithreaded Rendering & Task SystemsCPU 时钟频率约在 2003 年停在 3.4 GHz 附近后,性能来自多核。应用阶段的优化一是让代码与内存访问更缓存友好,二是利用多核:多处理器流水线(时间并行,提高吞吐但增加延迟)、并行处理(空间并行,延迟更低)以及两者结合的任务系统;图形 API 用命令缓冲让多个线程并行录制绘制命令,新一代 API(DirectX 12、Vulkan、Metal)把更多责任交给应用以降低驱动开销。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原