diff --git a/Assets/_Game/Scripts/Core/IPassengerReceiver.cs b/Assets/_Game/Scripts/Core/IPassengerReceiver.cs index 3ed0f40..001a98e 100644 --- a/Assets/_Game/Scripts/Core/IPassengerReceiver.cs +++ b/Assets/_Game/Scripts/Core/IPassengerReceiver.cs @@ -9,25 +9,28 @@ namespace BaseGames.Core /// /// 协议(执行顺序保障): /// 1. MovingPlatform.FixedUpdate(-300)计算本帧期望位移 delta → 调用 SetPlatformDelta, - /// 实现方将其转换为速度(delta / fixedDeltaTime)存入下帧缓冲区。 - /// 2. 实现方 FixedUpdate(-200)将缓冲区速度换入当前帧变量。 - /// 3. 状态机 FixedUpdate(-100)通过 Move() 将平台速度叠加到水平速度, - /// 由 velocity 驱动运动,与 RigidbodyInterpolation2D.Interpolate 完全兼容。 - /// 4. 乘客离开传感器时 MovingPlatform 调用 OnLeavePlatform,继承垂直速度分量; - /// 水平分量已通过最后一次 Move() 自然携带,无需重复叠加。 + /// 实现方将其累积到 _pendingPlatformDelta。 + /// 2. 实现方 FixedUpdate(-200)执行 _rb.position += _pendingPlatformDelta 并清零。 + /// 使用 Rigidbody2D.position(而非 transform.position):Unity 保证在 FixedUpdate 中 + /// 赋值时会同步更新插值参考点,不引起视觉抖动,与 RigidbodyInterpolation2D.Interpolate 兼容。 + /// 3. 状态机 FixedUpdate(-100)正常调用 Move(speedX);_rb.velocity.x 仅含玩家输入速度, + /// UpdateFacing / ApplyAirDrag 无需减去任何平台速度,语义清晰。 + /// 4. 乘客离开传感器时 MovingPlatform 调用 OnLeavePlatform,继承完整平台速度(含水平), + /// 保证从移动平台起跳时具有平台动量,手感自然。 /// public interface IPassengerReceiver { /// /// 由 MovingPlatform 每帧推送本帧平台期望位移量。 - /// 实现方将其转换为速度并存入缓冲区,在自己的 FixedUpdate 换入后 - /// 经 Move() 叠加到水平速度,保持插值平滑。 + /// 实现方将其累积到内部字段,在自己的 FixedUpdate 中通过 Rigidbody2D.position + /// 直接施加位移,不混入 velocity,保持玩家速度语义纯净。 /// void SetPlatformDelta(Vector2 delta); /// /// 乘客离开平台时调用,传入平台当前帧速度。 - /// 实现方仅需继承垂直分量;水平分量已由最后一次 Move() 自然携带。 + /// 实现方应将完整平台速度(含水平)叠加到自身 velocity, + /// 保证离台瞬间具有平台动量,手感自然。 /// void OnLeavePlatform(Vector2 platformVelocity); } diff --git a/Assets/_Game/Scripts/Player/PlayerMovement.cs b/Assets/_Game/Scripts/Player/PlayerMovement.cs index 03c87d7..8489bd2 100644 --- a/Assets/_Game/Scripts/Player/PlayerMovement.cs +++ b/Assets/_Game/Scripts/Player/PlayerMovement.cs @@ -31,10 +31,11 @@ namespace BaseGames.Player // 下一个 FixedUpdate(-200,先于状态机 -100)读取并清零, // 防止状态机用旧输入把速度重新写成非零值。 private bool _pendingHorizontalZero; - // 移动平台速度双缓冲:Platform(-300) 写入 _next;Player(-200) 将 _next 换入 _current 并清零 _next; - // StateM(-100) 的 Move() 读取 _current,确保速度基于 velocity 设置,与 Interpolate 系统兼容。 - private Vector2 _platformVelocity; // 本帧消费(StateM 通过 Move() 读取) - private Vector2 _nextPlatformVelocity; // Platform(-300) 写入缓冲区 + // 移动平台位移累积:Platform(-300) 写入;Player(-200) 通过 _rb.position += 消费后清零。 + // 使用位置偏移而非速度叠加,_rb.velocity.x 保持纯粹的玩家输入速度, + // UpdateFacing / ApplyAirDrag 无需任何修正。 + // Unity 文档明确:FixedUpdate 中设置 Rigidbody2D.position 与 Interpolate 完全兼容。 + private Vector2 _pendingPlatformDelta; private bool _isWallLeft; private bool _isWallRight; private bool _onOneWayPlatform; @@ -87,11 +88,11 @@ namespace BaseGames.Player private void FixedUpdate() { - // 平台速度双缓冲换入:Platform(-300) 已将本帧速度写入 _next; - // 这里将其换入 _current 供 Move() 叠加,并清零 _next 以备下帧使用。 - // 若本帧玩家已离台,Platform 不写 _next,换入结果为 Vector2.zero,自然恢复独立运动。 - _platformVelocity = _nextPlatformVelocity; - _nextPlatformVelocity = Vector2.zero; + // 消费本帧平台位移:直接修改物理位置,不污染 _rb.velocity。 + // Rigidbody2D.position 在 FixedUpdate 内赋值时,Unity 会同步更新插值参考点, + // 不会引起视觉抖动。 + _rb.position += _pendingPlatformDelta; + _pendingPlatformDelta = Vector2.zero; // 优先处理来自 Update 的强制清零请求(在状态机 OnStateFixedUpdate 之前执行)。 if (_pendingHorizontalZero) @@ -130,11 +131,11 @@ namespace BaseGames.Player /// 直接赋予目标水平速度(按键即全速,松键即停,无加速过渡)。 /// 地面状态每帧直接到达全速;空中调用时同样即时,但配合 ApplyAirDrag /// 在无输入时自然减速,保留跳出时的动量。 - /// 若当前站在移动平台上,自动叠加 _platformVelocity.x,保持与平台同步。 + /// 平台携带通过 _rb.position 偏移实现,此处无需叠加平台速度。 /// public void Move(float speedX) { - _rb.velocity = new Vector2(speedX + _platformVelocity.x, _rb.velocity.y); + _rb.velocity = new Vector2(speedX, _rb.velocity.y); } /// @@ -192,9 +193,9 @@ namespace BaseGames.Player // ── 朝向 ────────────────────────────────────────────────────────────── public void UpdateFacing() { - // 减去平台速度分量,只根据玩家自身输入速度判断朝向; - // 否则站在横向移动平台上静止时会被平台速度驱动朝向翻转。 - float vx = _rb.velocity.x - _platformVelocity.x; + // _rb.velocity.x 是纯玩家输入速度(平台携带通过位置偏移,不混入 velocity), + // 直接读取即可,无需减去平台速度。 + float vx = _rb.velocity.x; if (Mathf.Abs(vx) < 0.1f) return; int dir = vx > 0f ? 1 : -1; if (dir == _facingDirection) return; @@ -237,21 +238,19 @@ namespace BaseGames.Player // ── IPassengerReceiver ──────────────────────────────────────────────── /// /// MovingPlatform.FixedUpdate(-300) 推送本帧平台期望位移。 - /// 转换为速度(delta / fixedDeltaTime)写入下帧缓冲区 _next, - /// 在本类 FixedUpdate(-200) 换入 _current,由 Move() 叠加到水平速度。 - /// 基于 velocity 的方案与 RigidbodyInterpolation2D.Interpolate 完全兼容, - /// 消除直接写 _rb.position 导致的视觉抖动/速度不一致。 + /// 累积到 _pendingPlatformDelta;在本类 FixedUpdate(-200) 通过 _rb.position += 消费, + /// 与玩家 velocity 完全解耦,UpdateFacing / ApplyAirDrag 无需修正。 /// public void SetPlatformDelta(Vector2 delta) - => _nextPlatformVelocity += delta / Time.fixedDeltaTime; + => _pendingPlatformDelta += delta; /// - /// 乘客离开平台时调用。 - /// 水平速度已通过 Move() 自然继承(最后一次 Move 调用已包含 platformVelocity.x), - /// 此处仅继承垂直分量,保证从竖向移动平台离台时无速度突变。 + /// 乘客离开平台时调用,传入平台当前帧速度。 + /// 此方案下 _rb.velocity 为纯玩家速度,离台时直接继承平台完整速度(含水平), + /// 保证从移动平台起跳后具有平台动量,手感自然。 /// public void OnLeavePlatform(Vector2 platformVelocity) - => _rb.velocity += new Vector2(0f, platformVelocity.y); + => _rb.velocity += platformVelocity; // ── 冲刺 ────────────────────────────────────────────────────────────── ///