Unreal Engine Mutable 生产级换装系统

UE Mutable 换装系统(三):装备数据表、资产规范与部件管理

建立装备 ItemID、Slot 占用、Mutable 参数映射、兼容规则、Fallback 和编辑器校验,形成可维护的数据合同。

总览

前两篇已经确定了系统边界和 Mutable 资源规划:Mutable 负责生成表现资源,换装系统负责数据契约、状态管理和异常回退。本篇进入数据层。生产项目里,UI 蓝图不能直接写 Slot_Upper = Jacket_A,存档也不能保存 Mutable 内部选项名。装备必须先被数据化,换装流程只能消费经过校验的数据。

本篇的核心目标是建立一套稳定的装备数据合同:策划能配,美术能查,程序能校验,运行时能从 ItemID 可靠地翻译成 Mutable 参数。

UE Mutable 换装系统(三):装备数据表、资产规范与部件管理 配图
数据合同蓝图:ItemID 经 Catalog 解析成装备数据,再转成 Slot、规则和 Mutable 参数。

本篇目标

  1. 为每件装备定义稳定的 ItemID、插槽、显示信息、资产引用和 Mutable 参数映射。
  2. 建立 Slot、占用规则、冲突规则、身体遮挡区域和默认回退装备。
  3. 让存档、网络同步、UI、背包都只依赖稳定业务 ID,不直接依赖 Mutable 内部选项。
  4. 让数据在编辑器阶段可验证,避免打包后才发现参数名、选项值或资源引用错误。
  5. 为第四篇的 BP_OutfitComponent 提供清晰输入:给定 ItemID,可以得到完整、可应用、可回退的装备定义。

非目标

  1. 本篇不实现点击装备后的蓝图执行流,流程实现放到第四篇。
  2. 本篇不讨论衣柜 UI、预览确认、取消回滚,UI 交互放到第五篇。
  3. 本篇不设计完整存档版本迁移,只保证数据结构能被存档系统引用。
  4. 本篇不把背包、商城、掉落系统纳入装备数据资产。换装数据只描述“这个外观如何应用到角色”。
  5. 本篇不直接讲 Mutable 图节点搭建,只规定它对外暴露的参数合同。

数据资产选型

生产项目推荐用 PrimaryDataAsset 承载单件装备数据,再用 Asset Manager 或注册表管理批量加载。DataTable 也能做,但当装备包含图标、预览图、软引用、标签、校验逻辑和编辑器工具时,DataAsset 更适合维护。

推荐拆成三类资产:

资产 职责
PDA_OutfitItem 单件装备定义,包含 ItemID、Slot、Mutable 参数、图标、兼容规则
PDA_OutfitSlotConfig 全局插槽定义,包含 Slot 顺序、默认装备、互斥组
PDA_OutfitCatalog 装备目录,提供 ItemID 到装备资产的索引,可由编辑器工具自动生成

最小项目可以先只做 PDA_OutfitItem,但一旦装备数量超过几十件,应该引入 PDA_OutfitCatalog。否则运行时很容易出现到处 Get Assets by Class 的临时逻辑,打包和 DLC 管理也会变得不可控。

核心数据结构

装备数据层至少需要四组结构:装备主体、Mutable 参数、插槽占用、兼容条件。

UENUM(BlueprintType)
enum class EOutfitParamType : uint8
{
    Enum,
    Bool,
    Int,
    Float,
    Color,
    Texture
};

USTRUCT(BlueprintType)
struct FOutfitMutableParam
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName ParamName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    EOutfitParamType ParamType = EOutfitParamType::Enum;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FString StringValue;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    bool BoolValue = false;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 IntValue = 0;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float FloatValue = 0.0f;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FLinearColor ColorValue = FLinearColor::White;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TSoftObjectPtr<UTexture2D> TextureValue;
};

StringValue 主要用于 Enum 选项,例如 Jacket_A。颜色、开关、数值不要塞进字符串里,否则蓝图侧会变成大量类型转换节点,编辑器校验也很难做严。

USTRUCT(BlueprintType)
struct FOutfitSlotClaim
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName SlotId;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    bool bPrimarySlot = false;
};

一件装备可以占用多个 Slot。比如连体衣主 Slot 是 Upper,但同时占用 Lower;长袍主 Slot 是 Upper,同时占用 TorsoOverlayLowerOverlaybPrimarySlot 用来告诉 UI 和状态系统:这件装备应该显示在哪个主要分类下。

USTRUCT(BlueprintType)
struct FOutfitCompatibilityRule
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FGameplayTagContainer RequiredTags;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FGameplayTagContainer BlockedTags;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FName> BlockedSlots;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FName> HiddenBodyRegions;
};

RequiredTags 适合表达职业、体型、阵营、性别、角色基准等条件。BlockedSlots 适合表达明确互斥,例如头盔屏蔽发型、全脸面具屏蔽眼镜。HiddenBodyRegions 是给 Mutable 裁剪参数使用的身体区域列表。

