修改模板对象:类默认对象、组件模板与默认子对象
虚幻引擎创建 UObject 实例时,会从模板对象复制初始属性值。本文介绍如何在蓝图 Mod 运行时调整类默认对象、组件模板和默认子对象,以及如何处理关卡切换时的对象生命周期。
来源:Dmgvol / UE_Modding 原文。模板与 CDO 的技术背景可参考 Archetype and CDO。
虽然也可以使用 UAssetGUI 等工具直接替换资产来改变默认值,原文不推荐整体替换:它容易损坏,并可能与修改同一资产的其他 Mod 冲突。
三类模板对象
类默认对象(CDO)是特定类所有实例的默认模板,具有 RF_ClassDefaultObject 和 RF_ArchetypeObject 标志,名称带 Default__ 前缀,例如 Default__OreDistributorConfig_BP_C。修改 CDO 属性后,新建的该类实例会使用修改后的值。
在蓝图组件标签页添加的组件分别有一个组件模板,由 Simple Construction Script(SCS)管理,存储编辑器中配置的属性。其名称以 _GEN_VARIABLE 结尾,带 RF_ArchetypeObject 标志。Actor 生成时使用模板创建 SCS 组件,因此修改模板会影响之后新生成的 Actor 组件。
在 C++ 构造函数中通过 CreateDefaultSubobject 创建的组件称为默认子对象。其名称没有特别前后缀,但 outer 是名称以 Default__ 开头的 CDO,并带 RF_DefaultSubObject 与 RF_ArchetypeObject 标志。它们与组件模板类似,只是定义在原生代码中。
修改 CDO:获取类默认值
原文示例将蓝图中的结构体属性 config.NonOreYield 从 2 改为 100。
先在 FModel 中确认父类名称,示例为 /Script/SystemCore.OreDistributorConfigMule。在蓝图中使用 Get Class Defaults 节点,选择父类并勾选要编辑的变量。最好在 Construction Script 中尽早修改默认值。
结构体可用 Set members in <struct name> 只设置目标成员,无需拆解后重建整个结构体。其他属性类型可使用对应的 Set <type> (by ref) 节点。
如果值已改变但游戏效果没有变化,可能是游戏在 Mod 修改前已经使用了默认值。例如核心系统可能先用 CDO 初始化,之后的修改不会追溯生效;但 Mod 初始化后再读取该 CDO 时会使用修改后的值。请结合后面的对象生命周期部分处理加载时机。
修改 CDO:获取实际对象引用
也可以取得实际对象引用。原文把逻辑封装为 Get CDO 宏,也可以直接放入蓝图,但不能放入函数,因为函数中不允许使用 Load Asset Blocking 节点。
该逻辑把对象类路径(如 /Game/Code/Systems/OreDistributorConfig_BP.OreDistributorConfig_BP_C)转换成带 Default__ 的路径(如 /Game/Code/Systems/OreDistributorConfig_BP.Default__OreDistributorConfig_BP_C),再阻塞加载并返回对象引用。阻塞加载会等待资产载入后才继续;异步加载太慢时,修改代码可能无法按预期生效。
此方法使用实际对象类引用,而不是第一种方法中的父类;需要时先在编辑器中创建该对象资产的替身。然后将引用转换为目标对象类型,即可修改属性。
修改组件模板
示例在 FastFloor_C 蓝图中,将静态网格组件的默认引用改为 Mod 自己的网格。修改的是模板,因此之后创建的对象会使用新的网格引用。
原文使用 Get Component Template 宏;逻辑也可以放在蓝图中,但不能放在函数中,因为其中使用了 Load Asset Blocking。
该方法与获取 CDO 对象引用相似,但还接收 Property Name,构造带 _GEN_VARIABLE 的路径,例如 /Game/GridActors/slimActors/FastFloor.FastFloor_C:SM_Bridge_Wooden_01_A3_GEN_VARIABLE。同样需要先在项目中创建对象资产的替身来取得类引用。
将引用转换为对应属性类型;示例转换为 StaticMeshComponent,再用 Set Static Mesh 设置 Mod 中的新网格。
以下为原文展示的修改前后效果。由于改动作用于模板,建筑模式中创建的对象也会使用该模板。另一种做法是遍历世界中的所有 Actor 并逐个修改,但每次创建新 Actor 都要重复,可能造成显著性能开销。
修改前:
修改后:
原教程还引用了两张对比图,但提供的源仓库中没有对应文件。
修改默认子对象
示例修改一个 Widget 组件默认子对象的背景颜色。目标资产为 Agent_C,其默认对象名为 Default__Agent_C,但父类是原生类 Prototype_Agent,因此应修改后者。路径需要遵循 Default__<base class name> 格式。
对象生命周期
资产对象上的模板(并非原生 /Script/ 类)可能在切换关卡或某些游戏实例事件中被垃圾回收。此时,先前修改可能恢复为资产中的默认值;如果 Mod 再次加载得不够早,游戏系统可能已经读取了旧值。
原文建议尽早修改默认值,并在需要跨关卡保留时,将对象加入 GameInstance 的 ReferencedObjects 硬引用数组,避免被垃圾回收。这个属性没有标记为 Visible 或 BlueprintReadWrite,因此获取方法取决于引擎版本。原生类模板通常无需这样处理;某些场景也可能不希望跨关卡保留修改。
原作者没有将添加引用的操作放进宏,因为并非所有场景都希望修改后的对象跨关卡保留;也可以考虑通过宏中的布尔选项控制这一行为。
UE4 中可将以下 Kismet 文本复制到编辑器以获取变量,再连接 Get Game Instance 并将对象引用添加到数组:
Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="K2Node_VariableGet_3"
VariableReference=(MemberParent=Class'"/Script/Engine.GameInstance"',MemberName="ReferencedObjects")
SelfContextInfo=NotSelfContext
NodePosX=688
NodePosY=224
NodeGuid=CB4818F84C2F00E9C6031387C6D5EF57
End Object
原文指出 UE5 中上述方法会因属性对蓝图不可见而无法编译;其所述做法是修改引擎,将属性标记为 Visible 和 BlueprintReadWrite,然后自行编译引擎。具体适用性需结合所用引擎版本核实。