185 lines
7.6 KiB
C#
185 lines
7.6 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using Animancer;
|
||
using BaseGames.Combat;
|
||
using BaseGames.Core.Events;
|
||
|
||
namespace BaseGames.Boss
|
||
{
|
||
/// <summary>
|
||
/// 挂在 Boss GameObject 上,接收 BossOrchestrator 的指令执行指定 BossSkillSO。
|
||
/// 管理 VulnerabilityWindow 计时和 WeakPointSystem 激活。
|
||
/// </summary>
|
||
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;
|
||
/// <remarks>PlayerController 无 Instance(架构 05 §2),由 Inspector 指定。</remarks>
|
||
[SerializeField] private Transform _playerTransform;
|
||
|
||
private BossSkillSO _currentSkill;
|
||
private bool _isExecuting;
|
||
private Coroutine _activeCoroutine;
|
||
|
||
public bool IsExecuting => _isExecuting;
|
||
|
||
/// <summary>
|
||
/// 按 float 值复用 WaitForSeconds 实例,消除协程中每次 new WaitForSeconds 的 GC 分配。
|
||
/// Domain Reload 禁用时静态缓存跨 PlayMode 会话保留,但 WaitForSeconds 是幂等值对象,
|
||
/// 不会引发功能错误;[RuntimeInitializeOnLoadMethod] 确保每次进入 Play 时清空。
|
||
/// </summary>
|
||
private static readonly Dictionary<float, WaitForSeconds> _wfsCache = new();
|
||
|
||
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
|
||
private static void ClearWFSCache() => _wfsCache.Clear();
|
||
|
||
private static WaitForSeconds GetWFS(float t)
|
||
{
|
||
if (!_wfsCache.TryGetValue(t, out var wfs))
|
||
_wfsCache[t] = wfs = new WaitForSeconds(t);
|
||
return wfs;
|
||
}
|
||
|
||
// ── 公共 API ───────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 执行一个 Boss 技能。若当前已在执行中则直接返回。
|
||
/// </summary>
|
||
public void ExecuteSkill(BossSkillSO skill)
|
||
{
|
||
if (_isExecuting || skill == null) return;
|
||
_activeCoroutine = StartCoroutine(ExecuteSkillCoroutine(skill));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 立即打断正在执行的技能(阶段切换时调用)。
|
||
/// </summary>
|
||
public void InterruptCurrentSkill()
|
||
{
|
||
if (_activeCoroutine != null)
|
||
{
|
||
StopCoroutine(_activeCoroutine);
|
||
_activeCoroutine = null;
|
||
}
|
||
FinishExecution();
|
||
}
|
||
|
||
// ── 主协程 ─────────────────────────────────────────────────────────────
|
||
|
||
private IEnumerator ExecuteSkillCoroutine(BossSkillSO skill)
|
||
{
|
||
_isExecuting = true;
|
||
_currentSkill = skill;
|
||
_onBossSkillStarted?.Raise(new BossSkillEvent { BossId = _bossId, SkillId = skill.skillId });
|
||
|
||
// 播放技能动画
|
||
if (skill.skillAnimation != null)
|
||
_animancer.Play(skill.skillAnimation);
|
||
|
||
// 启动 VulnerabilityWindow 协程(与主序列并行)
|
||
Coroutine vulnCoroutine = null;
|
||
if (skill.vulnerabilityWindows != null && skill.vulnerabilityWindows.Length > 0)
|
||
vulnCoroutine = StartCoroutine(ActivateVulnerabilityWindowsCoroutine(skill));
|
||
|
||
// 执行攻击序列(优先 sequenceOnMiss 作为默认序列)
|
||
if (skill.sequenceOnMiss != null)
|
||
yield return ExecuteSequenceCoroutine(skill.sequenceOnMiss);
|
||
|
||
// 若弱点协程还在运行则等待其结束(避免孤立协程)
|
||
if (vulnCoroutine != null)
|
||
yield return vulnCoroutine;
|
||
|
||
FinishExecution();
|
||
}
|
||
|
||
private void FinishExecution()
|
||
{
|
||
_isExecuting = false;
|
||
if (_currentSkill != null)
|
||
{
|
||
_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))
|
||
foreach (var hb in _hitBoxes)
|
||
hb.Activate(pattern.DamageSource, transform);
|
||
|
||
if (pattern.ActiveDuration > 0f)
|
||
yield return GetWFS(pattern.ActiveDuration);
|
||
|
||
// 关闭 HitBox
|
||
foreach (var hb in _hitBoxes)
|
||
hb.Deactivate();
|
||
|
||
// 后摇
|
||
if (pattern.RecoveryDuration > 0f)
|
||
yield return GetWFS(pattern.RecoveryDuration);
|
||
}
|
||
|
||
// ── VulnerabilityWindow 协程 ─────────────────────────────────────────────
|
||
|
||
private IEnumerator ActivateVulnerabilityWindowsCoroutine(BossSkillSO skill)
|
||
{
|
||
foreach (var window in skill.vulnerabilityWindows)
|
||
{
|
||
if (window.TriggerDelay > 0f)
|
||
yield return GetWFS(window.TriggerDelay);
|
||
|
||
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();
|
||
}
|
||
}
|
||
|
||
// ── 工具 ───────────────────────────────────────────────────────────────
|
||
|
||
private bool IsPlayerInRange() =>
|
||
_playerTransform != null &&
|
||
Vector2.Distance(transform.position, _playerTransform.position) < 8f;
|
||
}
|
||
}
|