总览
前两篇已经确定了系统边界和 Mutable 资源规划:Mutable 负责生成表现资源,换装系统负责数据契约、状态管理和异常回退。本篇进入数据层。生产项目里,UI 蓝图不能直接写 Slot_Upper = Jacket_A,存档也不能保存 Mutable 内部选项名。装备必须先被数据化,换装流程只能消费经过校验的数据。
本篇的核心目标是建立一套稳定的装备数据合同:策划能配,美术能查,程序能校验,运行时能从 ItemID 可靠地翻译成 Mutable 参数。
本篇目标
- 为每件装备定义稳定的
ItemID、插槽、显示信息、资产引用和 Mutable 参数映射。 - 建立 Slot、占用规则、冲突规则、身体遮挡区域和默认回退装备。
- 让存档、网络同步、UI、背包都只依赖稳定业务 ID,不直接依赖 Mutable 内部选项。
- 让数据在编辑器阶段可验证,避免打包后才发现参数名、选项值或资源引用错误。
- 为第四篇的
BP_OutfitComponent提供清晰输入:给定ItemID,可以得到完整、可应用、可回退的装备定义。
非目标
- 本篇不实现点击装备后的蓝图执行流,流程实现放到第四篇。
- 本篇不讨论衣柜 UI、预览确认、取消回滚,UI 交互放到第五篇。
- 本篇不设计完整存档版本迁移,只保证数据结构能被存档系统引用。
- 本篇不把背包、商城、掉落系统纳入装备数据资产。换装数据只描述“这个外观如何应用到角色”。
- 本篇不直接讲 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,同时占用 TorsoOverlay 和 LowerOverlay。bPrimarySlot 用来告诉 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_Open 和 Jacket_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 描述“装备被激活时要写入哪些参数”。但还需要明确卸下装备时怎么恢复。推荐两种策略:
- 每个 Slot 都有默认装备。卸下等于装备默认装备。
- 每个 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。校验项包括:
ItemId不为空且全局唯一。SlotClaims至少有一个主 Slot。- 所有 Slot 都存在于
PDA_OutfitSlotConfig。 MutableParams.ParamName存在于目标CustomizableObject。- Enum 类型参数的
StringValue是合法选项。 - Color、Float、Int 参数范围符合项目约束。
HiddenBodyRegions都在KnownBodyRegions内。FallbackItemId存在,且不会形成循环回退。Icon、PreviewAsset等 UI 必需资产按规则配置。- 被标记为
bEnabled=false的装备不能作为默认装备。
校验结果要输出具体资产路径和字段名。只提示“装备数据错误”没有意义,内容团队无法修。
验收标准
- 任意一件已启用装备都能通过
ItemID从 Catalog 找到。 - 全部装备
ItemID唯一,重名会在编辑器阶段报错。 - 每件装备至少有一个主 Slot,且 Slot 存在于 SlotConfig。
- 每件装备的 Mutable 参数名和 Enum 选项能在目标 Mutable 对象中找到。
- 装备缺失、下架或条件不满足时,可以稳定回退到默认装备。
- 存档和网络层只需要保存 ItemID,不需要保存 Mutable 内部参数。
- 连体衣、头盔隐藏发型、多 Slot 占用等复杂装备能由数据表达,不需要 UI 写死。
- 打包构建后,Catalog 中的装备资源、图标和必要软引用能被正确 Cook。
- 编辑器校验能定位到具体错误资产,内容团队可以按报告修复。
- 第四篇的
BP_OutfitComponent可以只依赖数据接口,不需要知道装备资产目录结构。
本篇结论
装备数据层是换装系统的合同层。UI、存档、网络和 Mutable 都不应该互相直连,而应该围绕 ItemID -> OutfitItemData -> MutableParams 这条链路工作。只要数据合同稳定,后续蓝图实现、预览、保存、联网同步都可以复用同一套输入和回退规则。