diff --git a/Docs_Dev/superpowers/specs/2026-07-21-enemy-ability-config-to-so-design.md b/Docs_Dev/superpowers/specs/2026-07-21-enemy-ability-config-to-so-design.md new file mode 100644 index 00000000..74f3268d --- /dev/null +++ b/Docs_Dev/superpowers/specs/2026-07-21-enemy-ability-config-to-so-design.md @@ -0,0 +1,108 @@ +# 敌人能力配置迁移到 AbilitySO —— 设计文档 + +- 日期:2026-07-21 +- 范围:把 8 个「在能力 MonoBehaviour 上直接 `[SerializeField]` 挂载」的能力的**全部数值/资产参数**(动画片段、时长、速度、次数、LayerMask 等)迁移到对应的 `EnemyAbilitySO` **子类** SO 中。场景对象组件引用保留在 MonoBehaviour。只改脚本 + 脚手架,不动现有 prefab / 资产(用户手动重生成替换)。 + +## 目标 + +1. 能力的可配置参数统一放在 SO 里(数据与行为分离、跨实例复用、便于策划调参),消除「部分参数在 SO、部分在脚本」的分裂。 +2. 每个需要迁移的能力类型有一个专属 `EnemyAbilitySO` 子类,**类型安全**、字段自解释、编辑器可见。 +3. 迁移边界清晰:**数值/LayerMask/Clip/资产 SO → 子类 SO**;**场景对象组件引用(`BodyContactDamage`/`HitBox`/`HurtBox`)→ 保留在 MonoBehaviour**(逐实例场景引用无法进共享 SO 资产)。 + +## 背景(现状) + +- `EnemyAbilitySO`(`Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilitySO.cs`)是能力配置基类,承载通用配置:`abilityId`、`attackSequence`、`cooldown`、telegraph、中断规则、调度提示、`exclusionGroup`、`priority`。 +- `EnemyAbilityBase`(MonoBehaviour 抽象基类)持有 `[SerializeField] protected EnemyAbilitySO _config`,生命周期/冷却/中断在基类。 +- `EnemyAttackSO.clip`(`ClipTransition`)已在 SO 层,供 attackSequence 型能力(Melee/Charge/Blink/Projectile/Leap/ChaoFeng 等)使用——**这类能力已是 SO 驱动,不在本次范围**。 +- 有 8 个能力在 MonoBehaviour 上直接挂参数(含 `ClipTransition` 及其它数值),是本次迁移对象。 +- 现有 17 个 `ABL_*.asset` 配置资产均为基类 `EnemyAbilitySO` 类型;片段目前挂在各 prefab 的能力组件实例上。 + +## 设计 + +### A. 八个子类 SO + +在新子目录 `Assets/_Game/Scripts/Enemies/Abilities/Configs/` 下,为每个能力建一个 `XxxAbilitySO : EnemyAbilitySO`,各带 `[CreateAssetMenu(menuName = "BaseGames/Enemies/Abilities/<名>", fileName = "ABL_")]`。字段迁移如下(**以脚本实际字段为准,实现时逐文件核对**;下表为已核实的字段): + +| 能力 MonoBehaviour | 子类 SO | → 迁入 SO 的字段 | 保留在 MonoBehaviour(场景引用) | +|---|---|---|---| +| `ContactChaseAbility` | `ContactChaseAbilitySO` | `startClip, loopClip, endClip`(ClipTransition);`dashSpeed`(float) | `BodyContactDamage _contactDamage` | +| `RepeatSlamAbility` | `RepeatSlamAbilitySO` | `startClip, loopClip, endClip`;`hitActiveTime, staggerDuration`(float);`slamCount`(int) | `HitBox _hitBox` | +| `CeilingHangStrikeAbility` | `CeilingHangStrikeAbilitySO` | `strikeClip, loopClip, endClip`;`hangDuration`(float) | `HitBox _attackHitBox`, `HurtBox _hurtBox` | +| `AnimatedCeilingDropAbility` | `AnimatedCeilingDropAbilitySO` | `fallLoopClip`;`fallGravityScale, maxFallTime, recoveryTime`(float);`groundMask`(LayerMask) | `BodyContactDamage _contactDamage` | +| `AppearAbility` | `AppearAbilitySO` | `appearClip` | — | +| `FacePlayerAbility` | `FacePlayerAbilitySO` | `faceClip` | — | +| `MeleeVulnerabilityAbility` | `MeleeVulnerabilityAbilitySO` | `attackClip`;打击归一化时机字段;`staggerDuration`(float) | `HitBox _hitBox`, `HurtBox _hurtBox` | +| `PlayClipAbility` | `PlayClipAbilitySO` | `clip` | — | + +- 字段名去掉下划线前缀(SO 公有字段风格,对齐 `EnemyAbilitySO`/`EnemyAttackSO` 现有公有字段),保留原 `[Header]`/`[Tooltip]`/`[Min]`/`[Range]` 语义。 +- 子类只加自己的字段,通用配置继续由基类承载。 + +### B. 基类:类型化配置解析(根因显式报错,无兜底) + +在 `EnemyAbilityBase` 增加受保护助手: + +```csharp +/// 把 _config 解析为期望的子类型;类型不符时显式报错(不回退)。 +protected T ResolveConfig() where T : EnemyAbilitySO +{ + if (_config is T typed) return typed; + Debug.LogError($"[{GetType().Name}] _config 需为 {typeof(T).Name}," + + $"实际 = {(_config != null ? _config.GetType().Name : "null")}。请重建为正确的子类 SO。", this); + return null; +} +``` + +- 类型不符或漏配时**显式报错**(符合 CLAUDE.md §6 根因修复、禁止下游兜底),不静默回退到默认值。 +- 不引入泛型基类(`EnemyAbilityBase`),避免波及 attackSequence 型能力与 Unity 序列化面。基类保持非泛型,子类各自缓存类型化配置。 + +### C. 每个能力 MonoBehaviour 的改动(以 ContactChase 为例) + +```csharp +private ContactChaseAbilitySO _cfg; + +protected override void Awake() +{ + base.Awake(); + _cfg = ResolveConfig(); +} +``` + +- 删除迁走的 `[SerializeField]` 字段(`_startClip/_loopClip/_endClip/_dashSpeed`)。 +- 使用处:`_startClip` → `_cfg.startClip`,`_dashSpeed` → `_cfg.dashSpeed`,其余同理。空片段判空逻辑不变(`_cfg.startClip.Clip != null`)。 +- 若 `_cfg == null`(漏配/类型错),执行路径应安全短路(`ExecuteCoroutine` 起始判 `_cfg == null` 则 `yield break`),错误已由 `ResolveConfig` 报出——不静默补默认值。 +- 场景引用字段(`_contactDamage` 等)保留不动。 + +### D. 脚手架 / 创建入口 + +- 8 个子类各自 `[CreateAssetMenu]`:用户可从菜单创建正确类型的 `ABL_` 资产。 +- 若 `CharacterWizardWindow` / `DataHubWindow` 有创建能力 SO 的入口,改为按能力类型创建对应子类(实现前先核对这两个入口现状;无则仅靠 CreateAssetMenu)。 +- `SceneObjectPlacerTool` 中 `AssignAsset(ability, "_config", …, "ABL_xxx")` 按资产名引用——用户重建的 `ABL_` 资产为新子类即可正确绑定,脚手架代码基本不变(如需,补充「资产类型应为子类」的自检提示)。 + +### E. 现有资产迁移 + +- 按用户选择:**代码 + 脚手架保证正确,用户手动重建**这 8 类相关的 `ABL_` 资产(旧资产是基类 `EnemyAbilitySO`,重建为子类并把参数填进 SO),并重生成对应 prefab。 +- attackSequence 型能力(ChaoFeng/Melee/Charge/Blink/Projectile/Leap 等)继续用基类 `EnemyAbilitySO`,不受影响。 + +## 影响文件 + +- 新增:`Assets/_Game/Scripts/Enemies/Abilities/Configs/` 下 8 个子类 SO 脚本。 +- 修改:`EnemyAbilityBase.cs`(加 `ResolveConfig`)。 +- 修改:8 个能力 MonoBehaviour(删字段、Awake 解析、使用处改读 SO)。 +- 可能修改:`CharacterWizardWindow.cs` / `DataHubWindow.cs`(能力 SO 创建入口,按现状定)。 +- 不改:`EnemyAbilitySO.cs`(基类不动,除非需要把某几个当前 protected 成员对子类可见——实现时确认 `_config` 已 protected,子类无需访问基类私有)。 + +## 验证 + +- 编译门 0 错误。 +- EditMode 测试: + - `ResolveConfig` 类型匹配返回实例;类型不符/为 null 时报错(`LogAssert.Expect(LogType.Error, ...)` 断言)。 + - 每个子类 SO 可 `CreateInstance` 且暴露预期字段(反射校验字段名/类型存在)。 +- MCP 放置验证:在活动场景放置一个能力对象、绑定对应子类 SO 实例,反射读取能力的 `_cfg` 字段确认非空且参数值来自 SO(不必真跑 Animancer)。 +- 自检工具:`Validate All ScriptableObjects` 无新增错误。 + +## 非目标 + +- 不迁移场景对象组件引用(`BodyContactDamage`/`HitBox`/`HurtBox`)——保留在 MonoBehaviour 逐实例绑定。 +- 不动 `EnemyAttackSO` / `attackSequence`(其 `clip` 已在 SO 层)。 +- 不引入泛型能力基类。 +- 不改现有 prefab / 资产(用户手动重生成)。