test(enemy): 补测骨架 × 另两个模块的组合,以及入口覆盖契约
This commit is contained in:
@@ -216,5 +216,71 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
Assert.Throws<System.ArgumentNullException>(() => PerceptionSkeleton.Add(
|
Assert.Throws<System.ArgumentNullException>(() => PerceptionSkeleton.Add(
|
||||||
b, null, new RushEngagement("rush")));
|
b, null, new RushEngagement("rush")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 模块组合:抽象的存在理由,必须端到端验证 ─────────────────────
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Skeleton_WithApproachAttack_EscalatesAndDisengages()
|
||||||
|
{
|
||||||
|
var ctx = new Ctx();
|
||||||
|
var rt = Build(ctx, new SinglePost(LocomotionMode.Patrol), new ApproachAttackEngagement());
|
||||||
|
Assert.AreEqual(SinglePost.Post, rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.S.Chase = true; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(ApproachAttackEngagement.Approach, rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.C.Eligible = true; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(ApproachAttackEngagement.Attack, rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.C.Running = null; rt.Tick(0.1f); // 招式打完
|
||||||
|
Assert.AreEqual(ApproachAttackEngagement.Approach, rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.S.Chase = false; ctx.S.Vision = false; ctx.C.Eligible = false;
|
||||||
|
rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(SinglePost.Post, rt.CurrentStateName); // 脱战回 Rest
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Skeleton_WithDisguiseThenPatrol_DisengagesToPatrol_NotDisguise()
|
||||||
|
{
|
||||||
|
// 唯一 Entry != Rest 的未发现层:出生伪装静止,脱战只回巡逻,伪装态从此不可达。
|
||||||
|
var ctx = new Ctx();
|
||||||
|
var rt = Build(ctx, new DisguiseThenPatrol());
|
||||||
|
Assert.AreEqual(DisguiseThenPatrol.Disguise, rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.S.Chase = true; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.S.Chase = false; ctx.S.Vision = false; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(DisguiseThenPatrol.Patrol, rt.CurrentStateName); // 不是 Disguise
|
||||||
|
|
||||||
|
// 再交战一轮,仍然只回巡逻——伪装态不可逆
|
||||||
|
ctx.S.Chase = true; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
|
||||||
|
ctx.S.Chase = false; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(DisguiseThenPatrol.Patrol, rt.CurrentStateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EntryCanBeOverridden_ForEnemiesWithPrefixStates()
|
||||||
|
{
|
||||||
|
// 骨架注释承诺的出路:掉落链 / 出场链这类前置态,调 Add 之后再覆盖 Entry。
|
||||||
|
var ctx = new Ctx();
|
||||||
|
var b = new BrainBuilder();
|
||||||
|
PerceptionSkeleton.Add(b, new SinglePost(LocomotionMode.Patrol),
|
||||||
|
new RushEngagement("rush", RushExit.OnLostTarget));
|
||||||
|
AiStateFragments.Locomotion(b, "Ceiling", LocomotionMode.Idle)
|
||||||
|
.To(SinglePost.Post).When(x => x.Sensor.SeesPlayer(), "landed");
|
||||||
|
b.Entry("Ceiling"); // 覆盖骨架设的入口
|
||||||
|
|
||||||
|
var rt = new AiRuntime(b.Build(), ctx);
|
||||||
|
Assert.AreEqual("Ceiling", rt.CurrentStateName);
|
||||||
|
|
||||||
|
ctx.S.Sees = true; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(SinglePost.Post, rt.CurrentStateName); // 落地后并入感知层
|
||||||
|
|
||||||
|
ctx.S.Chase = true; rt.Tick(0.1f);
|
||||||
|
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName); // 升级边照常生效
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user