feat(ai): BrainGraph AiGraph + BrainBuilder(fluent, 显式标签, 目标校验)

This commit is contained in:
2026-07-03 14:04:51 +08:00
parent 3d6cef1f41
commit 6f9fa8c4b8
8 changed files with 301 additions and 0 deletions
@@ -0,0 +1,73 @@
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());
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0dd4a9e62b5f3614cbf783a420b80ee8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: