Unity基于Rigidbody.velocity的跳跃高度精度问题求解
角色跳跃高度精准控制问题解决思路
问题描述
我用Unity的Rigidbody.velocity实现角色跳跃时遇到两个问题:
- 最初用
Update()驱动逻辑,跳跃高度不稳定:设定jumpPickHeight为2时,实际_jumpedHeight在1.8-2.1之间波动 - 换成
FixedUpdate()后高度稳定,但始终达不到设定值,呈线性偏差:比如设为2时实际约1.8,设为1时约0.92。试过用误差因子修正,但无法精准匹配设定值,想知道问题根源和无需误差因子的解决办法。
现有代码
跳跃逻辑代码
[SerializeField] private float jumpPickHeight; [SerializeField] private float jumpPickTime; private float _jumpInitVelocith; // 注:变量名拼写错误,应为_jumpInitVelocity private float _gravity; void OnEnable() { //jumped height _start_Y = _md.transform.position.y; CalculateInitVelocithAndGravity(); _currentSpeed_Y = _jumpInitVelocith; } void FixedUpdate() { //jumped height _jumpedHeight = _md.transform.position.y - _start_Y; _currentSpeed_Y = Mathf.Max(_currentSpeed_Y - _gravity * Time.deltaTime, 0); _rb.velocity += _currentSpeed_Y * Vector3.up; } void OnDisable() { _currentSpeed_Y = 0; _start_Y = 0; //jumped height Debug.Log(_jumpedHeight); _jumpedHeight = 0; }
跳跃数据计算代码
public void CalculateInitVelocithAndGravity() { if (jumpPickHeight <= 0 || jumpPickTime <= 0) return; _jumpInitVelocith = (2 * JumpPickHeight) / jumpPickTime; _gravity = (2 * JumpPickHeight) / (jumpPickTime * jumpPickTime); }
尝试的误差因子修正代码
public const float JUMP_PICK_HEIGHT_ERROR = 1.0927f; public float JumpPickHeight { get => _jumpPickHeight * JUMP_PICK_HEIGHT_ERROR; } [SerializeField] float _jumpPickHeight;
问题根源
- Update()高度波动原因:
Update()的调用帧率不固定,Time.deltaTime每次取值不同,导致速度累加的步长不一致,最终跳跃高度出现随机波动。 - FixedUpdate()线性偏差原因:
- 速度更新逻辑错误:代码中使用
_rb.velocity += _currentSpeed_Y * Vector3.up,这是对速度的二次累加——Rigidbody的velocity本身已经包含上一帧的垂直速度,再次叠加当前计算的速度分量,导致实际速度与理论值严重偏离,最终高度不足。 - 变量名混淆:计算初始速度和重力时引用了
JumpPickHeight属性(带误差修正),但字段命名存在大小写混淆,可能导致计算用值与设定值不一致。 - 不合理的速度钳制:
Mathf.Max(..., 0)会在速度降到0后停止重力作用,但实际运动逻辑中,速度计算应该持续到达到设定高度为止,提前钳制会导致高度未达标就停止加速。
- 速度更新逻辑错误:代码中使用
无需误差因子的解决方法
方案1:修正Rigidbody速度控制逻辑
核心是直接设置垂直速度,而非累加,同时使用固定时间步长计算:
[SerializeField] private float jumpPickHeight; [SerializeField] private float jumpPickTime; private float _jumpInitVelocity; // 修正拼写错误 private float _gravity; private float _currentSpeed_Y; private float _start_Y; private float _jumpedHeight; void OnEnable() { _start_Y = _md.transform.position.y; CalculateInitVelocityAndGravity(); // 修正方法名拼写 _currentSpeed_Y = _jumpInitVelocity; } void FixedUpdate() { _jumpedHeight = _md.transform.position.y - _start_Y; // 用fixedDeltaTime确保步长固定 _currentSpeed_Y -= _gravity * Time.fixedDeltaTime; // 精准控制:当达到设定高度时,锁定垂直速度为0 if (_jumpedHeight >= jumpPickHeight) { _currentSpeed_Y = 0; } // 直接设置垂直速度,而非累加 Vector3 newVelocity = _rb.velocity; newVelocity.y = _currentSpeed_Y; _rb.velocity = newVelocity; } void OnDisable() { _currentSpeed_Y = 0; _start_Y = 0; Debug.Log(_jumpedHeight); _jumpedHeight = 0; } public void CalculateInitVelocityAndGravity() { if (jumpPickHeight <= 0 || jumpPickTime <= 0) return; // 使用原始字段值,避免误差修正的干扰 _jumpInitVelocity = (2 * jumpPickHeight) / jumpPickTime; _gravity = (2 * jumpPickHeight) / (jumpPickTime * jumpPickTime); }
方案2:基于时间的位移直接控制(零误差)
如果不需要物理交互,直接通过运动学公式计算位移,完全精准匹配设定高度:
[SerializeField] private float jumpPickHeight; [SerializeField] private float jumpPickTime; private float _start_Y; private float _jumpElapsedTime; void OnEnable() { _start_Y = _md.transform.position.y; _jumpElapsedTime = 0f; } void FixedUpdate() { _jumpElapsedTime += Time.fixedDeltaTime; // 匀减速运动位移公式:h(t) = v0*t - 0.5*g*t²,代入v0=2h/t、g=2h/t²推导得到 float targetHeight = (2 * jumpPickHeight / jumpPickTime) * _jumpElapsedTime - (jumpPickHeight / (jumpPickTime * jumpPickTime)) * _jumpElapsedTime * _jumpElapsedTime; // 限制高度不超过设定值,避免过冲 targetHeight = Mathf.Min(targetHeight, jumpPickHeight); // 直接设置角色Y位置 Vector3 newPos = _md.transform.position; newPos.y = _start_Y + targetHeight; _md.transform.position = newPos; // 到达跳跃时间后结束逻辑 if (_jumpElapsedTime >= jumpPickTime) { this.enabled = false; } }
内容的提问来源于stack exchange,提问作者SoMuch SMx
相关产品推荐
相关产品推荐

