fix(enemy): 禁用的能力组件不再报告可用
CanUse 纳入 enabled。此前禁用组件仍返回 true,选招器会选中一个 StartCoroutine 必然失败的招。这也是阶段门(按阶段启停能力)的前提。
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
using BaseGames.Enemies;
|
||||||
|
using BaseGames.Enemies.Abilities;
|
||||||
|
|
||||||
|
namespace BaseGames.Tests.EditMode.Enemies
|
||||||
|
{
|
||||||
|
public class BossPhaseAbilityGateTests
|
||||||
|
{
|
||||||
|
/// <summary>最小可实例化能力:不播动画、不碰 HitBox,只为验证启停与 CanUse。</summary>
|
||||||
|
private sealed class StubAbility : EnemyAbilityBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 编辑模式下 Unity 不会在 AddComponent 时调用 Awake,_enemy 会留空导致 CanUse 恒假。
|
||||||
|
/// 这里手动跑一次真实的 Awake(而非反射直写字段),让依赖解析走生产代码路径。
|
||||||
|
/// </summary>
|
||||||
|
public void RunAwake() => Awake();
|
||||||
|
|
||||||
|
protected override IEnumerator ExecuteCoroutine() { yield break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>测试期建出的宿主对象;在 TearDown 统一销毁,断言失败也不会残留到编辑器场景。</summary>
|
||||||
|
private GameObject _host;
|
||||||
|
|
||||||
|
// RunAwake 时 EnemyAbilityBase 找不到 AnimancerComponent 会告警,与本测试无关。
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => LogAssert.ignoreFailingMessages = true;
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
// 项目关闭了 Domain/Scene Reload:断言失败会跳过用例后续语句,
|
||||||
|
// 清理必须放在 TearDown,否则失败一次就往编辑器场景里漏一个对象。
|
||||||
|
if (_host != null) Object.DestroyImmediate(_host);
|
||||||
|
_host = null;
|
||||||
|
LogAssert.ignoreFailingMessages = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DisabledAbility_IsNotUsable()
|
||||||
|
{
|
||||||
|
// 必须先挂 EnemyBase:否则 _enemy==null 会让 CanUse 恒假,
|
||||||
|
// 这条断言在修复前也会"通过",成为一个永远不会失败的测试。
|
||||||
|
_host = new GameObject("ability-host");
|
||||||
|
_host.AddComponent<EnemyBase>(); // 裸 EnemyBase 的 _currentState=Controlled → IsAlive 为真
|
||||||
|
var ab = _host.AddComponent<StubAbility>();
|
||||||
|
ab.RunAwake(); // 编辑模式不自动跑 Awake,手动解析 _enemy
|
||||||
|
|
||||||
|
Assert.IsTrue(ab.CanUse,
|
||||||
|
"前提:启用且宿主存活时应可用——否则下一条断言分不清是 enabled 还是别的门在起作用");
|
||||||
|
|
||||||
|
ab.enabled = false;
|
||||||
|
Assert.IsFalse(ab.CanUse, "禁用的能力组件不可用——否则选招器会选中一个启不动招的招");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cc78469b00f228648844d2e34c13f80c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -37,11 +37,16 @@ namespace BaseGames.Enemies.Abilities
|
|||||||
public float CooldownRemaining => Mathf.Max(0f, _cooldownEndTime - Time.time);
|
public float CooldownRemaining => Mathf.Max(0f, _cooldownEndTime - Time.time);
|
||||||
public bool IsOnCooldown => CooldownRemaining > 0f;
|
public bool IsOnCooldown => CooldownRemaining > 0f;
|
||||||
|
|
||||||
/// <summary>能力被外部中断时触发(BD Task / 状态机订阅用)。</summary>
|
/// <summary>能力被外部中断时触发(AI 决策层 / 状态机订阅用)。</summary>
|
||||||
public event System.Action<InterruptReason> Interrupted;
|
public event System.Action<InterruptReason> Interrupted;
|
||||||
|
|
||||||
/// <summary>BD 任务统一查询入口:当前是否可用(冷却完毕且未执行中)。</summary>
|
/// <summary>
|
||||||
public virtual bool CanUse => !_isRunning && !IsOnCooldown && _enemy != null && _enemy.IsAlive;
|
/// 统一可用性查询:组件启用 + 冷却完毕 + 未执行中 + 宿主存活。
|
||||||
|
/// enabled 这一维供阶段门使用——被阶段禁用的能力不得进入选招候选,
|
||||||
|
/// 否则选招器会选中它然后 StartCoroutine 在禁用组件上必然失败。
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanUse => enabled && !_isRunning && !IsOnCooldown
|
||||||
|
&& _enemy != null && _enemy.IsAlive;
|
||||||
|
|
||||||
// ── IAttackCandidate(供 EnemyAttackSelector 选招)──────────────────
|
// ── IAttackCandidate(供 EnemyAttackSelector 选招)──────────────────
|
||||||
public bool RequiresLineOfSight => _config != null && _config.requiresLineOfSight;
|
public bool RequiresLineOfSight => _config != null && _config.requiresLineOfSight;
|
||||||
|
|||||||
Reference in New Issue
Block a user