优化移动平台与乘客的速度继承逻辑,改用双缓冲方案以提升平滑度和兼容性

This commit is contained in:
2026-05-21 18:59:22 +08:00
parent 247a218182
commit c7c8171b8a
3 changed files with 52 additions and 30 deletions

View File

@@ -8,21 +8,26 @@ namespace BaseGames.Core
/// 无需修改 Transform 父子关系,避免与动画/摄像机/HitBox 系统冲突。
///
/// 协议(执行顺序保障):
/// 1. MovingPlatform.FixedUpdate-300计算本帧期望位移 delta → 调用 SetPlatformDelta
/// 2. 实现方 FixedUpdate-200最前端消费 delta_rb.position += delta
/// 3. 乘客离开传感器时 MovingPlatform 调用 OnLeavePlatform继承平台速度防止卡顿
/// 1. MovingPlatform.FixedUpdate-300计算本帧期望位移 delta → 调用 SetPlatformDelta
/// 实现方将其转换为速度delta / fixedDeltaTime存入下帧缓冲区
/// 2. 实现方 FixedUpdate-200将缓冲区速度换入当前帧变量
/// 3. 状态机 FixedUpdate-100通过 Move() 将平台速度叠加到水平速度,
/// 由 velocity 驱动运动,与 RigidbodyInterpolation2D.Interpolate 完全兼容。
/// 4. 乘客离开传感器时 MovingPlatform 调用 OnLeavePlatform继承垂直速度分量
/// 水平分量已通过最后一次 Move() 自然携带,无需重复叠加。
/// </summary>
public interface IPassengerReceiver
{
/// <summary>
/// 由 MovingPlatform 每帧推送本帧平台期望位移量(不依赖 MovePosition 是否已执行)
/// 实现方存入待处理字段,在自己的 FixedUpdate 最前端消费,避免与 velocity 冲突。
/// 由 MovingPlatform 每帧推送本帧平台期望位移量。
/// 实现方将其转换为速度并存入缓冲区,在自己的 FixedUpdate 换入后
/// 经 Move() 叠加到水平速度,保持插值平滑。
/// </summary>
void SetPlatformDelta(Vector2 delta);
/// <summary>
/// 乘客离开平台时调用,传入平台当前帧速度,由实现方叠加到自身速度
/// 防止离台瞬间速度突变产生卡顿
/// 乘客离开平台时调用,传入平台当前帧速度。
/// 实现方仅需继承垂直分量;水平分量已由最后一次 Move() 自然携带
/// </summary>
void OnLeavePlatform(Vector2 platformVelocity);
}

View File

