feat(enemy): BossPhaseAbilityGate——阶段即换招池

按阶段启停能力组件,被禁用的招经 CanUse 自动退出选招候选,
阶段门不需要碰选招器。取代 BossSkillSO.availablePhaseIndices。
EnterPhase 先换池后广播;OnSpawn 回阶段 0(对象池复用)。
This commit is contained in:
2026-07-30 13:50:34 +08:00
parent 24c612930e
commit 50f505279d
5 changed files with 151 additions and 4 deletions
@@ -0,0 +1,61 @@
using System;
using UnityEngine;
using BaseGames.Enemies.Abilities;
namespace BaseGames.Enemies
{
/// <summary>
/// 阶段 = 换招池:按当前阶段启用 / 禁用能力组件。
/// 被禁用的能力其 <see cref="EnemyAbilityBase.CanUse"/> 为 false
/// 因此自动从 <see cref="EnemyAttackSelector"/> 的候选里消失——阶段门不需要碰选招器。
///
/// 由 <see cref="BossBase.EnterPhase"/> 与 <see cref="BossBase.OnSpawn"/> 直接调用
/// (而非订阅阶段频道),保证与阶段切换严格同序。
/// </summary>
public sealed class BossPhaseAbilityGate : MonoBehaviour
{
[Serializable]
public struct PhaseEntry
{
[Tooltip("受阶段管控的能力组件")]
public EnemyAbilityBase ability;
[Tooltip("该能力可用的阶段索引;空数组 = 全阶段可用")]
public int[] phases;
}
[Tooltip("阶段可用性表。未登记的能力不受本组件影响(保持其自身启用状态)")]
[SerializeField] private PhaseEntry[] _entries;
/// <summary>把指定阶段的启用集应用到所有登记的能力。</summary>
public void ApplyPhase(int phase)
{
if (_entries == null) return;
for (int i = 0; i < _entries.Length; i++)
{
var e = _entries[i];
if (e.ability == null) continue;
e.ability.enabled = IsAllowed(e.phases, phase);
}
}
private static bool IsAllowed(int[] phases, int phase)
{
if (phases == null || phases.Length == 0) return true; // 空 = 全阶段
for (int i = 0; i < phases.Length; i++)
if (phases[i] == phase) return true;
return false;
}
#if UNITY_EDITOR
// 漏配显式报错,不静默跳过(CLAUDE.md 第 6 条)
private void OnValidate()
{
if (_entries == null) return;
for (int i = 0; i < _entries.Length; i++)
if (_entries[i].ability == null)
Debug.LogError(
$"[BossPhaseAbilityGate] {name} 第 {i} 项未指定能力组件,该行不会生效。", this);
}
#endif
}
}