using System; using System.Collections; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Pool; namespace BaseGames.Enemies.Abilities { /// /// 闪烁突袭能力:消失后瞬移到目标侧后方/上方/侧翼,现身后衔接出手。 /// /// 流程: /// 1. 选择闪烁目标点(玩家侧后 / 上方 / 侧翼,依 ) /// 2. 隐身(隐藏 SpriteRenderer、关闭碰撞、播放消失 VFX) /// 3. 等待 disappearDuration 营造"消失"感 /// 4. 瞬移到目标点 /// 5. 现身 + 出现 VFX + 预警闪光(reappearTelegraph) /// 6. 调用 followUpAbilityId 指定的能力作为出手(若为空则播放 attackSequence[0]) /// /// 同时实现 for : /// 当 PathBerserker2d 路径包含 teleport NavLink 时,本能力接管穿越逻辑, /// 播放消失/出现动画后告知 NavAgent 继续路径,而不触发战斗出手。 /// public sealed class BlinkStrikeAbility : EnemyAbilityBase, INavLinkHandler { public enum BlinkPosition { BehindTarget, FrontTarget, AboveTarget, FlankTarget } [Header("闪烁参数")] [SerializeField] private BlinkPosition _blinkPosition = BlinkPosition.BehindTarget; [SerializeField] private float _offsetDistance = 1.8f; [SerializeField] private float _verticalOffsetAbove = 3.0f; [SerializeField] private float _disappearDuration = 0.25f; [SerializeField] private float _reappearTelegraph = 0.20f; [SerializeField] private LayerMask _groundMask; [SerializeField] private float _groundSnapMaxDistance = 3f; [Header("视觉")] [SerializeField] private SpriteRenderer[] _renderers; [SerializeField] private Collider2D[] _disableDuringBlink; [SerializeField] private string _disappearVfxKey = ""; [SerializeField] private string _appearVfxKey = ""; [Header("出手能力(在现身后触发,若为空则播放第一段攻击动画)")] [SerializeField] private string _followUpAbilityId = ""; private Rigidbody2D _rb; private IObjectPoolService _pool; private Coroutine _navLinkCoroutine; // ── INavLinkHandler(Teleport 连接段穿越)───────────────────── private static readonly NavLinkType[] _handledTypes = new[] { NavLinkType.Teleport }; public NavLinkType[] HandledLinkTypes => _handledTypes; public bool CanHandleLink(NavLinkType type, Vector2 linkStart, Vector2 linkEnd) => true; public void BeginLinkTraversal(NavLinkType type, Vector2 linkStart, Vector2 linkEnd, Action onComplete) { if (_navLinkCoroutine != null) StopCoroutine(_navLinkCoroutine); _navLinkCoroutine = StartCoroutine(TeleportNavLinkCoroutine(linkEnd, onComplete)); } public void AbortLinkTraversal() { if (_navLinkCoroutine != null) { StopCoroutine(_navLinkCoroutine); _navLinkCoroutine = null; } SetVisible(true); } /// 纯导航用传送:消失 → 移动到连接终点 → 出现,不触发战斗出手。 private IEnumerator TeleportNavLinkCoroutine(Vector2 destination, Action onComplete) { _pool ??= ServiceLocator.GetOrDefault(); if (!string.IsNullOrEmpty(_disappearVfxKey)) _pool?.Spawn(_disappearVfxKey, _transform.position, Quaternion.identity); SetVisible(false); if (_rb != null) _rb.velocity = Vector2.zero; yield return EnemyAbilityWaits.Get(_disappearDuration); if (_rb != null) _rb.position = destination; else _transform.position = destination; SetVisible(true); if (!string.IsNullOrEmpty(_appearVfxKey)) _pool?.Spawn(_appearVfxKey, _transform.position, Quaternion.identity); _navLinkCoroutine = null; onComplete?.Invoke(); // 通知 EnemyNavAgent → CompleteLinkTraversal } protected override void Awake() { base.Awake(); _rb = GetComponentInParent(); if (_renderers == null || _renderers.Length == 0) _renderers = GetComponentsInChildren(true); } protected override IEnumerator ExecuteCoroutine() { _pool ??= ServiceLocator.GetOrDefault(); var target = _enemy != null ? _enemy.PlayerTransform : null; if (target == null) yield break; // 1. 消失 if (!string.IsNullOrEmpty(_disappearVfxKey)) _pool?.Spawn(_disappearVfxKey, _transform.position, Quaternion.identity); SetVisible(false); if (_rb != null) _rb.velocity = Vector2.zero; yield return EnemyAbilityWaits.Get(_disappearDuration); // 2. 选目标点 + 瞬移 Vector2 dest = ComputeBlinkPosition(target); if (_rb != null) _rb.position = dest; else _transform.position = dest; FaceTarget(target); // 3. 现身 + 预警 SetVisible(true); if (!string.IsNullOrEmpty(_appearVfxKey)) _pool?.Spawn(_appearVfxKey, _transform.position, Quaternion.identity); Phase = AbilityRunState.Telegraph; yield return EnemyAbilityWaits.Get(_reappearTelegraph); // 4. 出手 Phase = AbilityRunState.Active; if (!string.IsNullOrEmpty(_followUpAbilityId) && _enemy != null) { var follow = _enemy.Abilities.Get(_followUpAbilityId); if (follow != null) { follow.Execute(); while (follow.IsRunning) yield return null; yield break; } } // 退化路径:播放第一段动画 var seq = _config != null ? _config.attackSequence : null; if (seq != null && seq.Length > 0) { var atk = seq[0]; float dur = atk.fallbackDuration; if (atk.clip != null && _animancer != null) { var st = _animancer.Play(atk.clip); if (st != null && st.Length > 0f) dur = st.Length; } yield return EnemyAbilityWaits.Get(dur); } } protected override void OnInterrupted(InterruptReason reason) { // 中断时同时终止 NavLink 穿越协程(若正在进行),防止 onComplete 回调污染导航状态 AbortLinkTraversal(); } private Vector2 ComputeBlinkPosition(Transform target) { Vector2 t = target.position; Vector2 selfDir = (t - (Vector2)_transform.position); float facing = selfDir.x >= 0f ? 1f : -1f; Vector2 raw; switch (_blinkPosition) { case BlinkPosition.FrontTarget: raw = t + new Vector2(facing * _offsetDistance, 0f); break; case BlinkPosition.AboveTarget: raw = t + new Vector2(0f, _verticalOffsetAbove); break; case BlinkPosition.FlankTarget: raw = t + new Vector2((UnityEngine.Random.value < 0.5f ? -1f : 1f) * _offsetDistance, 0f); break; case BlinkPosition.BehindTarget: default: float tFacing = target.localScale.x >= 0f ? 1f : -1f; raw = t - new Vector2(tFacing * _offsetDistance, 0f); break; } // 贴地(避免悬空) var hit = Physics2D.Raycast(raw + Vector2.up * 0.5f, Vector2.down, _groundSnapMaxDistance + 0.5f, _groundMask); if (hit.collider != null) raw.y = hit.point.y + 0.05f; return raw; } private void SetVisible(bool visible) { if (_renderers != null) for (int i = 0; i < _renderers.Length; i++) if (_renderers[i] != null) _renderers[i].enabled = visible; if (_disableDuringBlink != null) for (int i = 0; i < _disableDuringBlink.Length; i++) if (_disableDuringBlink[i] != null) _disableDuringBlink[i].enabled = visible; } } }