docs(enemy): 计划同步取消死亡层——T9 作废、新增 T9R,T10/T11/T13 去掉 death 参数
T9 原稿折叠保留作历史记录,附必然卡死的机制说明。 PerceptionSkeleton.Add 由三参收为两参并自声明 Death 终态; PerceptionRecipeSO 去掉死亡层字段。T5 段落保持原样(它记录的是已发生的事实, IDeathModule 由 T9R 删除)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**Goal:** 用「固定感知骨架 + 可插拔的 未发现层 / 交战层 / 死亡层」替换 `PerceptionStateMachine`,并让约 95% 的敌人 AI 成为配方资产而非 C# 类。
|
**Goal:** 用「固定感知骨架 + 可插拔的 未发现层 / 交战层 / 死亡层」替换 `PerceptionStateMachine`,并让约 95% 的敌人 AI 成为配方资产而非 C# 类。
|
||||||
|
|
||||||
**Architecture:** 三个模块契约(`IUnawareModule` / `IEngagementModule` / `IDeathModule`)各自声明自己的状态与边;`PerceptionSkeleton` 只负责把「未发现 → 警觉 → 交战」的升降级边挂到模块声明的态上。模块是 `[Serializable]` 普通类,既能在 C# 里 `new`,也能经 `[SerializeReference]` 存进 `PerceptionRecipeSO` 配方资产。`EnemyAiBrain` 上「配方资产引用」与「AiScript 定义 id」二选一。
|
**Architecture:** 两个模块契约(`IUnawareModule` / `IEngagementModule`)各自声明自己的状态与边;`PerceptionSkeleton` 只负责把「未发现 → 警觉 → 交战」的升降级边挂到模块声明的态上,并自行声明 `Death` 终态(死亡归物理层,不做成可插拔层——见 Task 9R)。模块是 `[Serializable]` 普通类,既能在 C# 里 `new`,也能经 `[SerializeReference]` 存进 `PerceptionRecipeSO` 配方资产。`EnemyAiBrain` 上「配方资产引用」与「AiScript 定义 id」二选一。
|
||||||
|
|
||||||
**Tech Stack:** Unity / C# 9 / NUnit EditMode / 自研 BrainGraph(`BaseGames.AI`)
|
**Tech Stack:** Unity / C# 9 / NUnit EditMode / 自研 BrainGraph(`BaseGames.AI`)
|
||||||
|
|
||||||
@@ -60,17 +60,15 @@ Assets/_Game/Scripts/AI/ 框架层(只依赖 Base
|
|||||||
|
|
||||||
Assets/_Game/Scripts/Enemies/AIBrain/Modules/ 敌人决策组合层
|
Assets/_Game/Scripts/Enemies/AIBrain/Modules/ 敌人决策组合层
|
||||||
AiStateFragments.cs 建态原语(4 个)
|
AiStateFragments.cs 建态原语(4 个)
|
||||||
IUnawareModule.cs / IEngagementModule.cs / IDeathModule.cs
|
IUnawareModule.cs / IEngagementModule.cs
|
||||||
PerceptionSkeleton.cs 感知三层规则
|
PerceptionSkeleton.cs 感知规则 + 自声明 Death 终态
|
||||||
Unaware/SinglePost.cs
|
Unaware/SinglePost.cs
|
||||||
Unaware/DisguiseThenPatrol.cs
|
Unaware/DisguiseThenPatrol.cs
|
||||||
Unaware/AlternatingIdlePatrol.cs
|
Unaware/AlternatingIdlePatrol.cs
|
||||||
Engagement/RushExit.cs
|
Engagement/RushExit.cs
|
||||||
Engagement/RushEngagement.cs
|
Engagement/RushEngagement.cs
|
||||||
Engagement/ApproachAttackEngagement.cs
|
Engagement/ApproachAttackEngagement.cs
|
||||||
Death/TerminalDeath.cs
|
(无 Death/ 目录:死亡归物理层,见 Task 9R)
|
||||||
Death/AbilityDeath.cs
|
|
||||||
Death/TwoStageDeath.cs
|
|
||||||
|
|
||||||
Assets/_Game/Scripts/Enemies/AIBrain/Recipes/
|
Assets/_Game/Scripts/Enemies/AIBrain/Recipes/
|
||||||
PerceptionRecipeSO.cs 一个 SO 类型覆盖所有感知型敌人
|
PerceptionRecipeSO.cs 一个 SO 类型覆盖所有感知型敌人
|
||||||
@@ -1408,7 +1406,24 @@ git commit -m "feat(enemy): ApproachAttackEngagement——攻击改为起手即
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task 9: 死亡层三个模块
|
## Task 9: ~~死亡层三个模块~~ → 已作废,改为 Task 9R
|
||||||
|
|
||||||
|
> **本任务已作废。** 三个死亡模块曾按下方原稿实现并提交(`3bc7eb1`、`5a9c1fd`),
|
||||||
|
> 但质量审查发现 `TwoStageDeath` 在真实运行中必然卡死,追根后确认整个死亡层放错了层。
|
||||||
|
> 详见 spec 的「修订:死亡层取消」一节与 commit `023a4a4`。
|
||||||
|
>
|
||||||
|
> **必然卡死的机制**:`EnemyBase.PerformDeath` 先 `ForceState(EnemyStateType.Dead)`(终态)
|
||||||
|
> 再 `_brain.Send(AiSignal.Died)`。`IsControllable` 是 `CurrentState == Controlled`,此后永久为假。
|
||||||
|
> `AiRuntime.Tick` 的顺序是「事件转换(不受门限制)→ IsControllable 让位门(为假即 return)→
|
||||||
|
> 条件转换」,所以 `Died` 事件能切进死亡链,但**此后任何条件边都不再被求值**,
|
||||||
|
> `TwoStageDeath` 的 `preDone` 边永不触发。
|
||||||
|
>
|
||||||
|
> `AbilityDeath` 同样不成立:死亡态 `OnEnter` 执行时,`InterruptAll(Dead)` 已跑过、
|
||||||
|
> 碰撞体已关、`Dead` 动画已在播,此刻触发能力是在和 `PerformDeath` 的死亡演出打架。
|
||||||
|
>
|
||||||
|
> **由 Task 9R 取代。** 下方原稿仅作历史记录保留,不要照它实施。
|
||||||
|
|
||||||
|
<details><summary>原稿(已作废,勿实施)</summary>
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death/TerminalDeath.cs`
|
- Create: `Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death/TerminalDeath.cs`
|
||||||
@@ -1447,10 +1462,10 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
var rt = RunWithDeath(new TerminalDeath(), ctx);
|
var rt = RunWithDeath(new TerminalDeath(), ctx);
|
||||||
rt.Send(AiSignal.Died);
|
rt.Send(AiSignal.Died);
|
||||||
rt.Tick(0.1f);
|
rt.Tick(0.1f);
|
||||||
Assert.AreEqual(TerminalDeath.Death, rt.CurrentStateName);
|
Assert.AreEqual(PerceptionSkeleton.Death, rt.CurrentStateName);
|
||||||
Assert.AreEqual(0, ctx.C.Used.Count); // 演出走物理状态机,AI 不触发能力
|
Assert.AreEqual(0, ctx.C.Used.Count); // 演出走物理状态机,AI 不触发能力
|
||||||
rt.Tick(0.1f);
|
rt.Tick(0.1f);
|
||||||
Assert.AreEqual(TerminalDeath.Death, rt.CurrentStateName);
|
Assert.AreEqual(PerceptionSkeleton.Death, rt.CurrentStateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -1607,6 +1622,49 @@ git add Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death Assets/Tests/EditMode
|
|||||||
git commit -m "feat(enemy): 死亡层三模块(终态/单段/两段)"
|
git commit -m "feat(enemy): 死亡层三模块(终态/单段/两段)"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 9R: 收掉死亡层,死亡归物理层
|
||||||
|
|
||||||
|
**为什么**:见上方 Task 9 的作废说明与 spec 的「修订:死亡层取消」。要点是——死亡在本项目里
|
||||||
|
完全归物理层(`PerformDeath` 切终态/清效果/关碰撞/播死亡动画/归还池;`EnemyDeathSequence`
|
||||||
|
做死亡前摇无敌演出并在演出中经动画事件生成小怪;`EnemyAnimationConfigSO.Dead` 提供动画)。
|
||||||
|
**AI 图需要 `Death` 态只是为了让全局 `Died` 边有去处、并让决策停止。死亡不是一个决策。**
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Delete: `Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death/`(整个目录,三个模块 + meta)
|
||||||
|
- Delete: `Assets/_Game/Scripts/Enemies/AIBrain/Modules/IDeathModule.cs`(+ meta)
|
||||||
|
- Delete: `Assets/Tests/EditMode/AI/DeathModuleTests.cs`(+ meta)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 删除三个死亡模块、契约与测试**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git rm -r Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death Assets/_Game/Scripts/Enemies/AIBrain/Modules/Death.meta
|
||||||
|
git rm Assets/_Game/Scripts/Enemies/AIBrain/Modules/IDeathModule.cs Assets/_Game/Scripts/Enemies/AIBrain/Modules/IDeathModule.cs.meta
|
||||||
|
git rm Assets/Tests/EditMode/AI/DeathModuleTests.cs Assets/Tests/EditMode/AI/DeathModuleTests.cs.meta
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 确认无残留引用**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn "IDeathModule\|TerminalDeath\|AbilityDeath\|TwoStageDeath" Assets --include=*.cs
|
||||||
|
```
|
||||||
|
期望:无结果。(`PerceptionSkeleton` 尚未实现,所以此时不该有引用。)
|
||||||
|
|
||||||
|
- [ ] **Step 3: 确认编译 + 全量测试**
|
||||||
|
|
||||||
|
MCP 查编译,期望 0 错误。跑全量 EditMode:**从 247 回落到 243**(删掉 4 个死亡层测试)。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "revert(enemy): 收掉死亡层——死亡归物理层,AI 只需一个终态"
|
||||||
|
```
|
||||||
|
|
||||||
|
死亡终态改由 `PerceptionSkeleton` 自己声明,见 Task 10。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task 10: `PerceptionSkeleton`
|
## Task 10: `PerceptionSkeleton`
|
||||||
@@ -1651,8 +1709,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
var b = new BrainBuilder();
|
var b = new BrainBuilder();
|
||||||
PerceptionSkeleton.Add(b,
|
PerceptionSkeleton.Add(b,
|
||||||
unaware ?? new SinglePost(LocomotionMode.Patrol),
|
unaware ?? new SinglePost(LocomotionMode.Patrol),
|
||||||
engagement ?? new RushEngagement("rush", RushExit.OnLostTarget),
|
engagement ?? new RushEngagement("rush", RushExit.OnLostTarget));
|
||||||
new TerminalDeath());
|
|
||||||
return new AiRuntime(b.Build(), ctx);
|
return new AiRuntime(b.Build(), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1767,7 +1824,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
|
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
|
||||||
rt.Send(AiSignal.Died);
|
rt.Send(AiSignal.Died);
|
||||||
rt.Tick(0.1f);
|
rt.Tick(0.1f);
|
||||||
Assert.AreEqual(TerminalDeath.Death, rt.CurrentStateName);
|
Assert.AreEqual(PerceptionSkeleton.Death, rt.CurrentStateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -1778,7 +1835,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
ctx.V.Controllable = false; // 硬直中
|
ctx.V.Controllable = false; // 硬直中
|
||||||
rt.Send(AiSignal.Died);
|
rt.Send(AiSignal.Died);
|
||||||
rt.Tick(0.1f);
|
rt.Tick(0.1f);
|
||||||
Assert.AreEqual(TerminalDeath.Death, rt.CurrentStateName);
|
Assert.AreEqual(PerceptionSkeleton.Death, rt.CurrentStateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 校验 ──────────────────────────────────────────────────────────
|
// ── 校验 ──────────────────────────────────────────────────────────
|
||||||
@@ -1806,7 +1863,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
{
|
{
|
||||||
var b = new BrainBuilder();
|
var b = new BrainBuilder();
|
||||||
Assert.Throws<System.InvalidOperationException>(() => PerceptionSkeleton.Add(
|
Assert.Throws<System.InvalidOperationException>(() => PerceptionSkeleton.Add(
|
||||||
b, new BadUnaware(), new RushEngagement("rush"), new TerminalDeath()));
|
b, new BadUnaware(), new RushEngagement("rush")));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -1814,7 +1871,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
{
|
{
|
||||||
var b = new BrainBuilder();
|
var b = new BrainBuilder();
|
||||||
Assert.Throws<System.InvalidOperationException>(() => PerceptionSkeleton.Add(
|
Assert.Throws<System.InvalidOperationException>(() => PerceptionSkeleton.Add(
|
||||||
b, new UndeclaredUnaware(), new RushEngagement("rush"), new TerminalDeath()));
|
b, new UndeclaredUnaware(), new RushEngagement("rush")));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -1822,7 +1879,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
{
|
{
|
||||||
var b = new BrainBuilder();
|
var b = new BrainBuilder();
|
||||||
Assert.Throws<System.ArgumentNullException>(() => PerceptionSkeleton.Add(
|
Assert.Throws<System.ArgumentNullException>(() => PerceptionSkeleton.Add(
|
||||||
b, null, new RushEngagement("rush"), new TerminalDeath()));
|
b, null, new RushEngagement("rush")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1844,8 +1901,8 @@ using BaseGames.AI;
|
|||||||
namespace BaseGames.Enemies
|
namespace BaseGames.Enemies
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 感知骨架:把「未发现 → 警觉 → 交战」的升降级规则,挂到三个模块声明的态上。
|
/// 感知骨架:把「未发现 → 警觉 → 交战」的升降级规则,挂到两个模块声明的态上。
|
||||||
/// 骨架永远只做这一件事——未发现态长什么样、交战怎么打、怎么死,全部归模块。
|
/// 骨架永远只做这一件事——未发现态长什么样、交战怎么打,全部归模块。
|
||||||
///
|
///
|
||||||
/// 规则:
|
/// 规则:
|
||||||
/// 未发现:在追逐区 → 交战(优先);否则 有警觉态且在视野 → 警觉;否则保持。
|
/// 未发现:在追逐区 → 交战(优先);否则 有警觉态且在视野 → 警觉;否则保持。
|
||||||
@@ -1853,12 +1910,19 @@ namespace BaseGames.Enemies
|
|||||||
/// 交战: 脱战条件由交战模块自定,脱战一律回 Rest —— 永不回警觉。
|
/// 交战: 脱战条件由交战模块自定,脱战一律回 Rest —— 永不回警觉。
|
||||||
/// 死亡: 全局事件边,优先级最高,且不受 IsControllable 门阻挡。
|
/// 死亡: 全局事件边,优先级最高,且不受 IsControllable 门阻挡。
|
||||||
///
|
///
|
||||||
|
/// 死亡态由骨架自己声明为无行为终态,不做成可插拔层——死亡在本项目里完全归物理层
|
||||||
|
/// (EnemyBase.PerformDeath 切终态/关碰撞/播死亡动画/归还池;EnemyDeathSequence 做
|
||||||
|
/// 死亡前摇演出并可经动画事件生成小怪)。且 PerformDeath 先 ForceState(Dead) 再发
|
||||||
|
/// Died 信号,此后 IsControllable 永久为假、AiRuntime 不再求值任何条件边,
|
||||||
|
/// 所以死亡链里放条件转换必然卡住。AI 图需要这个态只是为了让全局边有去处、决策停止。
|
||||||
|
///
|
||||||
/// 入口默认取 unaware.Entry;需要前置态(掉落链 / 出场链)的敌人在调用本方法后
|
/// 入口默认取 unaware.Entry;需要前置态(掉落链 / 出场链)的敌人在调用本方法后
|
||||||
/// 再调 b.Entry("自己的态") 覆盖即可。
|
/// 再调 b.Entry("自己的态") 覆盖即可。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PerceptionSkeleton
|
public static class PerceptionSkeleton
|
||||||
{
|
{
|
||||||
public const string Alert = "Alert";
|
public const string Alert = "Alert";
|
||||||
|
public const string Death = "Death";
|
||||||
|
|
||||||
static readonly Func<IAiContext, bool> HasAlertAndInVision =
|
static readonly Func<IAiContext, bool> HasAlertAndInVision =
|
||||||
x => x is IEnemyActor a && a.HasAlertState && x.Sensor.InVisionZone();
|
x => x is IEnemyActor a && a.HasAlertState && x.Sensor.InVisionZone();
|
||||||
@@ -1867,18 +1931,16 @@ namespace BaseGames.Enemies
|
|||||||
|
|
||||||
public static void Add(BrainBuilder b,
|
public static void Add(BrainBuilder b,
|
||||||
IUnawareModule unaware,
|
IUnawareModule unaware,
|
||||||
IEngagementModule engagement,
|
IEngagementModule engagement)
|
||||||
IDeathModule death)
|
|
||||||
{
|
{
|
||||||
if (b == null) throw new ArgumentNullException(nameof(b));
|
if (b == null) throw new ArgumentNullException(nameof(b));
|
||||||
if (unaware == null) throw new ArgumentNullException(nameof(unaware));
|
if (unaware == null) throw new ArgumentNullException(nameof(unaware));
|
||||||
if (engagement == null) throw new ArgumentNullException(nameof(engagement));
|
if (engagement == null) throw new ArgumentNullException(nameof(engagement));
|
||||||
if (death == null) throw new ArgumentNullException(nameof(death));
|
|
||||||
|
|
||||||
// 1) 各模块先声明自己的态(未发现层此阶段只声明态,不挂内部边)
|
// 1) 各模块先声明自己的态(未发现层此阶段只声明态,不挂内部边)
|
||||||
unaware.Declare(b);
|
unaware.Declare(b);
|
||||||
engagement.Build(b, unaware.Rest);
|
engagement.Build(b, unaware.Rest);
|
||||||
death.Build(b);
|
AiStateFragments.Terminal(b, Death);
|
||||||
|
|
||||||
var states = unaware.States;
|
var states = unaware.States;
|
||||||
if (states == null || states.Count == 0)
|
if (states == null || states.Count == 0)
|
||||||
@@ -1890,7 +1952,7 @@ namespace BaseGames.Enemies
|
|||||||
$"{unaware.GetType().Name}: Rest='{unaware.Rest}' 必须属于 States。");
|
$"{unaware.GetType().Name}: Rest='{unaware.Rest}' 必须属于 States。");
|
||||||
|
|
||||||
b.Entry(unaware.Entry);
|
b.Entry(unaware.Entry);
|
||||||
b.Global().To(death.EntryState).OnEvent(AiSignal.Died);
|
b.Global().To(Death).OnEvent(AiSignal.Died);
|
||||||
|
|
||||||
// 2) 升级边先挂——AiRuntime 按声明顺序评估条件边,升级必须优先于
|
// 2) 升级边先挂——AiRuntime 按声明顺序评估条件边,升级必须优先于
|
||||||
// 未发现层的内部边(如巡逻计时),否则玩家进追逐区那一帧可能被计时边抢走。
|
// 未发现层的内部边(如巡逻计时),否则玩家进追逐区那一帧可能被计时边抢走。
|
||||||
@@ -1949,7 +2011,7 @@ using BaseGames.AI;
|
|||||||
namespace BaseGames.Enemies
|
namespace BaseGames.Enemies
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// E001(草蛭)AI —— 纯决策层,组装三层模块。
|
/// E001(草蛭)AI —— 纯决策层,组装两层模块。
|
||||||
/// 出生伪装静止(伪装成石头),一旦交战过就只回巡逻;追击=带冷却的接触冲锋,
|
/// 出生伪装静止(伪装成石头),一旦交战过就只回巡逻;追击=带冷却的接触冲锋,
|
||||||
/// 起手即锁定、打完回巡逻走冷却;死亡演出走物理状态机(EnemyBase.PerformDeath)。
|
/// 起手即锁定、打完回巡逻走冷却;死亡演出走物理状态机(EnemyBase.PerformDeath)。
|
||||||
/// AI 只决策;移动 / 朝向 / 速度 / 动画 / 伤害全部在能力与 EnemyLocomotion 里实现。
|
/// AI 只决策;移动 / 朝向 / 速度 / 动画 / 伤害全部在能力与 EnemyLocomotion 里实现。
|
||||||
@@ -1960,8 +2022,7 @@ namespace BaseGames.Enemies
|
|||||||
{
|
{
|
||||||
protected override void Build(BrainBuilder b) => PerceptionSkeleton.Add(b,
|
protected override void Build(BrainBuilder b) => PerceptionSkeleton.Add(b,
|
||||||
unaware: new DisguiseThenPatrol(),
|
unaware: new DisguiseThenPatrol(),
|
||||||
engagement: new RushEngagement("e001_chase", RushExit.Committed),
|
engagement: new RushEngagement("e001_chase", RushExit.Committed));
|
||||||
death: new TerminalDeath());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -2130,8 +2191,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
var so = ScriptableObject.CreateInstance<PerceptionRecipeSO>();
|
var so = ScriptableObject.CreateInstance<PerceptionRecipeSO>();
|
||||||
so.SetModulesForTests(
|
so.SetModulesForTests(
|
||||||
new DisguiseThenPatrol(),
|
new DisguiseThenPatrol(),
|
||||||
new RushEngagement("e001_chase", RushExit.Committed),
|
new RushEngagement("e001_chase", RushExit.Committed));
|
||||||
new TerminalDeath());
|
|
||||||
return so;
|
return so;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2152,8 +2212,7 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
var b = new BrainBuilder();
|
var b = new BrainBuilder();
|
||||||
PerceptionSkeleton.Add(b,
|
PerceptionSkeleton.Add(b,
|
||||||
new DisguiseThenPatrol(),
|
new DisguiseThenPatrol(),
|
||||||
new RushEngagement("e001_chase", RushExit.Committed),
|
new RushEngagement("e001_chase", RushExit.Committed));
|
||||||
new TerminalDeath());
|
|
||||||
var handWritten = b.Build();
|
var handWritten = b.Build();
|
||||||
|
|
||||||
Assert.AreEqual(handWritten.EntryState, fromRecipe.EntryState);
|
Assert.AreEqual(handWritten.EntryState, fromRecipe.EntryState);
|
||||||
@@ -2235,16 +2294,17 @@ namespace BaseGames.Enemies
|
|||||||
[Tooltip("交战层:发现玩家之后怎么打")]
|
[Tooltip("交战层:发现玩家之后怎么打")]
|
||||||
[SerializeReference, SubclassSelector] IEngagementModule _engagement = new RushEngagement();
|
[SerializeReference, SubclassSelector] IEngagementModule _engagement = new RushEngagement();
|
||||||
|
|
||||||
[Tooltip("死亡层:怎么死")]
|
// 无「死亡层」字段:死亡归物理层,骨架自己声明 Death 终态。详见 spec 的
|
||||||
[SerializeReference, SubclassSelector] IDeathModule _death = new TerminalDeath();
|
// 「修订:死亡层取消」——PerformDeath 先 ForceState(Dead) 再发 Died 信号,
|
||||||
|
// 此后 IsControllable 永久为假,死亡链里的条件边永不被求值。
|
||||||
|
|
||||||
protected override void Build(BrainBuilder b)
|
protected override void Build(BrainBuilder b)
|
||||||
=> PerceptionSkeleton.Add(b, _unaware, _engagement, _death);
|
=> PerceptionSkeleton.Add(b, _unaware, _engagement);
|
||||||
|
|
||||||
/// <summary>仅供 EditMode 测试与脚手架向导装配模块,运行时不使用。</summary>
|
/// <summary>仅供 EditMode 测试与脚手架向导装配模块,运行时不使用。</summary>
|
||||||
public void SetModulesForTests(IUnawareModule unaware, IEngagementModule engagement, IDeathModule death)
|
public void SetModulesForTests(IUnawareModule unaware, IEngagementModule engagement)
|
||||||
{
|
{
|
||||||
_unaware = unaware; _engagement = engagement; _death = death;
|
_unaware = unaware; _engagement = engagement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user