Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/BlinkStrikeAbility.cs
T
joywayer c4e80afe9e chore(enemy): 清除随寻路库失效的 NavDriving 死代码与两处过时注释
NavDriving 原由已删除的寻路适配层设置,现无人写入(全项目 C# 零引用),
连同其调试字段一并移除;场景/预制体里的序列化残值由 Unity 下次保存自动丢弃。
2026-07-28 13:28:52 +08:00

194 lines
8.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Pool;
namespace BaseGames.Enemies.Abilities
{
/// <summary>
/// 闪烁突袭能力:消失后瞬移到目标侧后方/上方/侧翼,现身后衔接出手。
///
/// 流程:
/// 1. 选择闪烁目标点(玩家侧后 / 上方 / 侧翼,依 <see cref="BlinkPosition"/>
/// 2. 隐身(隐藏 SpriteRenderer、关闭碰撞、播放消失 VFX)
/// 3. 等待 disappearDuration 营造"消失"感
/// 4. 瞬移到目标点
/// 5. 现身 + 出现 VFX + 预警闪光(reappearTelegraph
/// 6. 调用 followUpAbilityId 指定的能力作为出手(若为空则播放 attackSequence[0]
///
/// 同时实现 <see cref="INavLinkHandler"/> for <see cref="NavLinkType.Teleport"/>
/// 被要求以"传送"方式穿越一段地形时,本能力接管穿越逻辑,
/// 播放消失/出现动画后回调完成,而不触发战斗出手。
/// </summary>
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;
// ── INavLinkHandlerTeleport 连接段穿越)─────────────────────
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);
}
/// <summary>纯导航用传送:消失 → 移动到连接终点 → 出现,不触发战斗出手。</summary>
private IEnumerator TeleportNavLinkCoroutine(Vector2 destination, Action onComplete)
{
_pool ??= ServiceLocator.GetOrDefault<IObjectPoolService>();
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<Rigidbody2D>();
if (_renderers == null || _renderers.Length == 0)
_renderers = GetComponentsInChildren<SpriteRenderer>(true);
}
protected override IEnumerator ExecuteCoroutine()
{
_pool ??= ServiceLocator.GetOrDefault<IObjectPoolService>();
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;
}
}
}