UCLASS(BlueprintType)
class UOutfitItemData : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName ItemId;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FGameplayTagContainer ItemTags;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FOutfitSlotClaim> SlotClaims;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FOutfitMutableParam> MutableParams;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FOutfitCompatibilityRule Compatibility;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TSoftObjectPtr<UTexture2D> Icon;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TSoftObjectPtr<UObject> PreviewAsset;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName FallbackItemId;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 DataVersion = 1;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    bool bEnabled = true;
};

FallbackItemId 不是给 UI 好看的字段,而是生产回退链路的一部分。装备资源缺失、下架、条件不满足时,系统需要知道该回到哪件默认装备,而不是让角色裸露或保留旧参数。

资产命名规范

装备资产和 Mutable 内部选项必须有不同命名边界。

对象 命名示例 稳定性
装备 ItemID eq_upper_jacket_a 对存档和网络稳定,不能随意改
DataAsset PDA_Outfit_Upper_Jacket_A 编辑器资产名,可迁移
Mutable 参数名 Slot_Upper 对代码和数据稳定,尽量不改
Mutable 选项值 Jacket_A Mutable 图内部选项,可通过数据迁移兼容
图标 T_UI_Icon_Upper_Jacket_A 表现资产,可替换
预览图 T_UI_Preview_Upper_Jacket_A 表现资产,可替换

关键原则:存档、网络、背包只保存 ItemID。Mutable 参数名和选项值是表现层映射,允许通过数据表修复和迁移。

装备数据示例

ItemId: eq_upper_jacket_a
DisplayName: 远征夹克
SlotClaims:
  - SlotId: Upper
    bPrimarySlot: true
  - SlotId: TorsoOverlay
    bPrimarySlot: false
MutableParams:
  - ParamName: Slot_Upper
    ParamType: Enum
    StringValue: Jacket_A
  - ParamName: Color_Primary
    ParamType: Color
    ColorValue: [0.62, 0.08, 0.04, 1.0]
  - ParamName: bClip_Torso
    ParamType: Bool
    BoolValue: true
Compatibility:
  RequiredTags:
    - Character.Body.Human
  BlockedTags:
    - Character.Body.Child
  BlockedSlots:
    - Cloak
  HiddenBodyRegions:
    - Torso
    - UpperArm
FallbackItemId: eq_upper_basic_shirt
DataVersion: 1
bEnabled: true

这个示例里,eq_upper_jacket_a 是系统级身份;Slot_Upper = Jacket_A 是 Mutable 应用参数。以后 Mutable 图把 Jacket_A 拆成 Jacket_A_OpenJacket_A_Closed,也应该通过装备数据或迁移表修复,不应该影响玩家存档里的 eq_upper_jacket_a

Slot 与部件管理

Slot 不只是 UI 分类,它是状态互斥和 Mutable 参数覆盖的单位。建议最小 Slot 集合如下:

Slot 用途
Upper 上衣、夹克、胸甲
Lower 裤子、裙装、腿甲
Shoes 鞋、靴子
Hands 手套
Head 头盔、帽子
Hair 发型
Face 面具、眼镜
Back 披风、背包
Accessory 小挂件或不影响主体的装饰

生产项目经常需要多 Slot 占用:

装备 主 Slot 额外占用 处理
连体衣 Upper Lower 装备后自动卸下裤子
全脸头盔 Head Hair, Face 隐藏发型和眼镜
长披风 Back TorsoOverlay 屏蔽部分外套叠层
长靴 Shoes LowerLegOverlay 裁剪小腿身体区域

不要让 UI 自己判断这些规则。UI 可以显示“会替换裤子”,但真正的判定必须在数据层和 BP_OutfitComponent 中完成。

Mutable 参数映射规范

MutableParams 描述“装备被激活时要写入哪些参数”。但还需要明确卸下装备时怎么恢复。推荐两种策略:

  1. 每个 Slot 都有默认装备。卸下等于装备默认装备。
  2. 每个 Mutable 参数都有默认值。应用完整 Outfit 时先写默认值,再写当前装备参数。

生产项目更推荐第一种:默认装备也是装备数据。这样默认衬衫、默认裤子、默认鞋都走同一套校验、加载和 Mutable 应用路径,后续存档和初始化不会出现第二套逻辑。

蓝图函数清单

数据层可以提供一个 BPL_OutfitData,专门做查询和校验。

