diff --git a/Assets/_Game/Scripts/Enemies/Boss/BossBase.cs b/Assets/_Game/Scripts/Enemies/Boss/BossBase.cs
index 5905b3ad..c2e4340e 100644
--- a/Assets/_Game/Scripts/Enemies/Boss/BossBase.cs
+++ b/Assets/_Game/Scripts/Enemies/Boss/BossBase.cs
@@ -1,16 +1,16 @@
using System.Collections;
-using System.Collections.Generic;
using UnityEngine;
using BaseGames.Boss;
using BaseGames.Combat;
using BaseGames.Core.Events;
-using BaseGames.Parry;
+using BaseGames.Enemies.Abilities;
namespace BaseGames.Enemies
{
///
- /// Boss 敌人基类。扩展 以支持多阶段切换、技能执行与战斗结束广播。
- /// 具体 Boss 继承此类并重写 。
+ /// Boss 敌人基类。扩展 以支持多阶段切换与战斗结束广播。
+ /// 招式的选取与执行走小怪同轨( + 能力组件),
+ /// 本类不再持有独立的技能执行器。具体 Boss 继承此类并重写 。
///
public class BossBase : EnemyBase, BaseGames.AI.IBossControl
{
@@ -19,9 +19,6 @@ namespace BaseGames.Enemies
[SerializeField] private BoolEventChannelSO _onBossFightEnded;
[SerializeField] private BossPhaseEventChannelSO _onBossPhaseChanged;
- [Header("技能执行器")]
- [SerializeField] private BossSkillExecutor _skillExecutor;
-
[Header("资源组件(可选)")]
[SerializeField] private BossResource _bossResource;
@@ -33,15 +30,8 @@ namespace BaseGames.Enemies
[Tooltip("定点走位用的锚点集合;AI 图未用到锚点片段时可留空")]
[SerializeField] private BossArenaAnchors _arenaAnchors;
- [Header("玩家反制事件(可选)")]
- [Tooltip("订阅此频道以响应玩家弹反成功事件")]
- [SerializeField] private ParryInfoEventChannelSO _onParrySuccess;
-
public string BossId => _bossId;
- /// 当前是否有 Boss 技能正在执行。
- public bool IsBossSkillExecuting => _skillExecutor != null && _skillExecutor.IsExecuting;
-
protected int _currentPhase = 0;
/// 当前 Boss 阶段索引。IBossControl 实现。
public int CurrentPhase => _currentPhase;
@@ -67,20 +57,10 @@ namespace BaseGames.Enemies
$"[BossBase] '{name}' 的 AI 图使用了竞技场锚点,但未挂 BossArenaAnchors 组件。" +
"请挂上该组件并配好锚点,或从 AI 图里移除锚点片段。");
- private Coroutine _counterStaggerCoroutine;
-
- // 缓存加权候选与其有效权重(两者等长、下标对应),避免 UseBossSkillWeighted() 每次 new List → GC 分配
- private readonly List _weightedCandidates = new(8);
- private readonly List _candidateWeights = new(8);
-
- // 单元素缓冲数组,供 ApplyCounterResponse 缓存当前技能,避免 new[] 分配
- private readonly BossSkillSO[] _singleSkillBuf = new BossSkillSO[1];
-
protected override void Awake()
{
base.Awake();
- // includeInactive:true 确保禁用状态的子组件也能被发现(如分阶段按需启用的执行器)
- if (_skillExecutor == null) _skillExecutor = GetComponentInChildren(true);
+ // includeInactive:true 确保禁用状态的子组件也能被发现(如阶段门按阶段停用的组件)
if (_bossResource == null) _bossResource = GetComponentInChildren(true);
if (_phaseGate == null) _phaseGate = GetComponentInChildren(true);
if (_arenaAnchors == null) _arenaAnchors = GetComponentInChildren(true);
@@ -96,110 +76,11 @@ namespace BaseGames.Enemies
_phaseGate?.ApplyPhase(_currentPhase);
}
- protected override void OnEnable()
- {
- base.OnEnable();
- _onParrySuccess?.Subscribe(HandleParrySuccess).AddTo(_subs);
- }
-
///
/// 阶段过渡期间完全无敌( 的 IsInvincible 检查由此路由)。
///
public override bool IsInvincible => IsPhaseTransitioning || base.IsInvincible;
- ///
- /// 上一次成功执行的技能 ID。 对其施加权重惩罚,防止相同技能连续重复。
- ///
- public string LastUsedSkillId { get; private set; }
-
- // ── 技能执行(决策层调用入口)───────────────────────────────────────
-
- ///
- /// 通过技能 ID 执行 Boss 技能。
- /// 若技能未找到、执行器忙或冷却中则返回 false,否则返回 true。
- ///
- public bool UseBossSkill(string skillId)
- {
- if (_skillExecutor == null || string.IsNullOrEmpty(skillId)) return false;
- if (IsPhaseTransitioning) return false;
- var skill = _skillExecutor.FindSkill(skillId);
- if (skill == null)
- {
- Debug.LogWarning($"[BossBase] 未找到技能 '{skillId}'(Boss: {_bossId})", this);
- return false;
- }
- if (!_skillExecutor.CanUseSkill(skillId))
- return false;
- if (!CheckResourceCost(skill))
- return false;
-
- _skillExecutor.ExecuteSkill(skill);
- _bossResource?.OnBossUseSkill();
- return true;
- }
-
- ///
- /// 在当前阶段可用且冷却就绪的技能中,按 加权随机选择一个并执行。
- /// 若上一次已使用某技能,则对该技能施加 0.3× 权重惩罚,降低连续重复的概率。
- /// 若无可用技能或执行器忙则返回 false。
- ///
- public bool UseBossSkillWeighted()
- {
- if (_skillExecutor == null || _skillExecutor.IsExecuting) return false;
- if (IsPhaseTransitioning) return false;
-
- var skills = _skillExecutor.Skills;
- if (skills == null || skills.Length == 0) return false;
-
- // 筛选:在当前阶段可用 + 冷却就绪 + weight > 0
- _weightedCandidates.Clear();
- _candidateWeights.Clear();
- foreach (var s in skills)
- {
- if (s == null || s.weight <= 0f) continue;
- if (!_skillExecutor.CanUseSkill(s.skillId)) continue;
- if (!IsSkillAvailableInPhase(s)) continue;
-
- _weightedCandidates.Add(s);
- // 防重复:上一个技能权重打折
- _candidateWeights.Add(s.skillId == LastUsedSkillId ? s.weight * 0.3f : s.weight);
- }
-
- // 加权随机抽取(共享原语:无正权重/无候选时返回 -1)
- int idx = BaseGames.Core.WeightedPick.Index(_candidateWeights);
- if (idx < 0) return false;
- BossSkillSO selected = _weightedCandidates[idx];
-
- if (!CheckResourceCost(selected)) return false;
-
- _skillExecutor.ExecuteSkill(selected);
- LastUsedSkillId = selected.skillId;
- _bossResource?.OnBossUseSkill();
- return true;
- }
-
- /// 检查技能的 availablePhaseIndices 是否包含当前阶段(空数组 = 全阶段可用)。
- private bool IsSkillAvailableInPhase(BossSkillSO skill)
- {
- if (skill.availablePhaseIndices == null || skill.availablePhaseIndices.Length == 0)
- return true;
- foreach (int p in skill.availablePhaseIndices)
- if (p == _currentPhase) return true;
- return false;
- }
-
- ///
- /// 检查 Boss 资源是否满足技能的 minRequired 门槛。
- /// 未配置资源组件或 minRequired <= 0 时视为通过。
- ///
- private bool CheckResourceCost(BossSkillSO skill)
- {
- if (_bossResource == null) return true;
- float min = skill.resourceCost.minRequired;
- if (min <= 0f) return true;
- return _bossResource.CurrentValue >= min;
- }
-
// ── 阶段 ──────────────────────────────────────────────────────────────
/// 当前是否处于阶段过渡(无敌帧 + 过渡演出)期间。
@@ -208,16 +89,15 @@ namespace BaseGames.Enemies
private Coroutine _phaseTransitionCoroutine;
///
- /// 进入指定阶段。自动打断当前执行中的技能,广播 供 UI / 音乐系统响应。
+ /// 进入指定阶段。自动打断在跑的招,广播 供 UI / 音乐系统响应。
/// 子类可重写以添加额外过渡逻辑(动画、无敌帧等)。
///
public virtual void EnterPhase(int phase)
{
- // 阶段切换必须先打断正在执行的技能,确保原子性
- _skillExecutor?.InterruptCurrentSkill();
+ // 阶段切换必须先打断在跑的招,确保原子性
+ Abilities.InterruptAll(InterruptReason.ExternalRequest);
_currentPhase = phase;
- LastUsedSkillId = null; // 新阶段重置权重惩罚,防止跨阶段漂移
// 新阶段换招池,上一阶段的防重复记忆不应跨阶段影响选招
AttackSelector?.ResetRepeatMemory();
// 阶段 = 换招池:先换池,再广播阶段事件,保证订阅方看到的是新池
@@ -254,8 +134,8 @@ namespace BaseGames.Enemies
IsPhaseTransitioning = true;
OnBeginPhaseTransition(targetPhase);
- // 打断技能 + 停止移动
- _skillExecutor?.InterruptCurrentSkill();
+ // 打断在跑的招 + 停止移动
+ Abilities.InterruptAll(InterruptReason.ExternalRequest);
StopMovement();
// 无敌帧期间接受的伤害由 IsInvincible 属性屏蔽(子类重写 IsInvincible 或在此处理)
@@ -310,97 +190,11 @@ namespace BaseGames.Enemies
_onBossFightEnded?.Raise(true);
}
- // ── 玩家反制响应 ──────────────────────────────────────────────────────
-
- private void HandleParrySuccess(ParryInfo info)
- {
- if (!IsAlive) return;
- var counterType = info.IsPerfect ? CounterType.PerfectParry : CounterType.Parry;
- ApplyCounterResponse(counterType, string.Empty);
- }
-
- ///
- /// 根据 counterType 查找当前技能(或所有技能)的 PlayerCounterResponse 并应用效果。
- /// 可由外部系统(闪避穿越、弱点命中等)直接调用。
- ///
- public void ApplyCounterResponse(CounterType counterType, string requiredSkillId)
- {
- if (_skillExecutor == null) return;
-
- // 优先检查当前正在执行的技能的反制规则
- BossSkillSO activeSkill = _skillExecutor.IsExecuting
- ? _skillExecutor.FindCurrentSkill()
- : null;
-
- BossSkillSO[] candidates;
- if (activeSkill != null)
- {
- _singleSkillBuf[0] = activeSkill;
- candidates = _singleSkillBuf;
- }
- else
- {
- candidates = _skillExecutor.Skills;
- }
-
- if (candidates == null) return;
-
- foreach (var skill in candidates)
- {
- if (skill?.counterResponses == null) continue;
- foreach (var resp in skill.counterResponses)
- {
- if (resp.counterType != counterType) continue;
- if (!string.IsNullOrEmpty(resp.requiredSkillId) &&
- !string.IsNullOrEmpty(requiredSkillId) &&
- resp.requiredSkillId != requiredSkillId)
- continue;
-
- ExecuteCounterEffect(resp);
- return; // 每次反制只触发第一条匹配规则
- }
- }
- }
-
- private void ExecuteCounterEffect(in PlayerCounterResponse resp)
- {
- if (resp.interruptSkill)
- _skillExecutor?.InterruptCurrentSkill();
-
- if (resp.bossStaggerDuration > 0f)
- {
- if (_counterStaggerCoroutine != null)
- StopCoroutine(_counterStaggerCoroutine);
- _counterStaggerCoroutine = StartCoroutine(CounterStaggerCoroutine(resp.bossStaggerDuration));
- }
-
- if (resp.openVulnWindow)
- {
- float duration = Mathf.Max(resp.bossStaggerDuration, 1f);
- float multiplier = 1f + resp.bossDamageBonus;
- _skillExecutor?.OpenVulnerabilityWindow(duration, multiplier);
- }
-
- resp.counterFeedback?.Play();
- }
-
- private IEnumerator CounterStaggerCoroutine(float duration)
- {
- ForceState(EnemyStateType.Stagger);
- // 时长固定且较短,直接 new WFY 即可;若需优化可接入 WFS 缓存
- yield return new WaitForSeconds(duration);
- if (IsAlive && CurrentState == EnemyStateType.Stagger)
- ForceState(EnemyStateType.Controlled);
- _counterStaggerCoroutine = null;
- }
-
public override void OnSpawn()
{
base.OnSpawn();
- LastUsedSkillId = null;
- _currentPhase = 0;
+ _currentPhase = 0;
_phaseGate?.ApplyPhase(0); // 对象池复用:回到阶段 0 的启用集
- _skillExecutor?.ResetAllCooldowns();
}
}
}
diff --git a/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs b/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs
index 89f52dfd..f1478caa 100644
--- a/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs
+++ b/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs
@@ -11,7 +11,7 @@ namespace BaseGames.Enemies
/// - AI 决策状态 / EnemyStateType
/// - 当前激活的能力名称及运行状态
/// - LOS 状态 / 到玩家距离
- /// - Boss:技能执行状态 + 各技能冷却倒计时
+ /// - Boss:当前阶段与阶段过渡状态
///
[AddComponentMenu("BaseGames/Debug/Enemy Debug Overlay")]
public sealed class EnemyDebugOverlay : MonoBehaviour
@@ -110,7 +110,9 @@ namespace BaseGames.Enemies
// Boss 信息
if (_boss != null)
{
- list.Add($"BossSkillExec: {_boss.IsBossSkillExecuting}");
+ // 上面第 2 行已用 "Phase:" 表示 AI 决策状态名,这里用 "BossPhase:" 区分,避免同名两行
+ list.Add($"BossPhase: {_boss.CurrentPhase}" +
+ (_boss.IsPhaseTransitioning ? " (transitioning)" : ""));
}
// 状态效果