State() 仍保持宽松(有则取),用于给已声明的态挂边;DeclareState() 只用于 首次声明行为,重复声明同名状态会抛异常。AiStateFragments 的四个建态原语 (Locomotion/Ability/AbilityOnce/Terminal)改用 DeclareState。同时给 IEngagementModule/IDeathModule/IUnawareModule 补充不变量说明。
119 lines
3.7 KiB
C#
119 lines
3.7 KiB
C#
using NUnit.Framework;
|
|
using BaseGames.AI;
|
|
|
|
namespace BaseGames.Tests.EditMode.AI
|
|
{
|
|
public class BrainBuilderTests
|
|
{
|
|
static AiGraph BuildSample()
|
|
{
|
|
var b = new BrainBuilder();
|
|
b.Entry("Patrol");
|
|
b.Global().To("Dead").OnEvent(AiSignal.Died);
|
|
b.State("Patrol")
|
|
.To("Chase").When(c => c.Sensor.SeesPlayer(), "SeesPlayer");
|
|
b.State("Chase")
|
|
.To("Patrol").When(c => c.Sensor.LostFor(2f), "LostFor(2s)");
|
|
b.State("Dead");
|
|
return b.Build();
|
|
}
|
|
|
|
[Test]
|
|
public void Build_SetsEntryState()
|
|
{
|
|
Assert.AreEqual("Patrol", BuildSample().EntryState);
|
|
}
|
|
|
|
[Test]
|
|
public void Build_RegistersAllStates()
|
|
{
|
|
var g = BuildSample();
|
|
CollectionAssert.AreEquivalent(
|
|
new[] { "Patrol", "Chase", "Dead" },
|
|
System.Linq.Enumerable.ToList(g.StateNames));
|
|
}
|
|
|
|
[Test]
|
|
public void Build_UsesExplicitConditionLabel()
|
|
{
|
|
var g = BuildSample();
|
|
var t = g.GetState("Patrol").Transitions[0];
|
|
Assert.AreEqual("SeesPlayer", t.Label);
|
|
}
|
|
|
|
[Test]
|
|
public void Build_UnlabeledCondition_FallsBackToCond()
|
|
{
|
|
var b = new BrainBuilder();
|
|
b.Entry("A");
|
|
b.State("A").To("B").When(c => true);
|
|
b.State("B");
|
|
var g = b.Build();
|
|
Assert.AreEqual("cond", g.GetState("A").Transitions[0].Label);
|
|
}
|
|
|
|
[Test]
|
|
public void Build_GlobalTransitionsAreSeparate()
|
|
{
|
|
var g = BuildSample();
|
|
Assert.AreEqual(1, g.GlobalTransitions.Count);
|
|
Assert.AreEqual("Dead", g.GlobalTransitions[0].Target);
|
|
Assert.AreEqual(AiSignal.Died, g.GlobalTransitions[0].Event);
|
|
}
|
|
|
|
[Test]
|
|
public void Build_UnknownTransitionTarget_Throws()
|
|
{
|
|
var b = new BrainBuilder();
|
|
b.Entry("A");
|
|
b.State("A").To("Nonexistent").When(c => true);
|
|
Assert.Throws<System.InvalidOperationException>(() => b.Build());
|
|
}
|
|
|
|
[Test]
|
|
public void RequireState_Throws_WhenStateNotDeclared()
|
|
{
|
|
var b = new BrainBuilder();
|
|
var ex = Assert.Throws<System.InvalidOperationException>(() => b.RequireState("Ghost"));
|
|
StringAssert.Contains("Ghost", ex.Message);
|
|
}
|
|
|
|
[Test]
|
|
public void RequireState_Passes_WhenStateDeclared()
|
|
{
|
|
var b = new BrainBuilder();
|
|
b.State("Real");
|
|
Assert.DoesNotThrow(() => b.RequireState("Real"));
|
|
}
|
|
|
|
[Test]
|
|
public void DeclareState_Throws_OnDuplicateName()
|
|
{
|
|
var b = new BrainBuilder();
|
|
b.DeclareState("Dup");
|
|
var ex = Assert.Throws<System.InvalidOperationException>(() => b.DeclareState("Dup"));
|
|
StringAssert.Contains("Dup", ex.Message);
|
|
}
|
|
|
|
[Test]
|
|
public void DeclareState_ReturnsUsableBuilder_OnFirstDeclare()
|
|
{
|
|
var b = new BrainBuilder();
|
|
b.Entry("A");
|
|
b.DeclareState("A").To("B").When(c => true, "go");
|
|
b.DeclareState("B");
|
|
Assert.DoesNotThrow(() => b.Build());
|
|
}
|
|
|
|
[Test]
|
|
public void State_StaysLenient_ForAttachingToDeclaredState()
|
|
{
|
|
// State() 仍是"有则取"——骨架要给模块已声明的态挂升级边,靠的就是这个。
|
|
var b = new BrainBuilder();
|
|
b.Entry("A");
|
|
b.DeclareState("A");
|
|
Assert.DoesNotThrow(() => b.State("A").To("A").When(c => false, "noop"));
|
|
}
|
|
}
|
|
}
|