21 KiB
Mod 开发体验改进 · 任务清单
文档类型:开发任务交接清单
生成日期:2026-03-13
负责角色:首席游戏设计师 → 技术员
适用范围:仅针对开发者(第一方)制作 Mod 内容的体验提升,暂不涉及外部 Modder 工具链
阅读方式:标注[设计]的条目由设计师驱动决策,标注[技术]的条目由技术员实现,标注[规范]的是制度约定无需编程
一、编辑器界面重构(IMGUI → Odin Inspector)
背景:当前
CardDataEditor.cs、CharacterDataEditor.cs、EquipmentDataEditor.cs均使用手动 IMGUI 代码。代码量大、维护难、功能弱。Odin Inspector 提供声明式 Attribute 方式定义界面,且无需使用SerializedScriptableObject(不影响 UMod 序列化)。
1-A. 删除旧 IMGUI Editor 类,改用 Odin Attribute 装饰 Data 类 [技术]
涉及文件:
Assets/Scripts/MainGame/Card/Editor/CardDataEditor.cs→ 删除Assets/Scripts/MainGame/Character/Editor/CharacterDataEditor.cs→ 删除Assets/Scripts/MainGame/Equipment/Editor/EquipmentDataEditor.cs→ 删除Assets/Scripts/MainGame/Card/CardData/CardData.cs→ 改造Assets/Scripts/MainGame/Character/CharacterData/CharacterData.cs→ 改造Assets/Scripts/MainGame/Equipment/EquipmentData.cs→ 改造
具体要求:
用以下 Odin Attribute 替代旧 IMGUI 的对应功能:
| 旧 IMGUI 功能 | Odin 替代 Attribute |
|---|---|
EditorGUILayout.LabelField("标题", boldLabel) |
[TitleGroup("标题")] 或 [FoldoutGroup("标题")] |
EditorGUI.BeginDisabledGroup(true) |
[ReadOnly] |
Show/Hide 条件逻辑(如 haveCustomClass) |
[ShowIf("haveCustomClass")] / [HideIf] |
| 手动绘制列表 | [ListDrawerSettings] |
| 字典(SerializableDictionary) | [DictionaryDrawerSettings] |
按钮([Button] 已在 NaughtyAttr 中使用) |
统一改为 Odin [Button],功能相同但支持更多参数 |
EditorGUILayout.PropertyField(sprite) 图片预览 |
[PreviewField(80)] |
| 多行文本 | [MultiLineProperty(3)] |
注意事项:
- 继续保留
CardData.PasteDefaultAttributes()等编辑器专属方法,用[Button("粘贴默认属性")]暴露 - 不得将
CardData/CharacterData的基类改为SerializedScriptableObject,必须保留ScriptableObject基类 - 检查
NaughtyAttributes与 Odin 的 Attribute 重叠,统一迁移到 Odin,逐步移除 NaughtyAttributes 依赖
1-B. CardData 编辑器特殊功能保留与增强 [技术]
当前 CardDataEditor 中有以下特殊功能须在 Odin 版本中等效实现:
-
可搜索类型选择器(
DrawSearchableTypeSelector):根据CardLogicBase的所有子类自动填充modName、categoryName、className、displayName、functionText- Odin 方案:使用
[ValueDropdown("GetCardLogicTypes")]+ 在同类中写private IEnumerable<ValueDropdownItem> GetCardLogicTypes()方法,返回所有已注册CardLogicBase子类的列表
- Odin 方案:使用
-
带引用选择器的列表(
DrawListWithEditRefSelector):关键词列表支持从 EditReference 资产中选取- Odin 方案:
[ValueDropdown("GetAvailableKeywords")]装饰keywords字段
- Odin 方案:
-
对象引用列表(
DrawCharacterListGUI<T>):通过资产名字符串引用其他 Data 资产- Odin 方案:保留字符串存储形式,但使用
[ValueDropdown]从AssetDatabase中查找并显示
- Odin 方案:保留字符串存储形式,但使用
1-C. 为 KeywordData 的编辑器添加更清晰的分组 [技术]
目前 KeywordData 依赖 NaughtyAttributes 的 [Button],界面简陋。
改造后:所有 InterpretedKeyword 在字典中的每个 entry 能展开显示 name、description 的本地化预览文本(通过 [OnInspectorGUI] 实现一个 Preview() 方法)。
二、Mod 内容结构与命名规范
背景:目前 Basic Mod 的目录结构相对合理,但没有正式成文的规范文档,新增内容的开发者需要靠"看现有文件猜"。以下是需要成文并固化的规范。
2-A. 制定并存档《Mod 内容规范》文档 [规范] [设计]
在项目中创建文档 docs/ModContentStandard_内容规范.md,包含以下内容:
目录结构规范(以 "Basic" Mod 为蓝本)
Assets/Mods/{ModName}/
├── {ModName}_Manifest.asset ← ModManifest,必须存在
├── {ModName}_CombatOrganizer.asset ← 战斗组织者配置(如有)
│
├── Cards/
│ ├── Data/ ← 存放所有 CardData.asset
│ ├── Scripts/
│ │ ├── {ClassCategory}/ ← 按职业/分类分子文件夹,例如 Assassin/
│ │ ├── Enemies/ ← 敌方专属卡牌
│ │ └── General/ ← 通用卡牌
│ ├── LogicComponents/ ← 存放该 Mod 特有的 CardLogicComponent 子类
│ ├── DefaultCollections/ ← CardAttributesDefaultCollection.asset
│ └── Sprites/ ← 卡牌图片
│
├── Characters/
│ ├── Data/ ← 存放所有 CharacterData.asset
│ ├── Scripts/ ← CharacterLogicBase 子类
│ ├── CombatBuffs/
│ │ ├── General/ ← 通用 Buff(跨职业可用)
│ │ └── {ClassCategory}/ ← 职业专属 Buff
│ ├── CharacterViews/ ← 角色视觉 Prefab
│ ├── DefaultCollections/ ← CharacterAttributesDefaultCollection.asset
│ └── Sprites&Animations/ ← 角色图片与动画资产
│
├── Equipments/
│ ├── Data/ ← EquipmentData.asset
│ └── Scripts/ ← EquipmentBase 子类(如有自定义逻辑)
│
├── Keywords/
│ └── KeywordData_{ModName}_*.asset ← 关键词数据
│
├── Rules/
│ └── {ModName}_AttributeRulesCollection.cs ← AttributeRulesCollectionBase 子类
│
├── Localization/
│ ├── Localization_{ModName}_CN.asset
│ └── Localization_{ModName}_EN.asset
│
├── Audios/ ← 音频资产
└── Prefabs/ ← 其他 Prefab(特效、HUD 等)
资产命名规范
格式:{AssetType}_{ModName}_{AssetName}
示例:
CardData_Basic_Backstab.asset
CharacterData_Basic_Assassin.asset
EquipmentData_Basic_SteelBracer.asset
KeywordData_Basic_Buff.asset
HUDData_Basic_Default.asset
规则细则:
AssetName使用 PascalCase(大驼峰),禁止空格和下划线ModName必须与 Manifest 的inEditorModFolder字段完全一致- 所有脚本
.cs文件名与类名保持一致(Unity 默认要求)
C# 命名空间规范
格式:Continentis.Mods.{ModName}.{Category}[.{SubCategory}]
示例:
Continentis.Mods.Basic.Cards.Assassin ← Assassin 职业卡牌逻辑
Continentis.Mods.Basic.Cards.Enemies ← 敌方卡牌逻辑
Continentis.Mods.Basic.Cards.General ← 通用卡牌逻辑
Continentis.Mods.Basic.Buffs ← 所有 Buff(通用)
Continentis.Mods.Basic.Characters ← CharacterLogicBase 子类
Continentis.Mods.Basic.Rules ← AttributeRulesCollection
2-B. 为每种 Data 类型创建「快速创建向导」菜单项 [技术](低优先级)
在 Unity 菜单栏添加 Continentis / Mod Tools / 新建卡牌...,弹出向导窗口:
- 输入:Mod 名称、分类名、卡牌类名
- 自动:在正确的目录下创建
CardData_{Mod}_{Name}.asset,并预填modName、categoryName、className字段 - 可选:同时创建配套的
.cs脚本文件(从模板复制)
三、ModManifest 功能增强
背景:
ModManifest当前已有「分类收集」按钮,功能基本具备,但仍有几处可以优化。
3-A. 增加「一键收集全部」按钮 [技术]
涉及文件:Assets/Scripts/Mod/Manifests/ModManifest.cs
在 #if UNITY_EDITOR 块中添加:
[Button("★ 一键收集全部", ButtonSizes.Large)]
private void CollectAll()
{
CollectAllKeywordData();
CollectAllCardData();
CollectAllCharacterData();
CollectAllEquipmentData();
CollectAllHUDData();
CollectAllLocalizations();
Debug.Log("[ModManifest] 全部资产已收集完毕。");
}
同时将此按钮放置在 Odin 的 [BoxGroup("Tools")] 中,与其他按钮分组显示。
3-B. 清理 cardDataIDList 中的空条目 [技术](BUG 修复)
问题:Basic_Manifest.asset 第 45 行有一个空条目 "CardData"(未填写完整的资产名),会在运行时 host.Assets.Load<CardData>("CardData") 失败。
修复:直接在 Basic_Manifest.asset 中删除该空行;同时在 ModManifest.SaveToDatabase() 中增加防御性检查:
foreach (var assetName in idList)
{
if (string.IsNullOrWhiteSpace(assetName)) continue; // 新增:跳过空条目
T data = host.Assets.Load<T>(assetName);
// ...
}
3-C. 增加 Mod 版本与元数据字段 [技术] [设计]
涉及文件:Assets/Scripts/Mod/Manifests/ModManifest.cs
在 ModManifest 类中新增以下字段:
[FoldoutGroup("Mod Info")]
[LabelText("Mod 名称")] public string modDisplayName;
[LabelText("Mod 版本")] public string modVersion = "1.0.0";
[LabelText("作者")] public string modAuthor;
[LabelText("描述"), MultiLineProperty(3)] public string modDescription;
[LabelText("最低游戏版本")] public string minimumGameVersion = "0.1.0";
这些字段在 UMod 的 ModManifest 元数据中是标准字段,添加后可在 Mod 选择界面展示。
3-D. 增加「收集后预览」校验功能 [技术](低优先级)
收集完成后,在 Odin Inspector 中用 [InfoBox] 或 [OnInspectorGUI] 显示一个摘要表格:
✓ 卡牌数据:81 项
✓ 角色数据:6 项
✓ 装备数据:1 项
✓ 关键词数据:3 项
✓ 本地化文件:13 项
⚠ 警告:发现 1 个不符合命名规范的资产(CardData)
四、属性名称安全化
背景:当前所有属性访问(
GetAttribute("Block")、SetAttribute("Stamina", 0)等)均使用裸字符串,存在 typo 风险且无 IDE 提示。
4-A. 创建 GameAttributes 静态常量类 [技术]
新建文件:Assets/Scripts/MainGame/Base/GameAttributes.cs
namespace Continentis.MainGame
{
/// <summary>
/// 游戏通用属性名称常量。
/// 请始终使用此类中的常量而非裸字符串访问属性,以避免 typo 错误。
/// </summary>
public static class GameAttributes
{
// ── 生命值 ──────────────────────────────────
public const string Health = "Health";
public const string MaximumHealth = "MaximumHealth";
// ── 资源 ────────────────────────────────────
public const string Stamina = "Stamina";
public const string MaximumStamina = "MaximumStamina";
public const string StaminaRecoverPerAction = "StaminaRecoverPerAction";
public const string Mana = "Mana";
public const string MaximumMana = "MaximumMana";
public const string ManaRecoverPerAction = "ManaRecoverPerAction";
// ── 防御 ────────────────────────────────────
public const string Block = "Block";
public const string Shield = "Shield";
public const string Dodge = "Dodge";
public const string BlockGainOffset = "BlockGainOffset";
public const string BlockGainMultiplier = "BlockGainMultiplier";
public const string DodgeGainOffset = "DodgeGainOffset";
public const string DodgeGainMultiplier = "DodgeGainMultiplier";
public const string ShieldGainOffset = "ShieldGainOffset";
public const string ShieldGainMultiplier = "ShieldGainMultiplier";
public const string KeepBlockOnActionStart = "KeepBlockOnActionStart";
public const string KeepDodgeOnActionStart = "KeepDodgeOnActionStart";
// ── 速度与行动 ──────────────────────────────
public const string Speed = "Speed";
public const string DrawCardAmountPerAction = "DrawCardAmountPerAction";
public const string DeckCapacity = "DeckCapacity";
// ── 伤害调整(通用)──────────────────────────
public const string PhysicsDamageDealtOffset = "PhysicsDamageDealtOffset";
public const string MagicDamageDealtOffset = "MagicDamageDealtOffset";
public const string FinalDamageDealtMultiplier = "FinalDamageDealtMultiplier";
public const string FinalDamageGainMultiplier = "FinalDamageGainMultiplier";
public const string MagicDamageDealtMultiplier = "MagicDamageDealtMultiplier";
public const string MagicDamageGainMultiplier = "MagicDamageGainMultiplier";
// ── 核心属性(角色创建时)──────────────────────
public const string Strength = "Strength";
public const string Agility = "Agility";
public const string Intelligence = "Intelligence";
public const string Physique = "Physique";
public const string Perception = "Perception";
public const string Charisma = "Charisma";
public const string Level = "Level";
// ── 感知与闪避检测 ──────────────────────────
public const string Awareness = "Awareness";
public const string DodgeCheckStartDamageMultiplier = "DodgeCheckStartDamageMultiplier";
// ── 元素伤害(格式:{Element}DamageDealtMultiplier)─
// 请按元素名在各 Mod 中自行定义扩展常量,或添加在此类的子类 / partial class 中
}
}
迁移策略:不要在全项目做批量 Find & Replace(风险高)。
建议:新代码强制要求使用常量,旧代码遇到则顺手替换,通过 Code Review 推进,不强制一次性迁移。
五、CardData 「默认属性模板」机制完善
背景:
CardAttributesDefaultCollection与CharacterAttributesDefaultCollection已存在,但在CardData中缺少对应的PasteDefaultAttributes()方法(角色端有,卡牌端没有)。
5-A. 为 CardData 补充 PasteDefaultAttributes() 功能 [技术]
涉及文件:Assets/Scripts/MainGame/Card/CardData/CardData.cs
参照 CharacterData.PasteDefaultAttributes() 的逻辑,在 #if UNITY_EDITOR 块中为 CardData 添加同名方法:
- 扫描
Assets/Mods/{ModName}/Cards/DefaultCollections/目录下的CardAttributesDefaultCollection资产 - 将模板中的
variableAttributes、originalAttributes、endowingCurrentAttributes合并粘贴到当前CardData中 - 注意:
CardAttributesDefaultCollection中的字段名是endowingCurrentAttributes,但CardData中对应字段已改名为runtimeCurrentAttributes(注意同步做字段名对齐,或检查是否存在[FormerlySerializedAs]标记)
补充:为 CardData Odin 编辑器 Inspector 中添加 [Button("粘贴默认属性")] 按钮暴露此功能。
六、BUG 修复与代码清理
以下是在代码审查中发现的已知问题,建议在本次迭代中一并修复。
6-A. 修复 ModifyMana() 中的 ClampAttribute 笔误 [技术](BUG)
文件:Assets/Scripts/MainGame/Character/CharacterMainFunctions.cs 第 70 行
// 当前(错误):
ClampAttribute("Stamina", 0, GetAttribute("MaximumStamina"));
// 修改为:
ClampAttribute("Mana", 0, GetAttribute("MaximumMana"));
6-B. 修复 NextAction() 中阵营判断的逻辑死区 [技术](BUG)
文件:Assets/Scripts/MainGame/Combat/CombatMainManager.cs 第 180-183 行
// 当前(第二个条件永远不会触发,与第一个条件完全相同):
else if (currentCharacter.fraction == Fraction.Enemy)
{
text = "Ally Action"; // 永远不会显示
}
// 修改为:
else if (currentCharacter.fraction == Fraction.Ally)
{
CombatUIManager.Instance.combatMainPage.endActionButton
.GetComponentInChildren<TMP_Text>().text = "Ally Action";
}
6-C. 移除临时的 DrawCardAmount 偏移量 [技术](技术债)
文件:Assets/Mods/Basic/Rules/Basic_AttributeRulesCollection.cs 第 42 行
// 当前(临时代码,需移除或改为正式配置):
general["DrawCardAmountPerAction"] += 10;
移除此行之前,需确认是否已通过其他途径(如 CharacterAttributesDefaultCollection 或 CharacterData.generalAttributes)为角色配置了合理的初始抽牌量,确保移除后游戏逻辑正常。
6-D. 清理 Basic_Manifest.asset 中的空 CardData 条目 [技术](BUG)
见第三节 3-B,此处不再赘述。
七、额外优化建议(低优先级,可延后)
7-A. 建立「卡牌预览」工具窗口 [技术]
在 Unity 编辑器中实现一个 EditorWindow(Continentis / Mod Tools / 卡牌预览),选中 CardData 后,在窗口中还原游戏内的卡牌 UI 外观(图片 + 类型色 + 描述文本)。这是提升开发效率的重要工具,能让设计师在不启动游戏的情况下确认卡牌视觉效果。
7-B. 本地化 Key 完整性检查工具 [技术]
写一个编辑器脚本,扫描所有 CardData 的 displayName 和 functionText 字段,检查它们是否在所有 Localization_*.asset 文件中都有对应条目,输出缺失的 Key 列表。避免出现"卡牌显示 Key 名称而非实际文字"的问题。
7-C. 为 CardLogicComponent_Attack 等内置组件添加 Tooltip 注释 [技术]
SetDamage_Default()、SetBlock_Default() 等方法目前缺少 XML 注释,Modder 无法通过 IDE 悬停了解参数含义。补充 <summary> 注释,提升 API 可发现性。
7-D. 升级系统:为 CardUpgradeNode 添加 Odin 预览 [技术]
CardUpgradeNode 中的 customUpgradeAttributes(List<Dictionary<string, float>>)在默认序列化下几乎无法编辑(Unity 不序列化嵌套字典)。建议:
- 改为
List<SerializableDictionary<string, float>>,并用 Odin[ListDrawerSettings(ShowIndexLabels = true)]装饰 - 这样每一级升级的属性增量都可以在 Inspector 中直观查看和编辑
八、任务优先级总表
| 优先级 | 编号 | 任务名称 | 类型 | 工作量估算 |
|---|---|---|---|---|
| 🔴 P0 | 6-A | 修复 ModifyMana 笔误 | BUG | 5 分钟 |
| 🔴 P0 | 6-B | 修复 NextAction 阵营判断死区 | BUG | 5 分钟 |
| 🔴 P0 | 3-B | 清理 Manifest 空条目 + 防御检查 | BUG | 15 分钟 |
| 🟠 P1 | 1-A | 删除旧 IMGUI Editor,改用 Odin Attribute | 编辑器重构 | 3-5 天 |
| 🟠 P1 | 1-B | 保留 CardData 可搜索类型选择器(Odin 版) | 编辑器重构 | 1-2 天 |
| 🟠 P1 | 3-A | ModManifest 一键收集全部按钮 | 功能增强 | 2 小时 |
| 🟠 P1 | 3-C | ModManifest 增加版本/元数据字段 | 功能增强 | 1 小时 |
| 🟠 P1 | 4-A | 创建 GameAttributes 常量类 | 规范化 | 2 小时 |
| 🟡 P2 | 2-A | 撰写《Mod 内容规范》文档 | 规范文档 | 半天 |
| 🟡 P2 | 5-A | CardData 补充 PasteDefaultAttributes | 功能补全 | 2 小时 |
| 🟡 P2 | 6-C | 移除临时 DrawCard 偏移量 | 技术债 | 30 分钟(需确认) |
| 🟡 P2 | 7-D | CardUpgradeNode 字典序列化修复 | 功能修复 | 3 小时 |
| 🟢 P3 | 1-C | KeywordData 编辑器改善 | 编辑器优化 | 1 小时 |
| 🟢 P3 | 3-D | Manifest 收集后预览校验 | 功能增强 | 2 小时 |
| 🟢 P3 | 7-A | 卡牌预览工具窗口 | 工具开发 | 3-5 天 |
| 🟢 P3 | 7-B | 本地化 Key 完整性检查工具 | 工具开发 | 1-2 天 |
| 🟢 P3 | 7-C | CardLogicComponent 注释补充 | 文档 | 1 小时 |
| ⚪ P4 | 2-B | 新建内容向导菜单项 | 工具开发 | 2-3 天 |
本文档由首席游戏设计师(game-designer-generic)生成,技术员在实现时如遇到设计意图不明确的地方,请向设计师确认后再动工。