You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

问题根源

  1. Update()高度波动原因:Update()的调用帧率不固定,Time.deltaTime每次取值不同,导致速度累加的步长不一致,最终跳跃高度出现随机波动。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:54:51