From 9dc10e03982d287264e29b5ebdeb06758d5e33db Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 29 Jul 2026 14:21:36 +0800 Subject: [PATCH] =?UTF-8?q?test(enemy):=20=E8=A1=A5=E6=B5=8B=E9=AA=A8?= =?UTF-8?q?=E6=9E=B6=20=C3=97=20=E5=8F=A6=E4=B8=A4=E4=B8=AA=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=E7=BB=84=E5=90=88=EF=BC=8C=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E8=A6=86=E7=9B=96=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditMode/AI/PerceptionSkeletonTests.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Assets/Tests/EditMode/AI/PerceptionSkeletonTests.cs b/Assets/Tests/EditMode/AI/PerceptionSkeletonTests.cs index b3f49a15..882dd2ab 100644 --- a/Assets/Tests/EditMode/AI/PerceptionSkeletonTests.cs +++ b/Assets/Tests/EditMode/AI/PerceptionSkeletonTests.cs @@ -216,5 +216,71 @@ namespace BaseGames.Tests.EditMode.AI Assert.Throws(() => PerceptionSkeleton.Add( 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); // 升级边照常生效 + } } }