Files
zeling_v2/Docs_Dev/superpowers/specs/2026-07-21-enemy-ability-config-to-so-design.md
T

109 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 敌人能力配置迁移到 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
/// <summary>把 _config 解析为期望的子类型;类型不符时显式报错(不回退)。</summary>
protected T ResolveConfig<T>() 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<T>`),避免波及 attackSequence 型能力与 Unity 序列化面。基类保持非泛型,子类各自缓存类型化配置。
### C. 每个能力 MonoBehaviour 的改动(以 ContactChase 为例)
```csharp
private ContactChaseAbilitySO _cfg;
protected override void Awake()
{
base.Awake();
_cfg = ResolveConfig<ContactChaseAbilitySO>();
}
```
- 删除迁走的 `[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<T>`)。
- 修改:8 个能力 MonoBehaviour(删字段、Awake 解析、使用处改读 SO)。
- 可能修改:`CharacterWizardWindow.cs` / `DataHubWindow.cs`(能力 SO 创建入口,按现状定)。
- 不改:`EnemyAbilitySO.cs`(基类不动,除非需要把某几个当前 protected 成员对子类可见——实现时确认 `_config` 已 protected,子类无需访问基类私有)。
## 验证
- 编译门 0 错误。
- EditMode 测试:
- `ResolveConfig<T>` 类型匹配返回实例;类型不符/为 null 时报错(`LogAssert.Expect(LogType.Error, ...)` 断言)。
- 每个子类 SO 可 `CreateInstance` 且暴露预期字段(反射校验字段名/类型存在)。
- MCP 放置验证:在活动场景放置一个能力对象、绑定对应子类 SO 实例,反射读取能力的 `_cfg` 字段确认非空且参数值来自 SO(不必真跑 Animancer)。
- 自检工具:`Validate All ScriptableObjects` 无新增错误。
## 非目标
- 不迁移场景对象组件引用(`BodyContactDamage`/`HitBox`/`HurtBox`)——保留在 MonoBehaviour 逐实例绑定。
- 不动 `EnemyAttackSO` / `attackSequence`(其 `clip` 已在 SO 层)。
- 不引入泛型能力基类。
- 不改现有 prefab / 资产(用户手动重生成)。