刚体控制器在移动平台上滑动
我有一个非常基础的刚体玩家控制器和一个移动平台脚本。当玩家站在移动平台上时,玩家会随着平台一起移动。然而,当平台迅速改变方向时,玩家在改变方向前的移动方向上滑动几帧。
我希望无论平台的速度有多快,都能让玩家在平台上的同一位置保持不变。我已经应用了一个物理材质来消除所有摩擦,但无论摩擦系数如何,当平台速度超过某个阈值时,仍会发生滑动。
using System;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
private Rigidbody _rigidbody;
private CapsuleCollider _capsule;
private RaycastHit _ground;
private bool _isGrounded = false;
private Vector3 _direction = Vector3.zero;
private Vector3 _velocity = Vector3.zero;
private Vector3 _gravity = Vector3.zero;
private Vector3 _platform = Vector3.zero;
private Vector3 _lastPlatformVelocity = Vector3.zero;
void Start()
{
_capsule = GetComponent<CapsuleCollider>();
_rigidbody = GetComponent<Rigidbody>();
_rigidbody.constraints = RigidbodyConstraints.FreezeRotation;
}
public void OnMove(InputAction.CallbackContext context)
{
var value = context.ReadValue<Vector2>();
_direction.x = value.x;
_direction.z = value.y;
}
public void OnJump(InputAction.CallbackContext context)
{
if (_isGrounded && context.performed)
{
_velocity.y = 10f;
}
}
private void FixedUpdate()
{
_isGrounded = CheckGrounded();
Gravity();
Platform();
Movement();
_rigidbody.linearVelocity = _velocity + _platform;
}
private void Platform()
{
if (_isGrounded && _ground.rigidbody != null)
{
_platform = _ground.rigidbody.GetPointVelocity(_ground.point);
// Shift _velocity into the new platform's reference frame.
// This handles both landing AND mid-platform direction changes.
_velocity -= Vector3.ProjectOnPlane(_platform - _lastPlatformVelocity, Vector3.up); ;
}
else
{
// Leaving the platform — eject any stored platform velocity into _velocity
// so the player carries their momentum through the air correctly.
_velocity += _lastPlatformVelocity;
_platform = Vector3.zero;
}
_lastPlatformVelocity = _platform;
}
private void Movement()
{
var acceleration = 15f;
const float baseSpeed = 10f;
Vector3 facing = transform.TransformDirection(_direction);
float verticalVelocity = _velocity.y;
if (_isGrounded)
{
_velocity = Vector3.Lerp(_velocity, facing * baseSpeed, acceleration * Time.deltaTime);
}
else
{
Vector3 horizontalVelocity = Vector3.ProjectOnPlane(_velocity, Vector3.up);
// Preserve inertia in air
float speedCap = Mathf.Max(horizontalVelocity.magnitude, baseSpeed);
horizontalVelocity += facing * acceleration * Time.deltaTime;
_velocity = Vector3.ClampMagnitude(horizontalVelocity, speedCap);
}
Debug.Log(Vector3.ProjectOnPlane(_velocity, Vector3.up));
_velocity.y = verticalVelocity;
}
private void Gravity()
{
if (!_isGrounded)
{
_velocity += Physics.gravity * 2f * Time.deltaTime;
}
}
private bool CheckGrounded()
{
Vector3 center = transform.TransformPoint(_capsule.center);
float radius = _capsule.radius * Mathf.Max(transform.lossyScale.x, transform.lossyScale.z);
return Physics.SphereCast(
center,
radius * 0.95f,
Vector3.down,
out _ground,
maxDistance: 0.55f
);
}
}
奇怪的是,这段平台方法中的这一部分让玩家滑动得更严重,但修复了在玩家跳跃时将平台速度应用给玩家的另外一个问题。
_velocity -= Vector3.ProjectOnPlane(_platform - _lastPlatformVelocity, Vector3.up); ;
注意:你可能会问“为什么不使用CharacterController呢?”CharacterController有一个重大缺陷,导致无法在X 轴和Z 轴上旋转玩家。我正在尝试引入重力井的想法。
解决方案
根本原因在于 GetPointVelocity 总是滞后一帧。你读取平台的速度、将其应用,然后下一个物理步骤发生——平台已经继续移动了。在低速时错误不可见;在高速时(尤其是方向反转时),那一帧的滞后就会变成可见的滑动。
解决办法是完全不再用速度空间来处理平台移动。改为跟踪平台的位置增量,并用 MovePosition 来应用。位置增量是精确的。没有近似,没有滞后。
Rewrite Platform():
private Rigidbody _platformRb;
private Vector3 _lastPlatformPos;
private Quaternion _lastPlatformRot;
private bool _wasOnPlatform;
private void Platform()
{
if (_isGrounded && _ground.rigidbody != null)
{
Rigidbody platformRb = _ground.rigidbody;
if (_wasOnPlatform && platformRb == _platformRb)
{
Vector3 posDelta = platformRb.position - _lastPlatformPos;
_rigidbody.MovePosition(_rigidbody.position + posDelta);
}
_lastPlatformPos = platformRb.position;
_lastPlatformRot = platformRb.rotation;
_platformRb = platformRb;
_wasOnPlatform = true;
// Only needed for jump handoff
_platform = platformRb.GetPointVelocity(_ground.point);
}
else
{
if (_wasOnPlatform)
_velocity += _lastPlatformVelocity;
_platform = Vector3.zero;
_wasOnPlatform = false;
_platformRb = null;
}
_lastPlatformVelocity = _platform;
}
Since MovePosition now handles platform movement, remove _platform from the velocity line:
// was: _rigidbody.linearVelocity = _velocity + _platform;
_rigidbody.linearVelocity = _velocity;
如果平台也会旋转,就把增量扩展以考虑旋转:
if (_wasOnPlatform && platformRb == _platformRb)
{
Quaternion rotDelta = platformRb.rotation * Quaternion.Inverse(_lastPlatformRot);
Vector3 pivotOffset = _rigidbody.position - _lastPlatformPos;
Vector3 posDelta = (platformRb.position - _lastPlatformPos)
+ (rotDelta * pivotOffset - pivotOffset);
_rigidbody.MovePosition(_rigidbody.position + posDelta);
}
一个需要注意的点:如果你的平台的 FixedUpdate 发生在玩家之后执行,platformRb.position 将读取上一帧的值——这样滞后就会重新出现。请检查Edit → Project Settings → Script Execution Order,并确保平台脚本在玩家控制器之上。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。