using System.Collections; using UnityEngine; using Animancer; using BaseGames.Core; using BaseGames.Core.Pool; namespace BaseGames.Enemies.Abilities { /// /// 敌人能力抽象基类(架构 07_EnemyModule §8.4)。 /// 责任:生命周期管理、冷却计时、中断分发。子类只实现 。 /// /// 设计要点: /// - 单一执行实例:同一能力同时只有一个协程在跑。 /// - 协程内 yield WaitForSeconds 复用 (无 GC)。 /// - 受击/死亡时由 调用 。 /// [DisallowMultipleComponent] public abstract class EnemyAbilityBase : MonoBehaviour, IAttackCandidate { [Header("配置 SO")] [SerializeField] protected EnemyAbilitySO _config; // 缓存依赖(Awake 填入,热路径无 GetComponent) protected EnemyBase _enemy; protected AnimancerComponent _animancer; protected Transform _transform; private Coroutine _runner; private float _cooldownEndTime = -1f; private bool _isRunning; // ── 公共状态 ───────────────────────────────────────────────────── public EnemyAbilitySO Config => _config; public bool IsRunning => _isRunning; public AbilityRunState Phase { get; protected set; } = AbilityRunState.Idle; public float CooldownRemaining => Mathf.Max(0f, _cooldownEndTime - Time.time); public bool IsOnCooldown => CooldownRemaining > 0f; /// 能力被外部中断时触发(AI 决策层 / 状态机订阅用)。 public event System.Action Interrupted; /// /// 统一可用性查询:组件启用 + 冷却完毕 + 未执行中 + 宿主存活。 /// enabled 这一维供阶段门使用——被阶段禁用的能力不得进入选招候选, /// 否则选招器会选中它然后 StartCoroutine 在禁用组件上必然失败。 /// public virtual bool CanUse => enabled && !_isRunning && !IsOnCooldown && _enemy != null && _enemy.IsAlive; // ── IAttackCandidate(供 EnemyAttackSelector 选招)────────────────── public bool RequiresLineOfSight => _config != null && _config.requiresLineOfSight; public bool RequiresGrounded => _config != null && _config.requiresGrounded; public float Weight => _config != null ? _config.weight : 0f; public int Priority => _config != null ? _config.priority : 0; /// 本招圆形攻击范围内是否有玩家(招式自管其射程;LOS 复用敌人级 IsPlayerVisible)。 public virtual bool InAttackRange() { if (_config == null || _config.rangeRadius <= 0f || _enemy == null || _enemy.PlayerTransform == null) return false; float sign = _transform.localScale.x < 0f ? -1f : 1f; Vector2 origin = (Vector2)_transform.position + new Vector2(_config.rangeOffset.x * sign, _config.rangeOffset.y); float r = _config.rangeRadius; return ((Vector2)_enemy.PlayerTransform.position - origin).sqrMagnitude <= r * r; } protected virtual void Awake() { _enemy = GetComponentInParent(); _animancer = _enemy != null ? _enemy.Animancer : GetComponentInParent(); _transform = transform; if (_enemy == null) Debug.LogError($"[EnemyAbilityBase] {GetType().Name} 找不到 EnemyBase。", this); if (_animancer == null) Debug.LogWarning($"[EnemyAbilityBase] {GetType().Name} 找不到 AnimancerComponent,动画能力将无法播放动画。", this); } protected virtual void OnDisable() { if (_isRunning) Interrupt(InterruptReason.ExternalRequest); } // ── 执行 ───────────────────────────────────────────────────────── /// /// 启动能力。重复调用、冷却中或已运行将返回 false。 /// 若 非空,会先中断同组其他能力(互斥)。 /// public bool Execute() { if (!CanUse) return false; // 互斥组:启动前中断同组正在运行的其他能力 if (_config != null && !string.IsNullOrEmpty(_config.exclusionGroup)) _enemy?.Abilities.InterruptGroup(_config.exclusionGroup, InterruptReason.ExternalRequest); _runner = StartCoroutine(RunInternal()); return true; } /// /// 强制启动能力,忽略冷却检查(连段语义:外部组合技调用子能力时使用)。 /// 若能力正在运行则先中断再重启。 /// public bool ForceExecute() { if (_enemy == null || !_enemy.IsAlive) return false; if (_isRunning) Interrupt(InterruptReason.ExternalRequest); _runner = StartCoroutine(RunInternal()); return true; } private IEnumerator RunInternal() { _isRunning = true; Phase = AbilityRunState.Telegraph; try { if (_config != null && _config.telegraphDuration > 0f) yield return TelegraphRoutine(); Phase = AbilityRunState.Windup; yield return ExecuteCoroutine(); Phase = AbilityRunState.Recovery; } finally { _isRunning = false; _runner = null; _cooldownEndTime = Time.time + (_config != null ? _config.cooldown : 0f); if (Phase != AbilityRunState.Interrupted) Phase = AbilityRunState.Idle; OnAbilityEnded(); } } /// 子类实现:能力主体。可分多段、含 HitBox 激活/弹幕生成/物理推进等。 protected abstract IEnumerator ExecuteCoroutine(); /// 预警阶段(默认生成 VFX 后等待 telegraphDuration)。子类可重写。 protected virtual IEnumerator TelegraphRoutine() { if (!string.IsNullOrEmpty(_config.telegraphVfxKey)) { var pool = ServiceLocator.GetOrDefault(); pool?.Spawn(_config.telegraphVfxKey, _transform.position, Quaternion.identity); } yield return EnemyAbilityWaits.Get(_config.telegraphDuration); } /// 能力结束钩子(被中断或正常结束都会调用)。 protected virtual void OnAbilityEnded() { } /// 中断当前执行。冷却仍会按配置计入。 public void Interrupt(InterruptReason reason) { if (!_isRunning) return; if (_config != null) { if (reason == InterruptReason.Hurt && !_config.interruptOnHurt) return; if (reason == InterruptReason.Stagger && !_config.interruptOnStagger) return; } if (_runner != null) StopCoroutine(_runner); _runner = null; _isRunning = false; Phase = AbilityRunState.Interrupted; OnInterrupted(reason); Interrupted?.Invoke(reason); OnAbilityEnded(); _cooldownEndTime = Time.time + (_config != null ? _config.cooldown * 0.5f : 0f); } protected virtual void OnInterrupted(InterruptReason reason) { } /// 子类辅助:朝向目标(写入输入信号,下一 FixedUpdate 由 EnemyMovement 消费)。 protected void FaceTarget(Transform target) { if (target == null || _enemy == null) return; _enemy.FaceTarget(target.position); } /// 把 _config 解析为期望的子类型;类型不符/为空时显式报错(不回退)。 protected T ResolveConfig() where T : EnemyAbilitySO { if (_config is T typed) return typed; Debug.LogError($"[{GetType().Name}] _config 需为 {typeof(T).Name}," + $"实际 = {(_config != null ? _config.GetType().Name : "null")}。请重建为正确的子类 SO。", this); return null; } #if UNITY_EDITOR /// 选中时绘制攻击射程范围(编辑器可视化辅助,不影响运行时)。 private void OnDrawGizmosSelected() { if (_config == null || _config.category != AbilityCategory.Attack || _config.rangeRadius <= 0f) return; float sign = transform.localScale.x < 0f ? -1f : 1f; Vector3 origin = transform.position + new Vector3(_config.rangeOffset.x * sign, _config.rangeOffset.y, 0f); UnityEditor.Handles.color = new Color(1f, 0.4f, 0.2f, 0.9f); UnityEditor.Handles.DrawWireDisc(origin, Vector3.forward, _config.rangeRadius); UnityEditor.Handles.Label(origin, $"{_config.abilityId} range"); } #endif } /// WaitForSeconds 池(架构 §10 GC 优化)。能力协程统一通过此获取等待指令。 internal static class EnemyAbilityWaits { private const int MaxCacheSize = 64; private static readonly System.Collections.Generic.Dictionary _cache = new System.Collections.Generic.Dictionary(32); public static WaitForSeconds Get(float seconds) { if (seconds <= 0f) return null; if (!_cache.TryGetValue(seconds, out var w)) { if (_cache.Count < MaxCacheSize) { w = new WaitForSeconds(seconds); _cache[seconds] = w; } else { return new WaitForSeconds(seconds); } } return w; } } }