using System.Collections; using UnityEngine; using BaseGames.Boss; using BaseGames.Combat; using BaseGames.Core.Events; using BaseGames.Enemies.Abilities; namespace BaseGames.Enemies { /// /// Boss 敌人基类。扩展 以支持多阶段切换与战斗结束广播。 /// 招式的选取与执行走小怪同轨( + 能力组件), /// 本类不再持有独立的技能执行器。具体 Boss 继承此类并重写 。 /// public class BossBase : EnemyBase, BaseGames.AI.IBossControl { [Header("Boss 配置")] [SerializeField] private string _bossId; [SerializeField] private BoolEventChannelSO _onBossFightEnded; [SerializeField] private BossPhaseEventChannelSO _onBossPhaseChanged; [Header("资源组件(可选)")] [SerializeField] private BossResource _bossResource; [Header("阶段招池门(可选)")] [Tooltip("按阶段启用 / 禁用能力组件;未挂载则所有能力全阶段可用")] [SerializeField] private BossPhaseAbilityGate _phaseGate; [Header("竞技场锚点(可选)")] [Tooltip("定点走位用的锚点集合;AI 图未用到锚点片段时可留空")] [SerializeField] private BossArenaAnchors _arenaAnchors; public string BossId => _bossId; protected int _currentPhase = 0; /// 当前 Boss 阶段索引。IBossControl 实现。 public int CurrentPhase => _currentPhase; /// Boss 资源是否已满。IBossControl 实现。 /// 未挂 BossResource 即抛——AI 图问了资源却没配资源组件是配置错误,必须暴露。 public bool ResourceFull => _bossResource != null ? _bossResource.IsFull : throw new System.InvalidOperationException( $"[BossBase] '{name}' 的 AI 图查询了资源满值,但未挂 BossResource 组件。" + "请挂上该组件,或从 AI 图里移除资源相关的边。"); /// 第 index 个竞技场锚点坐标。IBossControl 实现。未挂锚点组件即抛—— /// AI 图用了锚点片段却没配锚点是配置错误,必须暴露而非回退到自身位置。 public Vector2 AnchorAt(int index) => RequireAnchors().At(index); /// 当前位置到第 index 个锚点的距离。IBossControl 实现。 public float DistanceToAnchor(int index) => Vector2.Distance(transform.position, RequireAnchors().At(index)); private BossArenaAnchors RequireAnchors() => _arenaAnchors != null ? _arenaAnchors : throw new System.InvalidOperationException( $"[BossBase] '{name}' 的 AI 图使用了竞技场锚点,但未挂 BossArenaAnchors 组件。" + "请挂上该组件并配好锚点,或从 AI 图里移除锚点片段。"); protected override void Awake() { base.Awake(); // includeInactive:true 确保禁用状态的子组件也能被发现(如阶段门按阶段停用的组件) if (_bossResource == null) _bossResource = GetComponentInChildren(true); if (_phaseGate == null) _phaseGate = GetComponentInChildren(true); if (_arenaAnchors == null) _arenaAnchors = GetComponentInChildren(true); } // 初始阶段的招池必须在第一次选招前就位。 // ApplyPhase 此前只在 EnterPhase(阶段切换)与 OnSpawn(对象池复用)里调用, // 而直接摆在场景里的 Boss 两条路径都不走——开局时后续阶段的招式仍是 enabled, // 会混进阶段 0 的候选池。放在 Start:此时各能力组件的 Awake 均已执行完毕。 protected override void Start() { base.Start(); _phaseGate?.ApplyPhase(_currentPhase); } /// /// 阶段过渡期间完全无敌( 的 IsInvincible 检查由此路由)。 /// public override bool IsInvincible => IsPhaseTransitioning || base.IsInvincible; // ── 阶段 ────────────────────────────────────────────────────────────── /// 当前是否处于阶段过渡(无敌帧 + 过渡演出)期间。 public bool IsPhaseTransitioning { get; private set; } private Coroutine _phaseTransitionCoroutine; /// /// 进入指定阶段。自动打断在跑的招,广播 供 UI / 音乐系统响应。 /// 子类可重写以添加额外过渡逻辑(动画、无敌帧等)。 /// public virtual void EnterPhase(int phase) { // 阶段切换必须先打断在跑的招,确保原子性 Abilities.InterruptAll(InterruptReason.ExternalRequest); _currentPhase = phase; // 新阶段换招池,上一阶段的防重复记忆不应跨阶段影响选招 AttackSelector?.ResetRepeatMemory(); // 阶段 = 换招池:先换池,再广播阶段事件,保证订阅方看到的是新池 _phaseGate?.ApplyPhase(phase); _onBossPhaseChanged?.Raise(new BossPhaseEvent { BossId = _bossId, Phase = phase, }); } /// /// 启动阶段过渡演出:无敌帧 + 可选定格时间,结束后自动调用 。 /// 决策层检查 来等待过渡完成。 /// /// 过渡目标阶段索引。 /// 无敌帧持续时间(秒)。 public void BeginPhaseTransition(int targetPhase, float invincibleDuration) { if (IsPhaseTransitioning) { Debug.LogWarning( $"[BossBase] '{_bossId}' 已在阶段过渡中(当前阶段 {_currentPhase})," + $"忽略跳转至阶段 {targetPhase} 的请求。请检查决策层逻辑是否重复触发阶段切换。", this); return; } if (_phaseTransitionCoroutine != null) StopCoroutine(_phaseTransitionCoroutine); _phaseTransitionCoroutine = StartCoroutine(PhaseTransitionCoroutine(targetPhase, invincibleDuration)); } private IEnumerator PhaseTransitionCoroutine(int targetPhase, float duration) { IsPhaseTransitioning = true; OnBeginPhaseTransition(targetPhase); // 打断在跑的招 + 停止移动 Abilities.InterruptAll(InterruptReason.ExternalRequest); StopMovement(); // 无敌帧期间接受的伤害由 IsInvincible 属性屏蔽(子类重写 IsInvincible 或在此处理) float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; yield return null; } EnterPhase(targetPhase); IsPhaseTransitioning = false; _phaseTransitionCoroutine = null; } /// 立即终止阶段过渡协程并清除标志位。 /// 死亡时调用,防止 IsPhaseTransitioning 永久为 true 影响对象池复用。 /// private void AbortPhaseTransition() { if (_phaseTransitionCoroutine != null) { StopCoroutine(_phaseTransitionCoroutine); _phaseTransitionCoroutine = null; } IsPhaseTransitioning = false; } /// /// 阶段过渡开始时回调(子类可重写以触发演出动画或特殊逻辑)。 /// 在无敌帧等待之前调用。 /// protected virtual void OnBeginPhaseTransition(int targetPhase) { } /// 检查当前 HP 是否低于指定百分比(0~1)。 public bool IsHPBelow(float ratio) { if (_stats == null || _stats.MaxHP <= 0) return false; return (float)_stats.CurrentHP / _stats.MaxHP < ratio; } protected override void OnDamageTaken(DamageInfo info) { _bossResource?.OnBossTakeDamage(); } protected override void Die() { // 死亡时立即中止阶段过渡,防止 IsPhaseTransitioning 标志永久锁死(影响对象池复用) AbortPhaseTransition(); base.Die(); _onBossFightEnded?.Raise(true); } public override void OnSpawn() { base.OnSpawn(); _currentPhase = 0; _phaseGate?.ApplyPhase(0); // 对象池复用:回到阶段 0 的启用集 } } }