refactor(ai): 终审收尾——删死字段/事件转换刷新IsSuspended + 局部事件与双实例独立性测试

This commit is contained in:
2026-07-03 14:33:23 +08:00
parent 747d39072e
commit 11e4c9f1ea
3 changed files with 43 additions and 4 deletions
@@ -159,5 +159,42 @@ namespace BaseGames.Tests.EditMode.AI
Assert.IsEmpty(rt.Trace);
Assert.IsFalse(ctx.BB.Has("scratch"));
}
[Test]
public void LocalEventTransition_Fires()
{
var b = new BrainBuilder();
b.Entry("Idle");
b.State("Idle").To("Flee").OnEvent(AiSignal.Parried);
b.State("Flee");
var ctx = new FakeAiContext();
var rt = new AiRuntime(b.Build(), ctx);
rt.Send(AiSignal.Parried);
rt.Tick(0.1f);
Assert.AreEqual("Flee", rt.CurrentStateName);
}
[Test]
public void TwoRuntimes_ShareGraph_IndependentStateAndTimer()
{
var graph = Graph(); // 共享同一张不可变图
var a = new FakeAiContext(); var rtA = new AiRuntime(graph, a);
var b = new FakeAiContext(); var rtB = new AiRuntime(graph, b);
// A 发现玩家推进,B 不动 —— 状态独立
a.S.Sees = true; rtA.Tick(0.1f);
Assert.AreEqual("Chase", rtA.CurrentStateName);
Assert.AreEqual("Patrol", rtB.CurrentStateName);
// 两者各自进入 Search(计时归零)
a.S.Sees = false; a.S.LostForValue = 5f; rtA.Tick(0.1f); // A -> Search
b.S.Sees = true; rtB.Tick(0.1f); // B -> Chase
b.S.Sees = false; b.S.LostForValue = 5f; rtB.Tick(0.1f); // B -> Search
// 只推进 A 的 After 计时越过 3s;B 计时独立、不动
rtA.Tick(2f); rtA.Tick(2f); // A Search 计时 ~4 -> After -> Patrol
Assert.AreEqual("Patrol", rtA.CurrentStateName);
Assert.AreEqual("Search", rtB.CurrentStateName); // B 独立,仍在 Search
}
}
}
+4 -1
View File
@@ -54,7 +54,10 @@ namespace BaseGames.AI
{
var sig = _pending.Dequeue();
if (TryEventTransition(sig))
return; // 事件触发转换后本帧结束
{
IsSuspended = false; // 已推进转换,非挂起
return;
}
}
// 2) IsControllable 让位门:受击/硬直/击飞时挂起,不推进决策也不跑 Tick。
+2 -3
View File
@@ -23,7 +23,7 @@ namespace BaseGames.AI
s = new AiState(name);
_states[name] = s;
}
return new StateBuilder(this, s);
return new StateBuilder(s);
}
public GlobalBuilder Global() => new GlobalBuilder(this);
@@ -52,9 +52,8 @@ namespace BaseGames.AI
public sealed class StateBuilder
{
readonly BrainBuilder _owner;
readonly AiState _state;
internal StateBuilder(BrainBuilder owner, AiState state) { _owner = owner; _state = state; }
internal StateBuilder(AiState state) { _state = state; }
public StateBuilder OnEnter(Action<IAiContext> fn) { _state.OnEnter = fn; return this; }
public StateBuilder Tick(Action<IAiContext> fn) { _state.OnTick = (c, _) => fn(c); return this; }