fix(ai): AiRuntime 自转换设为no-op(根治死亡双发Died再入) + EnemyBase.OnSpawn复位brain.enabled + 测试
Switch 对 target==当前态直接 no-op(不重跑Exit/Enter),循环改为 if(Switch(t))return true; OnSpawn 复用时恢复被 QuotaManager 裁剪禁用的 brain。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,23 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
Assert.AreEqual("Flee", rt.CurrentStateName);
|
Assert.AreEqual("Flee", rt.CurrentStateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EventSelfTransition_DoesNotReenter()
|
||||||
|
{
|
||||||
|
var b = new BrainBuilder();
|
||||||
|
b.Entry("Dead");
|
||||||
|
int enterCount = 0;
|
||||||
|
b.State("Dead").OnEnter(c => enterCount++);
|
||||||
|
b.Global().To("Dead").OnEvent(AiSignal.Died);
|
||||||
|
var ctx = new FakeAiContext();
|
||||||
|
var rt = new AiRuntime(b.Build(), ctx);
|
||||||
|
// 构造即进入 Dead → enterCount==1
|
||||||
|
rt.Send(AiSignal.Died);
|
||||||
|
rt.Tick(0.1f); // 已在 Dead,自转换应 no-op,不再 +1
|
||||||
|
Assert.AreEqual(1, enterCount);
|
||||||
|
Assert.AreEqual("Dead", rt.CurrentStateName);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TwoRuntimes_ShareGraph_IndependentStateAndTimer()
|
public void TwoRuntimes_ShareGraph_IndependentStateAndTimer()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace BaseGames.AI
|
|||||||
{
|
{
|
||||||
var t = globals[i];
|
var t = globals[i];
|
||||||
if (!t.IsEvent && t.Condition(_ctx))
|
if (!t.IsEvent && t.Condition(_ctx))
|
||||||
return Switch(t);
|
{ if (Switch(t)) return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
var locals = _current.Transitions;
|
var locals = _current.Transitions;
|
||||||
@@ -93,7 +93,7 @@ namespace BaseGames.AI
|
|||||||
{
|
{
|
||||||
var t = locals[i];
|
var t = locals[i];
|
||||||
if (!t.IsEvent && t.Condition(_ctx))
|
if (!t.IsEvent && t.Condition(_ctx))
|
||||||
return Switch(t);
|
{ if (Switch(t)) return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -106,7 +106,7 @@ namespace BaseGames.AI
|
|||||||
{
|
{
|
||||||
var t = globals[i];
|
var t = globals[i];
|
||||||
if (t.IsEvent && t.Event.Value == sig)
|
if (t.IsEvent && t.Event.Value == sig)
|
||||||
return Switch(t);
|
{ if (Switch(t)) return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
var locals = _current.Transitions;
|
var locals = _current.Transitions;
|
||||||
@@ -114,13 +114,14 @@ namespace BaseGames.AI
|
|||||||
{
|
{
|
||||||
var t = locals[i];
|
var t = locals[i];
|
||||||
if (t.IsEvent && t.Event.Value == sig)
|
if (t.IsEvent && t.Event.Value == sig)
|
||||||
return Switch(t);
|
{ if (Switch(t)) return true; }
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Switch(Transition t)
|
bool Switch(Transition t)
|
||||||
{
|
{
|
||||||
|
if (t.Target == _current.Name) return false; // 自转换:no-op,不重跑 Exit/Enter
|
||||||
var from = _current.Name;
|
var from = _current.Name;
|
||||||
_current.OnExit?.Invoke(_ctx);
|
_current.OnExit?.Invoke(_ctx);
|
||||||
_current = _graph.GetState(t.Target);
|
_current = _graph.GetState(t.Target);
|
||||||
|
|||||||
@@ -724,6 +724,7 @@ namespace BaseGames.Enemies
|
|||||||
_abilities.InterruptAll(InterruptReason.Dead);
|
_abilities.InterruptAll(InterruptReason.Dead);
|
||||||
|
|
||||||
// 重置决策层(回到 Entry、清临时态)
|
// 重置决策层(回到 Entry、清临时态)
|
||||||
|
if (_brain != null) _brain.enabled = true; // 复用时恢复决策组件(可能曾被 QuotaManager 裁剪禁用)
|
||||||
_brain?.ResetBrain();
|
_brain?.ResetBrain();
|
||||||
|
|
||||||
// 通知配置型出生行为组件(如 EnemyAbilityTrigger)执行出生触发逻辑
|
// 通知配置型出生行为组件(如 EnemyAbilityTrigger)执行出生触发逻辑
|
||||||
|
|||||||
Reference in New Issue
Block a user