refactor(ai): AiSignal 8→1,删除零发送方零消费方的死枚举

This commit is contained in:
2026-07-29 11:14:29 +08:00
parent e76d6bc118
commit fd238b71f1
3 changed files with 28 additions and 24 deletions
+16 -13
View File
@@ -107,10 +107,10 @@ namespace BaseGames.Tests.EditMode.AI
b.Entry("A");
b.State("A")
.To("ByCond").When(c => true, "cond") // 条件恒真
.To("ByEvent").OnEvent(AiSignal.Parried); // 事件优先(Tick 第1步)
.To("ByEvent").OnEvent(AiSignal.Died); // 事件优先(Tick 第1步)
b.State("ByCond"); b.State("ByEvent");
var rt = new AiRuntime(b.Build(), new FakeAiContext());
rt.Send(AiSignal.Parried);
rt.Send(AiSignal.Died);
rt.Tick(0.1f);
Assert.AreEqual("ByEvent", rt.CurrentStateName);
}
@@ -125,27 +125,30 @@ namespace BaseGames.Tests.EditMode.AI
var ctx = new FakeAiContext();
ctx.S.Sees = true;
var rt = new AiRuntime(b.Build(), ctx);
rt.Send(AiSignal.Parried); // A 无 Parried 转换 → 被消费掉,不阻塞条件评估
rt.Send(AiSignal.Died); // A 无 Died 转换 → 被消费掉,不阻塞条件评估
rt.Tick(0.1f);
Assert.AreEqual("B", rt.CurrentStateName);
}
[Test]
public void MultipleQueuedSignals_FirstMatchWins_RemainingConsumed()
public void QueuedDuplicateSignals_TransitionOnce_RemainderConsumed()
{
// 注:原测试用两个不同信号验证"第一个匹配的先转"。AiSignal 现只有 Died 一个值,
// 该形态暂不可表达;等将来出现第二个真实信号(如战斗激活)时恢复此覆盖。
// 此处覆盖仍然成立的部分:残留信号被消费掉,不会重复触发转换。
var b = new BrainBuilder();
b.Entry("A");
b.State("A")
.To("B").OnEvent(AiSignal.Parried)
.To("C").OnEvent(AiSignal.Staggered);
b.State("B"); b.State("C");
var rt = new AiRuntime(b.Build(), new FakeAiContext());
rt.Send(AiSignal.Parried);
rt.Send(AiSignal.Staggered);
b.State("A").To("B").OnEvent(AiSignal.Died);
b.State("B").OnEnter(c => c.Blackboard.Set("enters", c.Blackboard.Get<int>("enters") + 1));
var ctx = new FakeAiContext();
var rt = new AiRuntime(b.Build(), ctx);
rt.Send(AiSignal.Died);
rt.Send(AiSignal.Died);
rt.Tick(0.1f);
Assert.AreEqual("B", rt.CurrentStateName); // 第一个匹配的 Parried 先转 B
Assert.AreEqual("B", rt.CurrentStateName);
rt.Tick(0.1f);
Assert.AreEqual("B", rt.CurrentStateName); // 残留 Staggered 在 B 无匹配 → 被消费,仍 B
Assert.AreEqual("B", rt.CurrentStateName);
Assert.AreEqual(1, ctx.BB.Get<int>("enters")); // 残留信号被消费,未重入 B
}
// ── 自转换 / OnTick 时序 ──────────────────────────────────────────
+2 -2
View File
@@ -165,11 +165,11 @@ namespace BaseGames.Tests.EditMode.AI
{
var b = new BrainBuilder();
b.Entry("Idle");
b.State("Idle").To("Flee").OnEvent(AiSignal.Parried);
b.State("Idle").To("Flee").OnEvent(AiSignal.Died);
b.State("Flee");
var ctx = new FakeAiContext();
var rt = new AiRuntime(b.Build(), ctx);
rt.Send(AiSignal.Parried);
rt.Send(AiSignal.Died);
rt.Tick(0.1f);
Assert.AreEqual("Flee", rt.CurrentStateName);
}
+10 -9
View File
@@ -1,15 +1,16 @@
namespace BaseGames.AI
{
/// <summary>决策层的推送信号,用于事件驱动的状态转换(不靠轮询)。</summary>
/// <summary>
/// 决策层的推送信号,用于事件驱动的状态转换(不靠轮询)。
///
/// 只保留真实存在发送方与消费方的信号。受击 / 硬直 / 击飞 / 弹反**不在此列**——
/// 它们由敌人物理状态机(EnemyStateType)处理,决策层经 IActorVitals.IsControllable
/// 让位门自动挂起。在这里重复定义会诱导写出与物理层打架的第二套受击逻辑。
///
/// 新增信号的判据:已经有确定的发送方与消费方,否则不要预留。
/// </summary>
public enum AiSignal
{
Damaged, // 受到伤害
Parried, // 被弹反
Staggered, // 进入硬直
KnockedUp, // 被击飞
Died, // 死亡
PlayerSpotted,// 首次侦测到玩家
AbilityEnded, // 当前能力协程结束/被打断
PhaseChanged // Boss 阶段变更
Died,
}
}