GameFeature功能使用备忘

GameFeature功能使用备忘

主要概念

加载状态

  • Installed:代表“内容在磁盘上/已下载到位、系统允许你去装载它”。但此时 Pak 还可能没 mount,所以 UE 的资产系统未必能通过路径找到它的 Content。
  • Mount(挂载):是让 Pak/目录进入虚拟文件系统,使得 /PluginName/... 之类的资源路径变得可见。只有 mount 之后,很多资源引用(尤其硬引用链、以及软引用的实际 Load)才更可能成功。

加载顺序流程图

1
2
3
4
5
flowchart LR
I[Installed\n内容已在磁盘/可安装] --> R[Registered\n已发现并注册 GameFeatureData]
R --> M[Mount\nPak/目录挂载\n资源路径可见]
M --> L[Loaded\n资产/类可解析与加载]
L --> A[Active\n执行 Actions\n玩法逻辑生效]

GameFeatures 插件的 4 个 CurrentState

这四个状态本质上是 GameFeature 插件状态机 的关键里程碑,用来描述一个 GFP 从“可发现”到“可用/生效”的过程;它们也直接决定了 跨 GFP 资产引用 在运行时是否会因为未挂载/未加载而失败。

  1. Installed(已安装/可用)
  • 含义:插件对系统来说“存在且可被安装/卸载管理”。如果是可选下载内容(DLC/Chunk/可选 Pak),此状态通常表示已完成安装步骤。知道有光盘,但没读取光盘。
  • 你能依赖什么:还 不保证 Content 已 mount,所以此时去加载资产路径通常仍不可靠。
  1. Registered(已注册/已发现)
  • 含义:插件已被识别并完成注册;对应的 GameFeatureData 能被找到,系统知道“这个 GFP 有哪些 Actions/规则”。光盘插入了光驱,可以读取它,但是里面啥不知道。
  • 你能依赖什么:一般仍 不保证资产已可加载(很多项目里 mount 发生在后续阶段或紧随其后),但可以进行“声明式”的准备(例如建立要执行哪些 Actions 的清单)。
  1. Loaded(已加载)
  • 含义:插件内容已准备到“可以加载资产/类”的程度(典型情况下包括 Pak 已 mount、资产包路径可解析)。进入了游戏,但是游戏功能没用。
  • 你能依赖什么:此时 软引用/硬引用的目标资产才有较大概率能成功解析与加载;跨 GFP 的 Content 引用若要求 B(另外一个GFP插件资产) 的资产可用,至少需要 B 达到 Loaded
  1. Active(已激活/已生效)
  • 含义:GameFeatureData 中的 Actions 已实际执行并对游戏世界产生效果(例如添加组件、注册能力/输入、注入数据、启用系统等)。游戏的玩法已经加载
  • 你能依赖什么:除“资产可加载”外,还能依赖“玩法逻辑已经注册/启用”。跨 GFP 依赖如果不仅是资产,还需要 B 的系统在运行时提供服务(比如注册表、Subsystem、组件注入),通常要求 B 达到 Active

三种“引用”

  1. 资产硬引用(Hard Reference)

    • 例:材质实例 MI 直接指定另一个 GFP 里的 Parent Material;蓝图默认值直接指向外部资产等。
    • 特点:会形成固定的加载依赖链;如果目标资产不可用,会在加载时出问题。
  2. 资产软引用(Soft Reference)

    • 例:TSoftObjectPtr / Path 引用;Primary Asset Id;DataAsset 内记录路径但不立刻加载。
    • 特点:只有在解析/加载时才需要目标资产可用;更适合“按需加载”。
  3. 代码/模块依赖(C++ Module Dependency)

    • 例:A*.Build.cs*.uplugin 里把 B 模块作为依赖。
    • 特点:属于编译期/链接期依赖;通常意味着“不可拆分”。

本文重点讨论 资产层(Content)跨 GFP 引用

GameFeatureAsset

GameFeatureAsset(通常为 GameFeatureData)是 GameFeature 插件的入口资产,用于声明该功能在 Registered/Loaded/Active 阶段要执行的 Actions、注入的组件/能力/数据,以及可选的依赖与卸载逻辑;运行时以它驱动插件状态机。

  • 每一个 GameFeature 插件至少需要一个 GameFeatureAsset 作为入口(通常放在 Content/ 目录下)。

GameFeaturesActions

image-20260224154743535

激活这个插件,就会执行的一些玩法。

GameFeaturesAssetManager

image-20260224155043010

开启这个插件虚幻会去加载的资源。

Rules是优先级问题。比如下载了部分,就可以进行游玩了。

lang: “zh-CN”

使用逻辑