@@ -31,7 +31,10 @@ namespace BaseGames.Player
// 下一个 FixedUpdate-200先于状态机 -100读取并清零
// 防止状态机用旧输入把速度重新写成非零值。
private bool _pendingHorizontalZero;
private Vector2 _pendingPlatformDelta; // MovingPlatform 推送的本帧位移,在 FixedUpdate 最前端消费
// 移动平台速度双缓冲Platform(-300) 写入 _nextPlayer(-200) 将 _next 换入 _current 并清零 _next
// StateM(-100) 的 Move() 读取 _current确保速度基于 velocity 设置,与 Interpolate 系统兼容。
private Vector2 _platformVelocity; // 本帧消费StateM 通过 Move() 读取)
private Vector2 _nextPlatformVelocity; // Platform(-300) 写入缓冲区
private bool _isWallLeft;
private bool _isWallRight;
private bool _onOneWayPlatform;
@@ -84,12 +87,11 @@ namespace BaseGames.Player
private void FixedUpdate()
{
// 消费移动平台推送的位移(在 velocity 系统之前直接写 position避免冲突
if (_pendingPlatformDelta != Vector2.zero)
{
_rb.position += _pendingPlatformDelta;
_pendingPlatformDelta = Vector2.zero;
}
// 平台速度双缓冲换入Platform(-300) 已将本帧速度写入 _next
// 这里将其换入 _current 供 Move() 叠加,并清零 _next 以备下帧使用。
// 若本帧玩家已离台Platform 不写 _next换入结果为 Vector2.zero自然恢复独立运动。
_platformVelocity = _nextPlatformVelocity;
_nextPlatformVelocity = Vector2.zero;
// 优先处理来自 Update 的强制清零请求(在状态机 OnStateFixedUpdate 之前执行)。
if (_pendingHorizontalZero)
@@ -128,10 +130,11 @@ namespace BaseGames.Player
/// 直接赋予目标水平速度(按键即全速,松键即停,无加速过渡)。
/// 地面状态每帧直接到达全速;空中调用时同样即时,但配合 ApplyAirDrag
/// 在无输入时自然减速,保留跳出时的动量。
/// 若当前站在移动平台上,自动叠加 <c>_platformVelocity.x</c>,保持与平台同步。
/// </summary>
public void Move(float speedX)
{
_rb.velocity = new Vector2(speedX, _rb.velocity.y);
_rb.velocity = new Vector2(speedX + _platformVelocity.x, _rb.velocity.y);
}
/// <summary>
@@ -189,7 +192,9 @@ namespace BaseGames.Player
// ── 朝向 ──────────────────────────────────────────────────────────────
public void UpdateFacing()
{
float vx = _rb.velocity.x;
// 减去平台速度分量,只根据玩家自身输入速度判断朝向;
// 否则站在横向移动平台上静止时会被平台速度驱动朝向翻转。
float vx = _rb.velocity.x - _platformVelocity.x;
if (Mathf.Abs(vx) < 0.1f) return;
int dir = vx > 0f ? 1 : -1;
if (dir == _facingDirection) return;
@@ -231,14 +236,22 @@ namespace BaseGames.Player
// ── IPassengerReceiver ────────────────────────────────────────────────
/// <summary>
/// MovingPlatform.FixedUpdate(-300) 推送本帧平台位移。
/// 在本类 FixedUpdate(-200) 最前端消费,直接写 _rb.position
/// 与 velocity 系统完全隔离,避免 Dynamic Rigidbody 上 MovePosition + velocity 叠加
/// MovingPlatform.FixedUpdate(-300) 推送本帧平台期望位移。
/// 转换为速度delta / fixedDeltaTime写入下帧缓冲区 _next
/// 在本类 FixedUpdate(-200) 换入 _current由 Move() 叠加到水平速度
/// 基于 velocity 的方案与 RigidbodyInterpolation2D.Interpolate 完全兼容,
/// 消除直接写 _rb.position 导致的视觉抖动/速度不一致。
/// </summary>
public void SetPlatformDelta(Vector2 delta) => _pendingPlatformDelta += delta;
public void SetPlatformDelta(Vector2 delta)
=> _nextPlatformVelocity += delta / Time.fixedDeltaTime;
/// <summary>离开移动平台时继承平台速度,防止速度骤变卡顿。</summary>
public void OnLeavePlatform(Vector2 platformVelocity) => _rb.velocity += platformVelocity;
/// <summary>
/// 乘客离开平台时调用。
/// 水平速度已通过 Move() 自然继承(最后一次 Move 调用已包含 platformVelocity.x
/// 此处仅继承垂直分量,保证从竖向移动平台离台时无速度突变。
/// </summary>
public void OnLeavePlatform(Vector2 platformVelocity)
=> _rb.velocity += new Vector2(0f, platformVelocity.y);
// ── 冲刺 ──────────────────────────────────────────────────────────────
/// <summary>

View File

@@ -9,11 +9,12 @@ namespace BaseGames.World
/// <summary>
/// 移动平台。Kinematic Rigidbody2D支持三种移动模式。
///
/// 乘客跟随(Delta Position 方案,替代 SetParent
/// - PassengerSensorTrigger检测进入乘客(<see cref="IPassengerReceiver"/>)。
/// - FixedUpdate(-300) 计算本帧期望位移 delta,推送给所有乘客,再执行 MovePosition
/// - 乘客在自己的 FixedUpdate(-200) 最前端消费 delta_rb.position += delta
/// - 乘客离台时通过 OnLeavePlatform 继承平台速度,避免卡顿
/// 乘客跟随(Velocity 双缓冲方案
/// - PassengerSensorTrigger通过 attachedRigidbody 检测乘客(<see cref="IPassengerReceiver"/>)。
/// - FixedUpdate(-300) 计算本帧期望位移 delta → SetPlatformDelta(delta) 写入乘客缓冲区
/// - 乘客 FixedUpdate(-200) 换入缓冲速度;状态机 FixedUpdate(-100) 的 Move() 将其叠加到 velocity
/// - 基于 velocity 驱动,与 RigidbodyInterpolation2D.Interpolate 完全兼容,无视觉抖动
/// - 乘客离台时 OnLeavePlatform 继承垂直速度分量;水平分量已由 Move() 自然携带。
///
/// 此方案不修改 Transform 层级,与动画/摄像机/HitBox 系统完全兼容。
/// </summary>
@@ -143,17 +144,20 @@ namespace BaseGames.World
private void OnTriggerEnter2D(Collider2D other)
{
if (!IsPassengerLayer(other)) return;
if (!other.TryGetComponent<IPassengerReceiver>(out var receiver)) return;
// attachedRigidbody若玩家碰撞体在子对象上也能正确找到根对象上的 IPassengerReceiver
if (!other.attachedRigidbody) return;
if (!other.attachedRigidbody.TryGetComponent<IPassengerReceiver>(out var receiver)) return;
if (!_passengers.Contains(receiver))
_passengers.Add(receiver);
}
private void OnTriggerExit2D(Collider2D other)
{
if (!other.TryGetComponent<IPassengerReceiver>(out var receiver)) return;
if (!other.attachedRigidbody) return;
if (!other.attachedRigidbody.TryGetComponent<IPassengerReceiver>(out var receiver)) return;
if (!_passengers.Remove(receiver)) return;
// 继承平台速度,防止离台时速度骤变卡顿
// 继承平台垂直速度;水平速度已由 IPassengerReceiver.Move() 自然携带,无需重复叠加
receiver.OnLeavePlatform(_frameVelocity);
}