diff --git a/Assets/_Game/Scripts/Core/IPassengerReceiver.cs b/Assets/_Game/Scripts/Core/IPassengerReceiver.cs
index 5710677..3ed0f40 100644
--- a/Assets/_Game/Scripts/Core/IPassengerReceiver.cs
+++ b/Assets/_Game/Scripts/Core/IPassengerReceiver.cs
@@ -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() 自然携带,无需重复叠加。
///
public interface IPassengerReceiver
{
///
- /// 由 MovingPlatform 每帧推送本帧平台期望位移量(不依赖 MovePosition 是否已执行)。
- /// 实现方存入待处理字段,在自己的 FixedUpdate 最前端消费,避免与 velocity 冲突。
+ /// 由 MovingPlatform 每帧推送本帧平台期望位移量。
+ /// 实现方将其转换为速度并存入缓冲区,在自己的 FixedUpdate 换入后
+ /// 经 Move() 叠加到水平速度,保持插值平滑。
///
void SetPlatformDelta(Vector2 delta);
///
- /// 乘客离开平台时调用,传入平台当前帧速度,由实现方叠加到自身速度。
- /// 防止离台瞬间速度突变产生卡顿。
+ /// 乘客离开平台时调用,传入平台当前帧速度。
+ /// 实现方仅需继承垂直分量;水平分量已由最后一次 Move() 自然携带。
///
void OnLeavePlatform(Vector2 platformVelocity);
}
diff --git a/Assets/_Game/Scripts/Player/PlayerMovement.cs b/Assets/_Game/Scripts/Player/PlayerMovement.cs
index 46bc9e1..03c87d7 100644
--- a/Assets/_Game/Scripts/Player/PlayerMovement.cs
+++ b/Assets/_Game/Scripts/Player/PlayerMovement.cs
@@ -31,7 +31,10 @@ namespace BaseGames.Player
// 下一个 FixedUpdate(-200,先于状态机 -100)读取并清零,
// 防止状态机用旧输入把速度重新写成非零值。
private bool _pendingHorizontalZero;
- private Vector2 _pendingPlatformDelta; // MovingPlatform 推送的本帧位移,在 FixedUpdate 最前端消费
+ // 移动平台速度双缓冲:Platform(-300) 写入 _next;Player(-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
/// 在无输入时自然减速,保留跳出时的动量。
+ /// 若当前站在移动平台上,自动叠加 _platformVelocity.x,保持与平台同步。
///
public void Move(float speedX)
{
- _rb.velocity = new Vector2(speedX, _rb.velocity.y);
+ _rb.velocity = new Vector2(speedX + _platformVelocity.x, _rb.velocity.y);
}
///
@@ -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 ────────────────────────────────────────────────
///
- /// 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 导致的视觉抖动/速度不一致。
///
- public void SetPlatformDelta(Vector2 delta) => _pendingPlatformDelta += delta;
+ public void SetPlatformDelta(Vector2 delta)
+ => _nextPlatformVelocity += delta / Time.fixedDeltaTime;
- /// 离开移动平台时继承平台速度,防止速度骤变卡顿。
- public void OnLeavePlatform(Vector2 platformVelocity) => _rb.velocity += platformVelocity;
+ ///
+ /// 乘客离开平台时调用。
+ /// 水平速度已通过 Move() 自然继承(最后一次 Move 调用已包含 platformVelocity.x),
+ /// 此处仅继承垂直分量,保证从竖向移动平台离台时无速度突变。
+ ///
+ public void OnLeavePlatform(Vector2 platformVelocity)
+ => _rb.velocity += new Vector2(0f, platformVelocity.y);
// ── 冲刺 ──────────────────────────────────────────────────────────────
///
diff --git a/Assets/_Game/Scripts/World/MovingPlatform.cs b/Assets/_Game/Scripts/World/MovingPlatform.cs
index e0c1de6..452b3d2 100644
--- a/Assets/_Game/Scripts/World/MovingPlatform.cs
+++ b/Assets/_Game/Scripts/World/MovingPlatform.cs
@@ -9,11 +9,12 @@ namespace BaseGames.World
///
/// 移动平台。Kinematic Rigidbody2D,支持三种移动模式。
///
- /// 乘客跟随(Delta Position 方案,替代 SetParent):
- /// - PassengerSensor(Trigger)检测进入乘客()。
- /// - FixedUpdate(-300) 计算本帧期望位移 delta,推送给所有乘客,再执行 MovePosition。
- /// - 乘客在自己的 FixedUpdate(-200) 最前端消费 delta:_rb.position += delta。
- /// - 乘客离台时通过 OnLeavePlatform 继承平台速度,避免卡顿。
+ /// 乘客跟随(Velocity 双缓冲方案):
+ /// - PassengerSensor(Trigger)通过 attachedRigidbody 检测乘客()。
+ /// - FixedUpdate(-300) 计算本帧期望位移 delta → SetPlatformDelta(delta) 写入乘客缓冲区。
+ /// - 乘客 FixedUpdate(-200) 换入缓冲速度;状态机 FixedUpdate(-100) 的 Move() 将其叠加到 velocity。
+ /// - 基于 velocity 驱动,与 RigidbodyInterpolation2D.Interpolate 完全兼容,无视觉抖动。
+ /// - 乘客离台时 OnLeavePlatform 继承垂直速度分量;水平分量已由 Move() 自然携带。
///
/// 此方案不修改 Transform 层级,与动画/摄像机/HitBox 系统完全兼容。
///
@@ -143,17 +144,20 @@ namespace BaseGames.World
private void OnTriggerEnter2D(Collider2D other)
{
if (!IsPassengerLayer(other)) return;
- if (!other.TryGetComponent(out var receiver)) return;
+ // attachedRigidbody:若玩家碰撞体在子对象上,也能正确找到根对象上的 IPassengerReceiver
+ if (!other.attachedRigidbody) return;
+ if (!other.attachedRigidbody.TryGetComponent(out var receiver)) return;
if (!_passengers.Contains(receiver))
_passengers.Add(receiver);
}
private void OnTriggerExit2D(Collider2D other)
{
- if (!other.TryGetComponent(out var receiver)) return;
+ if (!other.attachedRigidbody) return;
+ if (!other.attachedRigidbody.TryGetComponent(out var receiver)) return;
if (!_passengers.Remove(receiver)) return;
- // 继承平台速度,防止离台时速度骤变卡顿
+ // 继承平台垂直速度;水平速度已由 IPassengerReceiver.Move() 自然携带,无需重复叠加
receiver.OnLeavePlatform(_frameVelocity);
}