using System.Collections; using System.Collections.Generic; using UnityEngine; using Animancer; using BaseGames.Combat; using BaseGames.Core.Events; namespace BaseGames.Boss { /// /// 挂在 Boss GameObject 上,接收 BossOrchestrator 的指令执行指定 BossSkillSO。 /// 管理 VulnerabilityWindow 计时和 WeakPointSystem 激活。 /// public class BossSkillExecutor : MonoBehaviour { [SerializeField] private HitBox[] _hitBoxes; [SerializeField] private WeakPointSystem _weakPointSystem; [SerializeField] private AnimancerComponent _animancer; [SerializeField] private string _bossId; [SerializeField] private BossSkillEventChannelSO _onBossSkillStarted; [SerializeField] private BossSkillEventChannelSO _onBossSkillEnded; /// PlayerController 无 Instance(架构 05 §2),由 Inspector 指定。 [SerializeField] private Transform _playerTransform; [SerializeField] private BossSkillSO[] _skills; [Header("技能重复检测范围")] [Tooltip("SkillSequence RepeatIfPlayerInRange 的检测半径(m)")] [SerializeField, Min(1f)] private float _repeatRangeCheck = 8f; private BossSkillSO _currentSkill; private bool _isExecuting; private Coroutine _activeCoroutine; private Coroutine _vulnCoroutine; // 弱点窗口协程(中断时需同步停止) private bool _patternHitConfirmed; // 本次技能执行期间是否有 HitBox 命中 // 技能冷却:skillId → 冷却结束的 Time.time 时刻 private readonly Dictionary _skillCooldownEndTimes = new(); public bool IsExecuting => _isExecuting; /// 检查指定技能是否冷却就绪(无冷却记录或已过冷却时间)。 public bool CanUseSkill(string skillId) { if (string.IsNullOrEmpty(skillId)) return false; if (_skillCooldownEndTimes.TryGetValue(skillId, out float endTime)) return Time.time >= endTime; return true; } /// 强制重置指定技能的冷却(阶段切换、复活等场景使用)。 public void ResetSkillCooldown(string skillId) { _skillCooldownEndTimes.Remove(skillId); } /// 重置所有技能冷却。 public void ResetAllCooldowns() => _skillCooldownEndTimes.Clear(); private void Awake() { #if UNITY_EDITOR ValidateSkillConfig(); #endif } #if UNITY_EDITOR private void OnValidate() => ValidateSkillConfig(); private void ValidateSkillConfig() { if (_skills == null || _skills.Length == 0) { Debug.LogError($"[BossSkillExecutor] Boss '{_bossId}' ({gameObject.name}) 未配置任何技能 SO。", this); return; } foreach (var skill in _skills) { if (skill == null) Debug.LogError($"[BossSkillExecutor] Boss '{_bossId}' ({gameObject.name}) _skills 数组含 null 元素。", this); else if (string.IsNullOrEmpty(skill.skillId)) Debug.LogError($"[BossSkillExecutor] Boss '{_bossId}' ({gameObject.name}) 技能 '{skill.name}' 缺少 skillId。", this); } } #endif /// /// 按 float 值复用 WaitForSeconds 实例,消除协程中每次 new WaitForSeconds 的 GC 分配。 /// Domain Reload 禁用时静态缓存跨 PlayMode 会话保留,但 WaitForSeconds 是幂等值对象, /// 不会引发功能错误;[RuntimeInitializeOnLoadMethod] 确保每次进入 Play 时清空。 /// private static readonly Dictionary _wfsCache = new(); [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] private static void ClearWFSCache() => _wfsCache.Clear(); private const int MaxWFSCacheSize = 64; private static WaitForSeconds GetWFS(float t) { if (!_wfsCache.TryGetValue(t, out var wfs)) { if (_wfsCache.Count < MaxWFSCacheSize) _wfsCache[t] = wfs = new WaitForSeconds(t); else return new WaitForSeconds(t); } return wfs; } // ── 公共 API ─────────────────────────────────────────────────────────── /// /// 按 skillId 查找已在 Inspector 注册的技能 SO。未找到返回 null。 /// public BossSkillSO FindSkill(string skillId) { if (_skills == null) return null; foreach (var s in _skills) if (s != null && s.skillId == skillId) return s; return null; } /// 返回当前正在执行的技能 SO,未执行时返回 null。 public BossSkillSO FindCurrentSkill() => _isExecuting ? _currentSkill : null; /// Inspector 中注册的全部技能 SO(只读)。 public BossSkillSO[] Skills => _skills; /// /// 从候选技能列表中按 weight 加权随机选择一个技能。 /// 权重为 0 的技能不参与选择;所有候选权重均为 0 时返回 null。 /// public BossSkillSO SelectWeightedSkill(System.Collections.Generic.IList candidates) { if (candidates == null || candidates.Count == 0) return null; float totalWeight = 0f; for (int i = 0; i < candidates.Count; i++) { var s = candidates[i]; if (s != null && s.weight > 0f) totalWeight += s.weight; } if (totalWeight <= 0f) return null; float roll = UnityEngine.Random.value * totalWeight; float acc = 0f; for (int i = 0; i < candidates.Count; i++) { var s = candidates[i]; if (s == null || s.weight <= 0f) continue; acc += s.weight; if (roll <= acc) return s; } // 浮点精度兜底:返回最后一个有效候选 for (int i = candidates.Count - 1; i >= 0; i--) if (candidates[i]?.weight > 0f) return candidates[i]; return null; } /// /// 执行一个 Boss 技能。若当前正在执行或技能冷却未就绪则返回。 /// public void ExecuteSkill(BossSkillSO skill) { if (_isExecuting || skill == null) return; if (!CanUseSkill(skill.skillId)) { Debug.Log($"[BossSkillExecutor] 技能 '{skill.skillId}' 冷却中,无法执行。", this); return; } // 提前订阅,确保 InterruptCurrentSkill() 中断时 FinishExecution() 能正常取消 SubscribeHitCallbacks(); _activeCoroutine = StartCoroutine(ExecuteSkillCoroutine(skill)); } /// /// 立即打断正在执行的技能(阶段切换时调用)。 /// public void InterruptCurrentSkill() { // 同步停止弱点窗口协程,防止中断后继续激活 WeakPointSystem if (_vulnCoroutine != null) { StopCoroutine(_vulnCoroutine); _vulnCoroutine = null; } if (_activeCoroutine != null) { StopCoroutine(_activeCoroutine); _activeCoroutine = null; } FinishExecution(); } // 等待事件触发的 VulnWindow(事件驱动类型,存储后由 NotifyVulnTrigger 逐个激活) private readonly List _pendingEventWindows = new(); /// /// 通知执行器某一外部事件已发生(如格挡成功、反制命中等), /// 激活所有注册该触发类型的弱点窗口。 /// 由 BossBase.HandleParrySuccess / ApplyCounterResponse 等调用。 /// public void NotifyVulnTrigger(VulnTriggerType triggerType) { for (int i = _pendingEventWindows.Count - 1; i >= 0; i--) { var w = _pendingEventWindows[i]; if (w.TriggerType == triggerType) { _pendingEventWindows.RemoveAt(i); StartCoroutine(OpenWindowCoroutine(w)); } } } private void OnHitConfirmedCallback(DamageInfo _) => _patternHitConfirmed = true; private void SubscribeHitCallbacks() { if (_hitBoxes == null) return; foreach (var hb in _hitBoxes) if (hb != null) hb.OnHitConfirmed += OnHitConfirmedCallback; } private void UnsubscribeHitCallbacks() { if (_hitBoxes == null) return; foreach (var hb in _hitBoxes) if (hb != null) hb.OnHitConfirmed -= OnHitConfirmedCallback; } private IEnumerator ExecuteSkillCoroutine(BossSkillSO skill) { _isExecuting = true; _currentSkill = skill; _patternHitConfirmed = false; // HitBox 订阅已在 ExecuteSkill() 入口完成(确保 Interrupt 中断时能在 FinishExecution 取消) _onBossSkillStarted?.Raise(new BossSkillEvent { BossId = _bossId, SkillId = skill.skillId }); // 播放技能动画 if (skill.skillAnimation != null) _animancer.Play(skill.skillAnimation); // 启动 VulnerabilityWindow 协程(与主序列并行) _vulnCoroutine = null; if (skill.vulnerabilityWindows != null && skill.vulnerabilityWindows.Length > 0) _vulnCoroutine = StartCoroutine(ActivateVulnerabilityWindowsCoroutine(skill)); // 执行主攻击序列(始终执行 sequenceOnMiss;sequenceOnHit 是命中后的追加序列) if (skill.sequenceOnMiss != null) yield return ExecuteSequenceCoroutine(skill.sequenceOnMiss); // 若本次有命中确认且配置了 sequenceOnHit,执行追加序列(连段、击倒追击等) if (_patternHitConfirmed && skill.sequenceOnHit != null) yield return ExecuteSequenceCoroutine(skill.sequenceOnHit); // 若弱点协程还在运行则等待其结束(避免孤立协程) if (_vulnCoroutine != null) yield return _vulnCoroutine; FinishExecution(); } private void FinishExecution() { UnsubscribeHitCallbacks(); // 无论正常结束还是被 Interrupt,均在此取消订阅 _pendingEventWindows.Clear(); // 清除未触发的事件驱动弱点窗口,防止跨技能积压 _vulnCoroutine = null; // 正常结束时已自然结束,仅清除引用 _isExecuting = false; if (_currentSkill != null) { // 记录冷却结束时刻 if (_currentSkill.cooldown > 0f) _skillCooldownEndTimes[_currentSkill.skillId] = Time.time + _currentSkill.cooldown; _onBossSkillEnded?.Raise(new BossSkillEvent { BossId = _bossId, SkillId = _currentSkill.skillId }); _currentSkill = null; } } // ── 序列协程 ──────────────────────────────────────────────────────────── private IEnumerator ExecuteSequenceCoroutine(SkillSequenceSO seq) { int repeatCount = 0; do { foreach (var step in seq.steps) { if (step.delayBeforeStep > 0f) yield return GetWFS(step.delayBeforeStep); if (step.pattern != null) yield return ExecutePatternCoroutine(step.pattern); } repeatCount++; if (seq.RepeatIfPlayerInRange && seq.RepeatDelay > 0f) yield return GetWFS(seq.RepeatDelay); } while (seq.RepeatIfPlayerInRange && (seq.MaxRepeatCount == 0 || repeatCount < seq.MaxRepeatCount) && IsPlayerInRange()); } private IEnumerator ExecutePatternCoroutine(AttackPatternSO pattern) { // 预备 if (pattern.WindupDuration > 0f) yield return GetWFS(pattern.WindupDuration); // 激活 HitBox(架构 06 §4:Activate(DamageSourceSO, Transform)) if (_hitBoxes != null && _hitBoxes.Length > 0) foreach (var hb in _hitBoxes) if (hb != null) hb.Activate(pattern.DamageSource, transform); if (pattern.ActiveDuration > 0f) yield return GetWFS(pattern.ActiveDuration); // 关闭 HitBox if (_hitBoxes != null && _hitBoxes.Length > 0) foreach (var hb in _hitBoxes) if (hb != null) hb.Deactivate(); // 后摇 if (pattern.RecoveryDuration > 0f) yield return GetWFS(pattern.RecoveryDuration); } // ── VulnerabilityWindow 协程 ───────────────────────────────────────────── private IEnumerator ActivateVulnerabilityWindowsCoroutine(BossSkillSO skill) { _pendingEventWindows.Clear(); foreach (var window in skill.vulnerabilityWindows) { if (window.TriggerType == VulnTriggerType.OnAttackRecovery) { // 时间驱动:按 TriggerDelay 延迟后自动激活 if (window.TriggerDelay > 0f) yield return GetWFS(window.TriggerDelay); StartCoroutine(OpenWindowCoroutine(window)); } else { // 事件驱动(OnParriedSuccess / Manual 等):注册到待触发列表 _pendingEventWindows.Add(window); } } } /// 实际开启并持续弱点窗口,支持独立并行运行。 private IEnumerator OpenWindowCoroutine(VulnerabilityWindow window) { bool activateSpecific = window.ActivateWeakPointHurtBox; _weakPointSystem?.SetActive(true, window.DamageMultiplier, activateSpecific); window.OpenFeedback?.Play(); yield return GetWFS(window.Duration); _weakPointSystem?.SetActive(false, 1f, activateSpecific); window.CloseFeedback?.Play(); } // ── 工具 ─────────────────────────────────────────────────────────────── /// /// 在指定时长内开启弱点窗口(格挡/闪避反制时调用,独立于技能 VulnerabilityWindow 序列)。 /// public void OpenVulnerabilityWindow(float duration, float damageMultiplier) { if (_weakPointSystem == null || duration <= 0f) return; StartCoroutine(VulnWindowOverride(duration, damageMultiplier)); } private IEnumerator VulnWindowOverride(float duration, float multiplier) { _weakPointSystem.SetActive(true, multiplier, false); yield return GetWFS(duration); _weakPointSystem.SetActive(false, 1f, false); } private bool IsPlayerInRange() => _playerTransform != null && Vector2.Distance(transform.position, _playerTransform.position) < _repeatRangeCheck; } }