From 022e4f18d200d160086af85bb743a110587ae586 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Fri, 24 Jul 2026 12:47:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(enemy):=20Approach=20=E9=80=BC=E8=BF=91?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20aggro/=E8=A7=86=E9=87=8E=E5=8F=8C=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E7=9A=84=20LastKnown(=E4=BF=AE=20aggro=20=E5=85=88?= =?UTF-8?q?=E5=91=BD=E4=B8=AD=E6=89=91=E5=90=91=E9=94=99=E8=AF=AF=E7=82=B9?= =?UTF-8?q?)+=E8=A1=A5=20Attack=20=E9=80=80=E5=87=BA=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AI/PerceptionStateMachineTests.cs | 27 +++++++++++++++++++ .../Enemies/AIBrain/EnemyBrainContext.cs | 15 +++++------ .../Enemies/Abilities/EnemyAttackSelector.cs | 2 +- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Assets/Tests/EditMode/AI/PerceptionStateMachineTests.cs b/Assets/Tests/EditMode/AI/PerceptionStateMachineTests.cs index b1a7b572..ca004669 100644 --- a/Assets/Tests/EditMode/AI/PerceptionStateMachineTests.cs +++ b/Assets/Tests/EditMode/AI/PerceptionStateMachineTests.cs @@ -357,6 +357,33 @@ namespace BaseGames.Tests.EditMode.AI Assert.AreEqual("Death", rt.CurrentStateName); } + // 进 Attack 后玩家脱离全部感知区:攻击仍在跑 → 不回 Approach,改判 leftAllZones → Rest(Patrol)。 + [Test] + public void Attack_ToRest_WhenLeftAllZones() + { + var ctx = new Ctx(); + var rt = new AiRuntime(GraphApproach(), ctx); + ctx.S.Chase = true; rt.Tick(0.1f); // → Approach + ctx.C.Eligible = true; rt.Tick(0.1f); // → Attack, Running="best" + Assert.AreEqual("Attack", rt.CurrentStateName); + ctx.S.Chase = false; ctx.S.Vision = false; // 攻击仍 running,但脱离全部感知 + rt.Tick(0.1f); + Assert.AreEqual("Patrol", rt.CurrentStateName); + } + + // 死亡是全局事件,从 Attack 态也能达 Death。 + [Test] + public void ApproachAttack_Death_FromAttack() + { + var ctx = new Ctx(); + var rt = new AiRuntime(GraphApproach(), ctx); + ctx.S.Chase = true; rt.Tick(0.1f); // → Approach + ctx.C.Eligible = true; rt.Tick(0.1f); // → Attack + Assert.AreEqual("Attack", rt.CurrentStateName); + rt.Send(AiSignal.Died); rt.Tick(0.1f); + Assert.AreEqual("Death", rt.CurrentStateName); + } + // ── 死亡(全局事件,任意态可达)─────────────────────────────────── [Test] diff --git a/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs index 24a4e7f8..30dc8e08 100644 --- a/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs +++ b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs @@ -26,15 +26,14 @@ namespace BaseGames.Enemies /// 每帧由 EnemyAiBrain 在 Tick 之前调用,维护最后已知位置与丢失计时(供需要的敌人用)。 public void Refresh(float dt) { - if (InVisionZone() && _enemy.PlayerTransform != null) - { + bool vision = InVisionZone(); + // 追逐区(aggro)或视野内都刷新最后已知位置:Approach 逼近态仅由 aggro 门控进入, + // 若只在视野时刷新,aggro 先于视野命中时 Pursue 会拿到 zero/过期点而扑向错误位置。 + if ((vision || InChaseZone()) && _enemy.PlayerTransform != null) _lastKnown = _enemy.PlayerTransform.position; - _lostTimer = 0f; - } - else - { - _lostTimer += dt; - } + // 丢失计时仍以视野为准(LostFor 语义=脱离视野的时长,不受 aggro 影响)。 + if (vision) _lostTimer = 0f; + else _lostTimer += dt; } /// 对象池复用时清空临时态。 diff --git a/Assets/_Game/Scripts/Enemies/Abilities/EnemyAttackSelector.cs b/Assets/_Game/Scripts/Enemies/Abilities/EnemyAttackSelector.cs index 310bff99..d10ee61c 100644 --- a/Assets/_Game/Scripts/Enemies/Abilities/EnemyAttackSelector.cs +++ b/Assets/_Game/Scripts/Enemies/Abilities/EnemyAttackSelector.cs @@ -53,7 +53,7 @@ namespace BaseGames.Enemies.Abilities var c = _candidates[i]; if (Eligible(c, hasLOS, grounded)) total += Mathf.Max(0f, c.Weight); } - if (total <= 0f) return SelectByPriority(hasLOS, grounded); // 权重全 0 → 退化为确定性取首个合格 + if (total <= 0f) return SelectByPriority(hasLOS, grounded); // 权重全 0 → 退化为按 Priority 选(并列取首个) float roll = Random.value * total; for (int i = 0; i < _candidates.Count; i++) {