Unity基于Rigidbody的角色控制器问题:抖动、穿模及游戏卡顿
Unity Rigidbody角色控制器抖动、卡顿与穿模问题排查与修复建议
问题现象
- 角色移动轨迹异常、抖动
- 偶尔出现卡顿甚至卡死,卡顿后玩家可能穿模到地面下方或飞出场景
- 触发滑行动作(仅播放动画,未修改碰撞体或Rigidbody)、落地后偶尔出现穿模,该问题非持续触发
核心代码
移动逻辑(原Move()函数,在FixedUpdate()中调用)
private void Move() { if(!_hasAnimator) { return;} float targetSpeed = _inputManager.Run ? _runSpeed : _walkSpeed; if(_inputManager.Move == Vector2.zero) targetSpeed = 0; _currentVelocity.x = Mathf.Lerp(_currentVelocity.x, _inputManager.Move.x * targetSpeed, AnimBlendSpeed * Time.fixedDeltaTime); _currentVelocity.y = Mathf.Lerp(_currentVelocity.y, _inputManager.Move.y * targetSpeed, AnimBlendSpeed * Time.fixedDeltaTime); if(Mathf.Abs(_currentVelocity.x) < 0.01) _currentVelocity.x = 0; if(Mathf.Abs(_currentVelocity.y) < 0.01) _currentVelocity.y = 0; var xVelDifference = _currentVelocity.x - _playerRigidbody.velocity.x; var zVelDifference = _currentVelocity.y - _playerRigidbody.velocity.z; _playerRigidbody.AddForce(transform.TransformVector(new Vector3(xVelDifference, 0, zVelDifference)), ForceMode.VelocityChange); _animator.SetFloat(_xVelHash, _currentVelocity.x); _animator.SetFloat(_yVelHash, _currentVelocity.y); }
相机与角色旋转逻辑(原CameraMovements()函数,在LateUpdate()中调用)
private void CameraMovements() { if(!_hasAnimator) { return;} var Mouse_X = _inputManager.Look.x; var Mouse_Y = _inputManager.Look.y; Camera.position = CameraRoot.position; _xRotation -= Mouse_Y * MouseSensitivity * Time.smoothDeltaTime; _xRotation = Mathf.Clamp(_xRotation, UpperLimit, BottomLimit); Camera.localRotation = Quaternion.Euler(_xRotation, 0, 0); _playerRigidbody.MoveRotation(_playerRigidbody.rotation * Quaternion.Euler(0, Mouse_X * MouseSensitivity * Time.smoothDeltaTime, 0)); }
问题根源分析
移动逻辑的力控制冲突
使用ForceMode.VelocityChange强制修正速度,会和物理引擎的重力、碰撞反馈等原生力产生冲突,导致角色抖动;Mathf.Lerp结合Time.fixedDeltaTime的插值方式逻辑错误,插值比例随物理帧率波动,导致速度过渡不稳定。旋转与物理帧不同步
在LateUpdate()(渲染帧)中调用_playerRigidbody.MoveRotation,物理操作和渲染帧不同步,会引发角色旋转与移动的错位抖动;鼠标旋转使用Time.smoothDeltaTime,旋转速度随渲染帧率变化,进一步加剧不同步问题。物理属性与碰撞体潜在问题
未明确的刚体约束、碰撞体贴合度不足、碰撞检测模式设置不当,会在碰撞或卡顿后引发穿模。
修复方案
1. 重构移动逻辑,改用稳定的速度控制
替换原Move()函数,使用Mathf.SmoothDamp实现物理帧同步的速度平滑,直接设置刚体速度避免力冲突:
// 新增两个缓存变量用于SmoothDamp private float _velXSmooth; private float _velYSmooth; private void Move() { if(!_hasAnimator) { return;} float targetSpeed = _inputManager.Run ? _runSpeed : _walkSpeed; if(_inputManager.Move == Vector2.zero) targetSpeed = 0; // 计算目标局部速度,先归一化避免斜向移动超速 Vector2 targetLocalVel = _inputManager.Move.normalized * targetSpeed; // 使用SmoothDamp实现稳定的速度过渡,适配物理帧 _currentVelocity.x = Mathf.SmoothDamp(_currentVelocity.x, targetLocalVel.x, ref _velXSmooth, 0.1f, float.MaxValue, Time.fixedDeltaTime); _currentVelocity.y = Mathf.SmoothDamp(_currentVelocity.y, targetLocalVel.y, ref _velYSmooth, 0.1f, float.MaxValue, Time.fixedDeltaTime); // 将局部速度转换为世界空间,保留Y轴原有速度(重力) Vector3 targetWorldVel = transform.TransformVector(new Vector3(_currentVelocity.x, _playerRigidbody.velocity.y, _currentVelocity.y)); // 直接设置刚体速度,避免力的冲突 _playerRigidbody.velocity = targetWorldVel; _animator.SetFloat(_xVelHash, _currentVelocity.x); _animator.SetFloat(_yVelHash, _currentVelocity.y); }
2. 修正旋转逻辑的帧同步问题
将角色旋转逻辑移到FixedUpdate()中,保证物理操作与物理帧同步;相机垂直旋转保留在LateUpdate():
// 角色旋转逻辑,在FixedUpdate()中调用 private void RotatePlayer() { if(!_hasAnimator) { return;} var Mouse_X = _inputManager.Look.x; // 使用fixedDeltaTime保证物理帧同步的旋转速度 float turnAmount = Mouse_X * MouseSensitivity * Time.fixedDeltaTime; _playerRigidbody.MoveRotation(_playerRigidbody.rotation * Quaternion.Euler(0, turnAmount, 0)); } // 相机垂直旋转逻辑,保留在LateUpdate() private void CameraMovements() { if(!_hasAnimator) { return;} var Mouse_Y = _inputManager.Look.y; Camera.position = CameraRoot.position; _xRotation -= Mouse_Y * MouseSensitivity * Time.deltaTime; _xRotation = Mathf.Clamp(_xRotation, UpperLimit, BottomLimit); Camera.localRotation = Quaternion.Euler(_xRotation, 0, 0); }
3. 物理属性与碰撞体优化
- 刚体设置:锁定X、Z轴旋转(避免角色倾斜),设置
Mass为8-12,Drag为0.1-0.5,Angular Drag为5-10,减少不必要的物理波动 - 碰撞体设置:角色使用胶囊碰撞体(
CapsuleCollider)并贴合模型;地面若使用网格碰撞体,勾选Convex以支持动态物体交互 - 碰撞检测:角色刚体设为
Continuous Dynamic,地面设为Continuous,减少高速移动时的穿模概率
4. 卡顿与穿模额外排查
- 检查是否有其他脚本在非
FixedUpdate()中修改刚体位置/速度(如动画脚本误操作Transform) - 滑行动作关闭动画根运动,避免动画位移与刚体移动冲突
- 检查项目物理设置中的
Fixed Timestep,建议设为0.02(50帧/秒),避免物理帧间隔过大引发卡顿
内容的提问来源于stack exchange,提问作者CrackerBone
相关产品推荐
相关产品推荐

