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); }