Files
zeling_v2/Assets/Tests/EditMode/AI/PerceptionRecipeSoTests.cs
T

152 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using NUnit.Framework;
using UnityEngine;
using BaseGames.AI;
using BaseGames.Enemies;
namespace BaseGames.Tests.EditMode.AI
{
public class PerceptionRecipeSoTests
{
sealed class Ctx : IAiContext, IEnemyActor
{
public FakeSensor S = new FakeSensor();
public FakeLocomotion L = new FakeLocomotion();
public FakeCombat C = new FakeCombat();
public FakeVitals V = new FakeVitals();
public Blackboard BB = new Blackboard();
public ISensor Sensor => S;
public IEnemyLocomotion Locomotion => L;
public ICombatant Combat => C;
public IActorVitals Vitals => V;
public Blackboard Blackboard => BB;
public bool HasAlertState => true;
}
static PerceptionRecipeSO MakeE001Recipe()
{
var so = ScriptableObject.CreateInstance<PerceptionRecipeSO>();
so.AssignModules(
new DisguiseThenPatrol(),
new RushEngagement("e001_chase", RushExit.Committed));
return so;
}
[Test]
public void Graph_IsCachedAcrossCalls_Flyweight()
{
var so = MakeE001Recipe();
Assert.AreSame(so.GetOrBuildGraph(), so.GetOrBuildGraph());
Object.DestroyImmediate(so);
}
[Test]
public void Graph_MatchesEquivalentHandWrittenAssembly()
{
var so = MakeE001Recipe();
var fromRecipe = so.GetOrBuildGraph();
var b = new BrainBuilder();
PerceptionSkeleton.Add(b,
new DisguiseThenPatrol(),
new RushEngagement("e001_chase", RushExit.Committed));
var handWritten = b.Build();
Assert.AreEqual(handWritten.EntryState, fromRecipe.EntryState);
Object.DestroyImmediate(so);
}
[Test]
public void Graph_BehavesLikeE001()
{
var so = MakeE001Recipe();
var ctx = new Ctx();
var rt = new AiRuntime(so.GetOrBuildGraph(), ctx);
Assert.AreEqual(DisguiseThenPatrol.Disguise, rt.CurrentStateName);
ctx.S.Chase = true;
rt.Tick(0.1f);
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
Object.DestroyImmediate(so);
}
[Test]
public void UnconfiguredRecipe_ThrowsClearError_NotSilentlyInert()
{
// 新建的空配方默认用 RushEngagement 但尚未配能力。建图时必须**显式报错**,
// 而不是静默产出一张"永远不会出手"的图——第 6 条:暴露问题,不掩盖。
// 注:策划建完资产到配好模块之间没有任何东西会调 GetOrBuildGraph()
// 它只在 EnemyAiBrain.Start() 运行时被调,所以这里报错不影响编辑期体验。
var so = ScriptableObject.CreateInstance<PerceptionRecipeSO>();
var ex = Assert.Throws<System.InvalidOperationException>(() => so.GetOrBuildGraph());
StringAssert.Contains("RushEngagement", ex.Message);
Object.DestroyImmediate(so);
}
[Test]
public void AssignModules_InvalidatesCachedGraph()
{
// 不失效的话,装配后拿到的还是旧图——静默的陈旧配置。
var so = MakeE001Recipe();
var first = so.GetOrBuildGraph();
so.AssignModules(new SinglePost(LocomotionMode.Idle),
new RushEngagement("other", RushExit.OnLostTarget));
var second = so.GetOrBuildGraph();
Assert.AreNotSame(first, second);
Assert.AreEqual(SinglePost.Post, second.EntryState); // 新模块生效
Object.DestroyImmediate(so);
}
static System.Collections.Generic.List<BaseGames.Core.ValidationResult> Validate(PerceptionRecipeSO so)
=> new System.Collections.Generic.List<BaseGames.Core.ValidationResult>(so.Validate());
[Test]
public void Validate_Clean_WhenFullyConfigured()
{
var so = MakeE001Recipe();
CollectionAssert.IsEmpty(Validate(so));
Object.DestroyImmediate(so);
}
[Test]
public void Validate_ReportsError_WhenAbilityMissing()
{
// 默认配方:RushEngagement 未配能力
var so = ScriptableObject.CreateInstance<PerceptionRecipeSO>();
var results = Validate(so);
Assert.IsTrue(results.Exists(r => r.Severity == BaseGames.Core.ValidationSeverity.Error));
Object.DestroyImmediate(so);
}
[Test]
public void Validate_DoesNotLeaveCachedGraph()
{
// 校验期试建的图不能留给运行时——否则改了模块后拿到的是校验时的旧图。
var so = MakeE001Recipe();
Validate(so);
so.AssignModules(new SinglePost(LocomotionMode.Idle),
new RushEngagement("other", RushExit.OnLostTarget));
Assert.AreEqual(SinglePost.Post, so.GetOrBuildGraph().EntryState);
Object.DestroyImmediate(so);
}
[Test]
public void Validate_ReportsError_WhenCommittedRushHasZeroCooldown()
{
// Committed 的防抖动保证依赖能力自身有冷却;cooldown<=0 时冲锋态↔未发现态
// 每帧抖动,这是配置期就能发现的错误,必须在校验里显式报出。
var ability = ScriptableObject.CreateInstance<BaseGames.Enemies.Abilities.EnemyAbilitySO>();
ability.abilityId = "e001_chase";
ability.cooldown = 0f;
var so = ScriptableObject.CreateInstance<PerceptionRecipeSO>();
so.AssignModules(new DisguiseThenPatrol(), new RushEngagement(ability, RushExit.Committed));
var results = Validate(so);
Assert.IsTrue(results.Exists(r =>
r.Severity == BaseGames.Core.ValidationSeverity.Error && r.Message.Contains("cooldown")));
Object.DestroyImmediate(so);
Object.DestroyImmediate(ability);
}
}
}