docs(spec): 敌人能力配置迁移到 AbilitySO 子类设计文档
This commit is contained in:
@@ -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
|
||||
/// <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 / 资产(用户手动重生成)。
|
||||
Reference in New Issue
Block a user