diff --git a/Assets/Tests/EditMode/AI/AiRuntimeTests.cs b/Assets/Tests/EditMode/AI/AiRuntimeTests.cs index 9748d14d..a650d32e 100644 --- a/Assets/Tests/EditMode/AI/AiRuntimeTests.cs +++ b/Assets/Tests/EditMode/AI/AiRuntimeTests.cs @@ -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 + } } } diff --git a/Assets/_Game/Scripts/AI/AiRuntime.cs b/Assets/_Game/Scripts/AI/AiRuntime.cs index c767c320..51783243 100644 --- a/Assets/_Game/Scripts/AI/AiRuntime.cs +++ b/Assets/_Game/Scripts/AI/AiRuntime.cs @@ -54,7 +54,10 @@ namespace BaseGames.AI { var sig = _pending.Dequeue(); if (TryEventTransition(sig)) - return; // 事件触发转换后本帧结束 + { + IsSuspended = false; // 已推进转换,非挂起 + return; + } } // 2) IsControllable 让位门:受击/硬直/击飞时挂起,不推进决策也不跑 Tick。 diff --git a/Assets/_Game/Scripts/AI/BrainBuilder.cs b/Assets/_Game/Scripts/AI/BrainBuilder.cs index 6b754ed6..fe64a4e6 100644 --- a/Assets/_Game/Scripts/AI/BrainBuilder.cs +++ b/Assets/_Game/Scripts/AI/BrainBuilder.cs @@ -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 fn) { _state.OnEnter = fn; return this; } public StateBuilder Tick(Action fn) { _state.OnTick = (c, _) => fn(c); return this; }