using System.Collections; using System.Collections.Generic; using UnityEngine; using BaseGames.Combat; namespace BaseGames.Enemies.Abilities { /// /// 近战连击能力。 /// 按 EnemyAbilitySO.attackSequence 顺序播放动画段,每段在 hitBoxEnterT~hitBoxExitT /// 之间激活对应槽位的 HitBox。 /// /// 设计:HitBox 通过命名槽位绑定,避免对 EnemyCombat 的硬依赖;同一敌人多个近战 /// 能力可共享同一 HitBox 槽位(如不同段使用同一把武器)。 /// public sealed class MeleeAttackAbility : EnemyAbilityBase { [System.Serializable] public struct HitBoxSlot { public string slotName; public HitBox hitBox; } [Header("HitBox 槽位(按名字索引)")] [SerializeField] private HitBoxSlot[] _hitBoxSlots; [Header("行为")] [SerializeField] private bool _faceTargetOnStart = true; private Dictionary _slotMap; protected override void Awake() { base.Awake(); _slotMap = new Dictionary(_hitBoxSlots?.Length ?? 0); if (_hitBoxSlots != null) { for (int i = 0; i < _hitBoxSlots.Length; i++) { var s = _hitBoxSlots[i]; if (s.hitBox != null && !string.IsNullOrEmpty(s.slotName)) _slotMap[s.slotName] = s.hitBox; } } } protected override IEnumerator ExecuteCoroutine() { var seq = _config != null ? _config.attackSequence : null; if (seq == null || seq.Length == 0) yield break; if (_faceTargetOnStart && _enemy != null && _enemy.PlayerTransform != null) FaceTarget(_enemy.PlayerTransform); for (int i = 0; i < seq.Length; i++) { var atk = seq[i]; if (atk == null) continue; yield return PlayAttackStep(atk); if (atk.postDelay > 0f) yield return EnemyAbilityWaits.Get(atk.postDelay); } } private IEnumerator PlayAttackStep(EnemyAttackSO atk) { Phase = AbilityRunState.Active; float duration = atk.fallbackDuration; if (atk.clip != null && _animancer != null) { var state = _animancer.Play(atk.clip); if (state != null && state.Length > 0f) duration = state.Length; } HitBox hb = null; if (!string.IsNullOrEmpty(atk.hitBoxSlot)) _slotMap.TryGetValue(atk.hitBoxSlot, out hb); float enterAbs = atk.hitBoxEnterT * duration; float exitAbs = atk.hitBoxExitT * duration; float t = 0f; bool active = false; while (t < duration) { t += Time.deltaTime; if (hb != null) { if (!active && t >= enterAbs) { hb.Activate(atk.damageSource, _transform); active = true; } else if (active && t >= exitAbs) { hb.Deactivate(); active = false; } } yield return null; } if (active && hb != null) hb.Deactivate(); } protected override void OnInterrupted(InterruptReason reason) { // 确保 HitBox 被关闭,防止中断时残留激活 if (_hitBoxSlots == null) return; for (int i = 0; i < _hitBoxSlots.Length; i++) { var hb = _hitBoxSlots[i].hitBox; if (hb != null && hb.IsActive) hb.Deactivate(); } } } }