开启插件需要的内容GameFeature、Modualr Gameplay。


允许 / 限制 / 不推荐:判定标准

允许(推荐)

  • GFP 内部自引用GFP A 引用 GFP A/Content 的资产。
  • GFP 引用引擎内容(如 Engine Content)在很多项目中是可接受的(取决于团队规范)。
  • GFP 引用“共享内容插件/基础内容包”(非 GFP 或作为明确基础依赖的 GFP),并确保它在游戏启动时就被挂载。

允许但不推荐(高耦合、易踩坑)

  • GFP A 引用 GFP B 的 Content,但没有明确保证 BA 之前被安装/挂载/激活。
    • 在编辑器里经常“看起来没问题”,但运行时按需加载时可能失败。
    • 会削弱 GFP 的可插拔性(A 不能独立启用/禁用)。

会被 GameFeatures 运行时逻辑“限制”(可能直接失败)

  • A 激活时就需要解析/加载 B 的资产,但 B 未挂载(pak 未 mount、plugin 未 install)
    • 常见表现:软引用加载失败、硬引用加载时找不到包、或触发意外的依赖加载顺序问题。

注意:这类“限制”通常不是“编译时报错”,而是 运行时加载链/挂载顺序导致的不确定性。


依赖关系流程图(Mermaid)

说明:

  • 实线:建议/允许
  • 虚线:允许但不推荐(除非你明确控制加载顺序或声明依赖)
  • 红色:运行时逻辑限制,可能失败(B 未挂载/未激活时被引用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
flowchart LR
subgraph A[GFP A]
AContent[A/Content 资产]
ACode[A/Source 代码]
end

subgraph B[GFP B]
BContent[B/Content 资产]
BCode[B/Source 代码]
end

subgraph S[Shared / Base]
SharedContent[共享内容插件/基础包 Content]
end

Engine[Engine Content]

%% 推荐/允许
AContent -->|允许(推荐)| AContent
AContent -->|允许(常见)| Engine
AContent -->|允许(推荐)| SharedContent

%% 允许但不推荐
AContent -.->|允许但不推荐:跨GFP资产引用| BContent

%% 运行时限制(条件)
AContent -->|限制:若B未mount/未activate则可能失败| BContent

%% 代码依赖(提示,不是本文重点)
ACode -.->|不推荐:A模块依赖B模块会导致强耦合| BCode

Debug

Debug方案

  • Reference Viewer 检查 A 的关键资产是否硬引用到 B。

    可以点击那个小眼睛,然后“ShowAssetPath”,来看每一个引用和被引用资产的路径。

  • 确认运行时:

    • A 激活前,B 是否已 Install + Mount + Activate
    • 若 B 未激活,A 是否仍能独立运行?
  • 若无法保证顺序:把共享资产上移到共享插件/基础包。

一个GameFeature引用另一个GameFeature的Content,但在UI上检查不出哪里有引用

表面上看起来这两个没有执行的节点人畜无害。

image-20260224142949027

image-20260224143018127

image-20260224143259155

但实际上: 它是从ABP_Main_Youan_DNA_C直接拷贝过来的两个节点。

编辑器的UI中并没有直接显示这两个节点一些信息(从哪里拷贝过来) 并且这个叫Livelinksubject的变量,在当前蓝图中不存在,所以该节点信息没有更新。 又因为这个节点没有接通,所以Compile的时候不会识别到这个问题报错。 所以导致引用的时候莫名其妙地应用了GameFeatures YouAn的内容。

这个故事告诉我们一个道理: 不要留下废的蓝图节点。

材质相关引用

材质/材质实例常见的跨 GFP 引用方式是:

  • MI_A(在 GFP A)以 M_B(在 GFP B)作为 Parent Material 或引用了 B 中的 Material Function / Texture。

这会产生 硬引用链,意味着:

  • A 的某些资产被加载时,B 的资产也必须可用;
  • 如果按需激活流程里 B 没有先 mount,A 的加载可能直接失败。

建议处理方式(按优先级):

  1. 把公共材质基类/材质函数/共享贴图迁移到共享内容位置

    • 例如:Plugins/SharedAssets/Content/...(普通内容插件)
    • 或建立一个“明确基础依赖的 GFP”(但它就成了所有依赖方的前置条件)。
  2. 保留跨 GFP 引用,但显式声明/保证加载顺序

    • 在你们的 GameFeature 管理逻辑中实现:激活 A 前先安装/激活 B。
    • 适用场景:A 与 B 本就打包为一组功能,无法独立启停。
  3. 改为软引用 + 激活时解析

    • 让 A 不在编辑期形成硬引用链;在激活阶段通过路径/Primary Asset 解析并加载。

资料