feat: Implement Room Streaming System
- Add RoomStreamingManager to manage room loading and unloading based on player proximity. - Create StreamingBudgetConfigSO for memory and performance budgeting of the streaming system. - Introduce TransitionDirector to handle seamless and atmospheric fade transitions between rooms. - Develop WorldGraph to represent room connectivity and facilitate neighbor queries and distance calculations. - Implement RoomNode and RoomEdge classes to structure room data and connections.
This commit is contained in:
193
Assets/_Game/Scripts/Enemies/Abilities/BlinkStrikeAbility.cs
Normal file
193
Assets/_Game/Scripts/Enemies/Abilities/BlinkStrikeAbility.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
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"/>:
|
||||
/// 当 PathBerserker2d 路径包含 teleport NavLink 时,本能力接管穿越逻辑,
|
||||
/// 播放消失/出现动画后告知 NavAgent 继续路径,而不触发战斗出手。
|
||||
/// </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;
|
||||
|
||||
// ── 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);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user