From e59e24cca0c779c62f8a26483e3e4ca866ee289b Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 29 Jul 2026 10:38:07 +0800 Subject: [PATCH] =?UTF-8?q?docs(enemy):=20=E4=BF=AE=E6=AD=A3=20AI=20?= =?UTF-8?q?=E9=85=8D=E6=96=B9=E8=A7=A3=E6=9E=90=E6=96=B9=E5=BC=8F=E2=80=94?= =?UTF-8?q?=E2=80=94=E7=9B=B4=E6=8E=A5=E5=BC=95=E7=94=A8=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E8=A1=A8=E5=8F=8C=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实施计划阶段发现两处硬阻塞: 1. BaseGames.AI 只引用 BaseGames.Core,无法收集依赖 Enemies 的 AiRecipeSO; 2. 项目 database SO 走 [SerializeField] 直接引用而非 Addressables,静态注册表取不到。 改为 EnemyAiBrain 上 _recipe 资产引用 / _definitionId 脚本 id 二选一。 净减 AiRecipeDatabaseSO、Addressables 加载、AddressKeys 改动、asmdef 改动; flyweight 与配方路径零字符串 id 均保持。 Co-Authored-By: Claude Opus 5 (1M context) --- ...7-29-enemy-ai-composable-modules-design.md | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md b/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md index d66dd768..9973b308 100644 --- a/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md +++ b/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md @@ -311,14 +311,50 @@ public sealed class PerceptionRecipeSO : AiRecipeSO | ~15% | 同上 + 在配方里追加 1–2 个自定义前置态(如掉落链、出场链) | 少量代码 | | ~5% | 写 `[AiDefinition] AiScript` 子类,完全自由 | 与现状相同 | -### 4.3 注册表双源 +### 4.3 解析方式:配方走直接引用,脚本走注册表 -`AiDefinitionRegistry` 改为: +> 修订(2026-07-29,实施计划阶段):初稿设计为「注册表双源 + `AiRecipeDatabaseSO` 经 +> Addressables 加载」。该方案有两处硬阻塞,已废弃: +> 1. **程序集方向不允许** —— `AiDefinitionRegistry` 在 `BaseGames.AI`,该程序集只引用 +> `BaseGames.Core`,无法引用 `BaseGames.Enemies`(模块依赖 `IEnemyActor` / `EnemyAbilitySO`), +> 也未引用 `Unity.Addressables`。 +> 2. **与项目既有模式不符** —— `FormSkillDatabaseSO` 等库资产走的是 `[SerializeField]` +> 直接引用,不是 Addressables。静态注册表拿不到序列化引用;改用 Addressables 异步加载 +> 则与 `EnemyAiBrain.Start()` 的同步取图产生时序冲突。 -1. 反射收集 `[AiDefinition]` 标注的 `AiScript` 子类(现状逻辑不变)。 -2. 从 `AiRecipeDatabaseSO`(Addressables 单一权威源,沿用 `FormSkillDatabaseSO` / - `BestiaryDatabaseSO` 的既有模式)收集配方资产。 -3. **id 冲突显式抛错**(现状对重复 id 已抛,扩展到跨源)。 +改为两条互斥的解析路径,各自最短: + +```csharp +// BaseGames.AI —— 只依赖 BrainBuilder / AiGraph,无 Enemies 依赖 +public interface IAiDefinition { AiGraph GetOrBuildGraph(); } +public abstract class AiScript : IAiDefinition { ... } +public abstract class AiRecipeSO : ScriptableObject, IAiDefinition { ... } + +// BaseGames.Enemies +public sealed class PerceptionRecipeSO : AiRecipeSO { ... } +``` + +`EnemyAiBrain` 上二选一,两者互斥: + +```csharp +[Tooltip("配方路径(约 95% 敌人):直接引用 AI 配方资产")] +[SerializeField] AiRecipeSO _recipe; + +[Tooltip("定制路径(约 5% 敌人):[AiDefinition(id)] 的 AiScript 子类 id")] +[SerializeField] string _definitionId; +``` + +`Awake` 校验「恰好设置其一」,否则 `Debug.LogError` 并禁用组件(沿用现状对漏配 id 的处理)。 + +这条修订带来三点净收益: + +- **无需 `AiRecipeDatabaseSO`、无需 Addressables、无需改 `AddressKeys`、无需改任何 asmdef。** + 预制体到配方是硬引用,Unity 保证随预制体一起加载,不存在"库没加载到"的失败模式。 +- **flyweight 不变** —— 所有实例引用同一个 SO 资产,`AiRecipeSO` 内缓存唯一一份 `AiGraph`。 +- **配方路径彻底没有字符串 id**,痛点「硬编码字符串」在 95% 的敌人上完全消失。 + +`AiDefinitionRegistry` 保持单源(反射收集 `AiScript`),逻辑不变,仅让 `AiScript` 实现 +`IAiDefinition`。校验器侧枚举配方资产用 `AssetDatabase.FindAssets("t:AiRecipeSO")`(编辑器专用)。 ### 4.4 关闭 Domain Reload 的约束 @@ -338,7 +374,7 @@ public struct AbilityRef [SerializeField] EnemyAbilitySO _asset; string _literal; // 定制脚本路径使用 - public string Id => _asset != null ? _asset.Id : _literal; + public string Id => _asset != null ? _asset.abilityId : _literal; public static implicit operator AbilityRef(string id) => new AbilityRef(id); } ``` @@ -379,9 +415,9 @@ E003(战斗区激活才掉落)、E004(战斗触发才出场)将来需要 | 工具 | 作用 | 优先级 | |---|---|---| -| `AiDefinitionValidator` | 接入 `SOValidationRunner`。校验:配方引用的能力是否存在于该敌人的 `EnemyAbilityRegistry`、`ApproachAttackEngagement` 是否配了 `EnemyAttackSelector`、id 是否跨源重复、模块字段是否留空。**唯一能防住 450+ 引用出错的东西** | 紧接本次 | -| `EnemyAiRecipeWizard` | 建配方资产的脚手架(CLAUDE.md 第 2 条:不裸建),顺带完成 Addressables 注册与 `AiRecipeDatabaseSO` 登记。**本次必须先有它**,E001 的配方资产才能合规产出 | 本次 | -| `_definitionId` 下拉 Drawer | 从 `AiDefinitionRegistry.Ids` 取值,预制体上不再手打字符串 | 紧接本次 | +| `AiDefinitionValidator` | 接入 `SOValidationRunner`。以 `AssetDatabase.FindAssets("t:AiRecipeSO")` 枚举全部配方,校验:模块字段是否留空、`AbilityRef` 是否两侧同时非空、`ApproachAttackEngagement` 是否配了 `EnemyAttackSelector`。**唯一能防住 450+ 引用出错的东西** | 紧接本次 | +| `EnemyAiRecipeWizard` | 建配方资产的脚手架(CLAUDE.md 第 2 条:不裸建),按 `AssetFolderSpec` 定名定路径。**本次必须先有它**,E001 的配方资产才能合规产出 | 本次 | +| `_definitionId` 下拉 Drawer | 定制路径(约 5%)从 `AiDefinitionRegistry.Ids` 取值,预制体上不再手打字符串 | 紧接本次 | | `AiGraphExporter` | 把 `AiGraph` 导出为 Mermaid(`Transition.Label` 已为此准备)。150 张图若不能一眼看懂就没人敢改 | 路线图 | --- @@ -401,7 +437,7 @@ E003(战斗区激活才掉落)、E004(战斗触发才出场)将来需要 | 阶段 | 内容 | |---|---| -| **本次** | 四层架构 + 8 个模块 + `AiStateFragments` + `BrainBuilder.RequireState` + `AiSignal` 清理 + `AbilityRef` + `AiRecipeSO` / `PerceptionRecipeSO` / `AiRecipeDatabaseSO` + 双源注册表 + `PlayModeResetHook` + `SubclassSelector` Drawer + **`EnemyAiRecipeWizard` 脚手架** + E001 迁移为配方资产 + 测试重写 | +| **本次** | 四层架构 + 8 个模块 + `AiStateFragments` + `BrainBuilder.RequireState` + `AiSignal` 清理 + `AbilityRef` + `IAiDefinition` / `AiRecipeSO` / `PerceptionRecipeSO` + `EnemyAiBrain` 双路径解析 + `PlayModeResetHook` + `SubclassSelector` Drawer + **`EnemyAiRecipeWizard` 脚手架** + E001 迁移为配方资产 + `SceneObjectPlacerTool` 提示更新 + 测试重写 | | **紧接** | `AiDefinitionValidator` 校验器 + `_definitionId` 下拉 Drawer | | **随敌人落地** | 新模块按需增加;`AiSignal.Engaged` 在 E003 / E004 需要时才加 | | **单独立项** | Boss 双轨统一(`BossSkillExecutor` + `BossSkillSO` vs `EnemyAttackSelector` + `EnemyAbilitySO`)。E004「小 BOSS」归属未定前不动。本次 `ApproachAttackEngagement` 按精英怪(E005)尺度做,不为 E004 加码 | @@ -417,8 +453,8 @@ E003(战斗区激活才掉落)、E004(战斗触发才出场)将来需要 E001 迁移为 `PerceptionRecipeSO` 配方资产,依 `AssetFolderSpec.md` 命名与放置: - 配方资产:`Assets/_Game/Data/Enemies/E001/ENM_E001_Ai.asset`(与既有 `ENM_E001_Stats.asset` / `ENM_E001_AnimConfig.asset` 同目录同前缀) -- 权威源库:`Assets/_Game/Data/Enemies/ENM_AiRecipeDatabase.asset` -- 两者均由 `EnemyAiRecipeWizard` 创建并自动完成 Addressables 注册与登记(CLAUDE.md 第 2 条) +- 由 `EnemyAiRecipeWizard` 创建(CLAUDE.md 第 2 条:不裸建) +- 预制体 `EnemyAiBrain._recipe` 直接引用该资产,`_definitionId` 留空 字段取值: @@ -460,7 +496,8 @@ public sealed class E001CaoZhiAi : AiScript | `RushEngagementTests` | 两种 `RushExit` 的进入条件与脱战边;committed 不被感知丢失打断;CD 期不抖动 | | `ApproachAttackEngagementTests` | 逼近 ↔ 攻击往返;脱战;**新增:攻击起手后玩家离开感知区仍完整打完**(§3.6 行为变更) | -补充:`AiDefinitionRegistryTests` 增加双源收集与跨源 id 冲突抛错用例。 +补充:`PerceptionRecipeSoTests` —— 配方产出的图与等价手写组装的图行为一致;同一配方多次 +`GetOrBuildGraph()` 返回同一实例(flyweight)。`AiDefinitionRegistry` 逻辑未变,现有测试保持不动。 --- @@ -475,19 +512,20 @@ public sealed class E001CaoZhiAi : AiScript 新增 Assets/_Game/Scripts/Enemies/AIBrain/Modules/Unaware/{SinglePost, DisguiseThenPatrol, AlternatingIdlePatrol}.cs 新增 Assets/_Game/Scripts/Enemies/AIBrain/Modules/Engagement/{RushEngagement, ApproachAttackEngagement}.cs 新增 Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death/{TerminalDeath, AbilityDeath, TwoStageDeath}.cs -新增 Assets/_Game/Scripts/Enemies/AIBrain/Recipes/{AiRecipeSO, PerceptionRecipeSO, AiRecipeDatabaseSO}.cs +新增 Assets/_Game/Scripts/AI/IAiDefinition.cs +新增 Assets/_Game/Scripts/AI/AiRecipeSO.cs (BaseGames.AI:无 Enemies 依赖) +新增 Assets/_Game/Scripts/Enemies/AIBrain/Recipes/PerceptionRecipeSO.cs 新增 Assets/_Game/Scripts/Enemies/Abilities/AbilityRef.cs 新增 Assets/_Game/Scripts/Editor/AI/SubclassSelectorDrawer.cs 新增 Assets/_Game/Scripts/Editor/AI/EnemyAiRecipeWizard.cs 新增 Assets/_Game/Data/Enemies/E001/ENM_E001_Ai.asset(经向导产出) -新增 Assets/_Game/Data/Enemies/ENM_AiRecipeDatabase.asset(经向导产出) 删除 Assets/_Game/Scripts/Enemies/AIBrain/PerceptionStateMachine.cs 删除 Assets/_Game/Scripts/Enemies/AIBrain/Ai/E001CaoZhiAi.cs(迁移为配方资产) 改 Assets/_Game/Scripts/AI/BrainBuilder.cs + RequireState(name) 改 Assets/_Game/Scripts/AI/AiSignal.cs 8 → 1 改 Assets/_Game/Scripts/AI/AiScript.cs 实现 IAiDefinition -改 Assets/_Game/Scripts/AI/AiDefinitionRegistry.cs 双源收集 -改 AddressKeys.cs 新增配方库地址常量(AddressablesLabelSpec:禁止硬编码字符串) +改 Assets/_Game/Scripts/Enemies/AIBrain/EnemyAiBrain.cs 配方引用 / 脚本 id 二选一解析 +改 Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs 7 处 AI 挂载提示改为配方路径 测试 Assets/Tests/EditMode/AI/ 拆 PerceptionStateMachineTests → 5 份,扩 AiDefinitionRegistryTests 文档 Docs/Guides/ 新增一节「新敌人 AI 怎么写」(四层组装规则 + 三条作者路径 + 六个敌人范例) ```