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:
@@ -1,22 +1,117 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人移动组件(架构 07_EnemyModule §3)。
|
||||
/// 实现:水平移动、面向目标、击退。
|
||||
/// 实现:水平移动、面向目标、击退,以及导航连接段穿越(<see cref="INavLinkHandler"/>)。
|
||||
///
|
||||
/// 作为 <see cref="INavLinkHandler"/> 处理 Jump / Fall 两种 NavLink 类型:
|
||||
/// - 跳跃连接(Jump):调用 <see cref="JumpToTarget"/> 施加物理冲量,等待落地后通知完成
|
||||
/// - 下落连接(Fall) :水平对准目标X,让重力自然下坠,到达目标Y附近通知完成
|
||||
/// 没有 EnemyMovement 组件(或 Jump 能力被移除)的敌人将无法通过跳跃连接,
|
||||
/// 路径代价保持 TransformBasedMovement 兜底(仍可跳,但无自定义动画/物理)。
|
||||
///
|
||||
/// ⚠️ 使用 Rigidbody2D.velocity(Unity 2022 LTS)。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
public class EnemyMovement : MonoBehaviour
|
||||
public class EnemyMovement : MonoBehaviour, INavLinkHandler
|
||||
{
|
||||
[SerializeField] private EnemyStatsSO _config;
|
||||
[SerializeField] private SpriteRenderer _spriteRenderer;
|
||||
|
||||
[Header("导航跳跃能力(INavLinkHandler)")]
|
||||
[Tooltip("可处理的最大跳跃垂直高度(超出则让 TBM 兜底)")]
|
||||
[SerializeField] private float _navJumpMaxHeight = 6f;
|
||||
[Tooltip("可处理的最大跳跃水平距离")]
|
||||
[SerializeField] private float _navJumpMaxDist = 10f;
|
||||
[Tooltip("地面检测射线长度(用于判断跳跃是否落地)")]
|
||||
[SerializeField] private float _groundCheckDist = 0.35f;
|
||||
[Tooltip("地面层 LayerMask")]
|
||||
[SerializeField] private LayerMask _groundMask;
|
||||
|
||||
private Rigidbody2D _rb;
|
||||
private int _facingDir = 1;
|
||||
private Coroutine _linkCoroutine;
|
||||
|
||||
public bool IsGrounded { get; private set; }
|
||||
/// <summary>当前朝向:1 = 右,-1 = 左。</summary>
|
||||
public int FacingDirection => _facingDir;
|
||||
|
||||
// ── INavLinkHandler ────────────────────────────────────────────
|
||||
private static readonly NavLinkType[] _handledTypes =
|
||||
new[] { NavLinkType.Jump, NavLinkType.Fall };
|
||||
|
||||
public NavLinkType[] HandledLinkTypes => _handledTypes;
|
||||
|
||||
public bool CanHandleLink(NavLinkType type, Vector2 linkStart, Vector2 linkEnd)
|
||||
{
|
||||
if (type == NavLinkType.Jump)
|
||||
{
|
||||
float dy = Mathf.Abs(linkEnd.y - linkStart.y);
|
||||
float dx = Mathf.Abs(linkEnd.x - linkStart.x);
|
||||
return dy <= _navJumpMaxHeight && dx <= _navJumpMaxDist;
|
||||
}
|
||||
return true; // Fall 总是可以处理
|
||||
}
|
||||
|
||||
public void BeginLinkTraversal(NavLinkType type, Vector2 linkStart, Vector2 linkEnd, Action onComplete)
|
||||
{
|
||||
if (_linkCoroutine != null) StopCoroutine(_linkCoroutine);
|
||||
_linkCoroutine = type == NavLinkType.Jump
|
||||
? StartCoroutine(JumpLinkCoroutine(linkStart, linkEnd, onComplete))
|
||||
: StartCoroutine(FallLinkCoroutine(linkStart, linkEnd, onComplete));
|
||||
}
|
||||
|
||||
public void AbortLinkTraversal()
|
||||
{
|
||||
if (_linkCoroutine != null) { StopCoroutine(_linkCoroutine); _linkCoroutine = null; }
|
||||
StopHorizontal();
|
||||
}
|
||||
|
||||
private IEnumerator JumpLinkCoroutine(Vector2 start, Vector2 end, Action onComplete)
|
||||
{
|
||||
JumpToTarget(end);
|
||||
yield return null; // 等一帧让 velocity 生效
|
||||
|
||||
// 等待离地后落地(超时 3s 防死锁)
|
||||
float timer = 0f;
|
||||
bool leftGround = false;
|
||||
while (timer < 3f)
|
||||
{
|
||||
timer += Time.fixedDeltaTime;
|
||||
yield return new WaitForFixedUpdate();
|
||||
if (!leftGround && !IsGroundedCheck()) { leftGround = true; }
|
||||
if (leftGround && IsGroundedCheck()) break;
|
||||
}
|
||||
StopHorizontal();
|
||||
_linkCoroutine = null;
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
|
||||
private IEnumerator FallLinkCoroutine(Vector2 start, Vector2 end, Action onComplete)
|
||||
{
|
||||
// 水平对准目标
|
||||
float dx = end.x - (float)transform.position.x;
|
||||
if (Mathf.Abs(dx) > 0.15f) MoveHorizontal(Mathf.Sign(dx));
|
||||
|
||||
// 等待接近目标Y(重力驱动下落)
|
||||
float timer = 0f;
|
||||
while (timer < 3f)
|
||||
{
|
||||
timer += Time.fixedDeltaTime;
|
||||
yield return new WaitForFixedUpdate();
|
||||
if (IsGroundedCheck() && Mathf.Abs(_rb.position.y - end.y) < 0.6f) break;
|
||||
}
|
||||
StopHorizontal();
|
||||
_linkCoroutine = null;
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
|
||||
private bool IsGroundedCheck() =>
|
||||
Physics2D.Raycast(_rb.position, Vector2.down, _groundCheckDist, _groundMask);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -24,6 +119,11 @@ namespace BaseGames.Enemies
|
||||
_rb = GetComponent<Rigidbody2D>();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
IsGrounded = IsGroundedCheck();
|
||||
}
|
||||
|
||||
/// <summary>按 SO 配置速度水平移动。dir: +1 右 / -1 左 / 0 停止。</summary>
|
||||
public void MoveHorizontal(float dir)
|
||||
{
|
||||
@@ -53,6 +153,20 @@ namespace BaseGames.Enemies
|
||||
_rb.velocity = dir.normalized * force;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 击飞冲量:向上 + 沿受击反方向水平。
|
||||
/// sourceDir 为伤害来源朝向(通常是 DamageInfo.KnockbackDirection),横向取其反方向。
|
||||
/// </summary>
|
||||
/// <param name="sourceDir">来袭方向(已归一化)</param>
|
||||
/// <param name="horzForce">水平冲量大小</param>
|
||||
/// <param name="upForce">纵向冲量大小</param>
|
||||
public void LaunchKnockup(Vector2 sourceDir, float horzForce, float upForce)
|
||||
{
|
||||
if (_rb == null) return;
|
||||
float horzSign = sourceDir.x >= 0f ? -1f : 1f; // 反方向弹飞
|
||||
_rb.velocity = new Vector2(horzSign * horzForce, upForce);
|
||||
}
|
||||
|
||||
public void StopHorizontal()
|
||||
{
|
||||
var vel = _rb.velocity;
|
||||
|
||||
Reference in New Issue
Block a user