函数 输入 输出 说明
GetItemDataById ItemId OutfitItemData, bFound 从 Catalog 查装备
GetDefaultItemForSlot SlotId ItemId 获取 Slot 默认装备
ResolveFallbackItem ItemId, Reason FallbackItemId 根据失败原因找回退装备
GetPrimarySlot OutfitItemData SlotId 找主 Slot,用于 UI 分类
GetClaimedSlots OutfitItemData SlotIds 获取所有占用 Slot
ValidateItemForCharacter ItemData, CharacterTags Result 检查 Required / Blocked Tags
ValidateMutableParams ItemData, CustomizableObject Result 编辑器或开发模式下检查参数存在
BuildSlotReplacementList ItemData, CurrentOutfit SlotsToClear 计算装备后要卸下哪些 Slot

PDA_OutfitCatalog 建议包含:

变量 类型 说明
AllItems TArray<TSoftObjectPtr<UOutfitItemData>> 全部装备软引用
ItemMap TMap<FName, TSoftObjectPtr<UOutfitItemData>> Cook 或编辑器生成的索引
SlotConfig TObjectPtr<UOutfitSlotConfig> Slot 全局配置
CatalogVersion int32 数据版本
bStrictValidation bool 开发环境是否严格报错

主流程:从 ItemID 到可应用数据

Input ItemID
 -> Query PDA_OutfitCatalog
 -> Check item exists and bEnabled
 -> Load PDA_OutfitItem if soft reference is not loaded
 -> Validate SlotClaims
 -> Validate Character Tags
 -> Validate blocked slots and occupied slots
 -> Resolve MutableParams
 -> Return Resolved Outfit Item

解析成功后,第四篇的 BP_OutfitComponent 才能更新状态并写入 Mutable。解析失败时,不应该直接返回空;系统需要给出明确失败原因和回退建议。

失败路径和回退

失败 常见原因 回退策略
ItemID 不存在 存档旧数据、装备下架、Catalog 未更新 查迁移表;失败则用 Slot 默认装备
DataAsset 加载失败 资源未 Cook、软引用路径错误 使用 FallbackItemId,同时记录错误
Slot 不合法 数据手填错误、SlotConfig 未维护 阻止装备,开发环境报错
RequiredTags 不满足 职业、体型、性别限制 UI 显示不可装备,运行时不改状态
BlockedTags 命中 装备与角色状态冲突 阻止装备或自动换默认装备
Mutable 参数不存在 参数改名、Mutable 图未同步 开发环境失败;线上跳过该参数并回退整件装备
Mutable 选项不存在 Child Object 删除或选项改名 使用迁移表;失败则回退默认装备
图标缺失 UI 资产漏配 使用默认图标,不影响换装
预览资源缺失 预览图漏配 UI 降级显示 3D 角色或占位图

线上策略要保守:表现资源缺失可以占位,核心参数缺失必须回退。不要让半套参数进入正式状态。

编辑器校验

生产项目建议给 UOutfitItemData 做 Data Validation,或者至少提供一个编辑器按钮 Validate Outfit Catalog。校验项包括:

  1. ItemId 不为空且全局唯一。
  2. SlotClaims 至少有一个主 Slot。
  3. 所有 Slot 都存在于 PDA_OutfitSlotConfig
  4. MutableParams.ParamName 存在于目标 CustomizableObject
  5. Enum 类型参数的 StringValue 是合法选项。
  6. Color、Float、Int 参数范围符合项目约束。
  7. HiddenBodyRegions 都在 KnownBodyRegions 内。
  8. FallbackItemId 存在,且不会形成循环回退。
  9. IconPreviewAsset 等 UI 必需资产按规则配置。
  10. 被标记为 bEnabled=false 的装备不能作为默认装备。

校验结果要输出具体资产路径和字段名。只提示“装备数据错误”没有意义,内容团队无法修。

验收标准

  1. 任意一件已启用装备都能通过 ItemID 从 Catalog 找到。
  2. 全部装备 ItemID 唯一,重名会在编辑器阶段报错。
  3. 每件装备至少有一个主 Slot,且 Slot 存在于 SlotConfig。
  4. 每件装备的 Mutable 参数名和 Enum 选项能在目标 Mutable 对象中找到。
  5. 装备缺失、下架或条件不满足时,可以稳定回退到默认装备。
  6. 存档和网络层只需要保存 ItemID,不需要保存 Mutable 内部参数。
  7. 连体衣、头盔隐藏发型、多 Slot 占用等复杂装备能由数据表达,不需要 UI 写死。
  8. 打包构建后,Catalog 中的装备资源、图标和必要软引用能被正确 Cook。
  9. 编辑器校验能定位到具体错误资产,内容团队可以按报告修复。
  10. 第四篇的 BP_OutfitComponent 可以只依赖数据接口,不需要知道装备资产目录结构。

本篇结论

装备数据层是换装系统的合同层。UI、存档、网络和 Mutable 都不应该互相直连,而应该围绕 ItemID -> OutfitItemData -> MutableParams 这条链路工作。只要数据合同稳定,后续蓝图实现、预览、保存、联网同步都可以复用同一套输入和回退规则。

参考资料