Files
zeling_v2/Assets/Tests/EditMode/Enemies/BossPhaseAbilityGateTests.cs
T
joywayer 50f505279d feat(enemy): BossPhaseAbilityGate——阶段即换招池
按阶段启停能力组件,被禁用的招经 CanUse 自动退出选招候选,
阶段门不需要碰选招器。取代 BossSkillSO.availablePhaseIndices。
EnterPhase 先换池后广播;OnSpawn 回阶段 0(对象池复用)。
2026-07-30 13:50:34 +08:00

126 lines
5.6 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 System.Collections;
using System.Reflection;
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, "禁用的能力组件不可用——否则选招器会选中一个启不动招的招");
}
// 阶段门的 _entries 是私有序列化字段——与放置脚手架同样用反射写入。
// 反射只用来喂入这份配置数据,ApplyPhase 的判定逻辑走的仍是生产代码。
private static void SetEntries(BossPhaseAbilityGate gate,
params (EnemyAbilityBase ab, int[] phases)[] items)
{
var arr = new BossPhaseAbilityGate.PhaseEntry[items.Length];
for (int i = 0; i < items.Length; i++)
arr[i] = new BossPhaseAbilityGate.PhaseEntry { ability = items[i].ab, phases = items[i].phases };
typeof(BossPhaseAbilityGate)
.GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(gate, arr);
}
[Test]
public void ApplyPhase_EnablesOnlyAbilitiesAllowedInThatPhase()
{
_host = new GameObject("boss");
var gate = _host.AddComponent<BossPhaseAbilityGate>();
var p0 = _host.AddComponent<StubAbility>();
// EnemyAbilityBase 是 [DisallowMultipleComponent],第二个能力必须另起子节点
// (挂在 _host 下,TearDown 会连带销毁)
var p1Go = new GameObject("p1"); p1Go.transform.SetParent(_host.transform);
var p1 = p1Go.AddComponent<StubAbility>();
SetEntries(gate, (p0, new[] { 0 }), (p1, new[] { 1 }));
gate.ApplyPhase(0);
Assert.IsTrue (p0.enabled, "阶段 0 的招应在阶段 0 启用");
Assert.IsFalse(p1.enabled, "阶段 1 的招不应在阶段 0 启用");
gate.ApplyPhase(1);
Assert.IsFalse(p0.enabled, "换阶段后旧池的招应被禁用");
Assert.IsTrue (p1.enabled, "换阶段后新池的招应被启用");
}
[Test]
public void ApplyPhase_EmptyPhaseArrayMeansAllPhases()
{
_host = new GameObject("boss");
var gate = _host.AddComponent<BossPhaseAbilityGate>();
var any = _host.AddComponent<StubAbility>();
SetEntries(gate, (any, new int[0]));
gate.ApplyPhase(0);
Assert.IsTrue(any.enabled, "空阶段数组 = 全阶段可用");
gate.ApplyPhase(7);
Assert.IsTrue(any.enabled, "空阶段数组在任意阶段都应保持启用");
}
[Test]
public void ApplyPhase_UnlistedAbilityIsUntouched()
{
_host = new GameObject("boss");
var gate = _host.AddComponent<BossPhaseAbilityGate>();
var listed = _host.AddComponent<StubAbility>();
var otherGo = new GameObject("unlisted"); otherGo.transform.SetParent(_host.transform);
var unlisted = otherGo.AddComponent<StubAbility>();
SetEntries(gate, (listed, new[] { 1 }));
gate.ApplyPhase(0);
Assert.IsFalse(listed.enabled, "登记的能力应被阶段门管控");
Assert.IsTrue (unlisted.enabled, "未登记的能力不受阶段门影响");
}
}
}