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

如何让通过Velocity控制的刚体车辆平稳上下斜坡?

问题分析与解决

为什么直接设置velocity爬坡会出问题

你现在直接给rigidbody.velocity = transform.forward * speed,相当于强制让刚体只沿着自身车头的局部Z轴方向运动,完全忽略了物理引擎的自然响应。当车辆驶上斜坡时,地面的碰撞力会抵消部分速度,而且你没有给刚体留出适应斜坡的速度分量空间——本该向上的速度被你强制改成了水平(相对于自身),自然爬不动或者方向乱掉。

这种场景该不该用velocity?

不推荐直接设置velocity。直接赋值会覆盖刚体的物理状态(比如碰撞后的速度变化、重力影响),适合那种完全不需要物理交互的“瞬移式”移动,但你的场景需要车辆和斜坡、地面有真实的物理交互,所以应该用物理引擎友好的方式驱动。

具体修改方案

1. 改用AddForce驱动移动,贴合物理规则

用AddForce给刚体施加沿地面平面的前进力,而不是直接设置速度。同时需要检测地面的法线,把前进方向约束在地面平面上,避免车辆浮空或者卡斜坡。

2. 用Rigidbody.MoveRotation代替直接赋值旋转

直接设置rigidbody.rotation会跳过物理引擎的旋转插值,容易出现卡顿或者和物理冲突,改用MoveRotation让旋转更平滑,符合物理逻辑。

3. 优化地面检测

原来的CheckBox检测范围太大,建议用射线检测地面,更精准地获取地面法线和判断是否在地面上。

修改后的示例代码

using UnityEngine;

public class Movement : MonoBehaviour
{
    [SerializeField] private GameObject followCamera;
    [SerializeField] private AnimationCurve lerpCurve;
    [SerializeField] private float turnSpeed;
    [SerializeField] private float moveForce = 500f; // 改成力的参数,替代直接速度赋值
    [SerializeField] private float maxSpeed = 15f; // 限制最大速度,避免无限加速

    private static float turnTimer = 2f;
    private float turnTimeElapsed;
    private float clampedTimeElapsed;
    private float lerpTimer;

    Rigidbody ridgy;
    LayerMask groundLayer;

    private void Start()
    {
        groundLayer = LayerMask.GetMask("Ground");
        ridgy = GetComponent<Rigidbody>();
        turnTimeElapsed = 0f;
    }

    private void Update()
    {
        // 保留原有的转向逻辑
        if (Input.GetKey(KeyCode.Space) && turnTimeElapsed < turnTimer)
        {
            turnTimeElapsed += turnSpeed * Time.deltaTime;
        }
        else if (!Input.GetKey(KeyCode.Space) && turnTimeElapsed > 0)
        {
            turnTimeElapsed -= turnSpeed * Time.deltaTime;
        }

        clampedTimeElapsed = Mathf.Clamp(turnTimeElapsed, 0f, turnTimer);
        lerpTimer = clampedTimeElapsed / turnTimer;
        lerpTimer = lerpCurve.Evaluate(lerpTimer);
    }

    private void FixedUpdate()
    {
        // 用射线检测地面,精准获取地面法线
        bool isGrounded = Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, 1f, groundLayer);
        
        if (isGrounded)
        {
            followCamera.GetComponent<CameraFollow>().enabled = true; // 回到地面时重启相机跟随

            // 计算平行于地面的前进方向,避免垂直地面的分量
            Vector3 forward = Vector3.ProjectOnPlane(transform.forward, hit.normal).normalized;

            // 保留原有的转向状态对应速度逻辑
            if (lerpTimer > 0f && lerpTimer < .75f)
            {
                maxSpeed = 45f;
            }
            else
            {
                maxSpeed = 15f;
            }

            // 限制地面平面内的速度,防止无限加速
            Vector3 currentGroundVelocity = Vector3.ProjectOnPlane(ridgy.velocity, hit.normal);
            if (currentGroundVelocity.magnitude < maxSpeed)
            {
                ridgy.AddForce(forward * moveForce * Time.fixedDeltaTime, ForceMode.Force);
            }

            // 平滑转向,避免物理冲突
            Quaternion targetRot = Quaternion.Euler(0f, lerpTimer * 90f, 0f);
            ridgy.MoveRotation(Quaternion.Lerp(ridgy.rotation, targetRot, turnSpeed * Time.fixedDeltaTime));
        }
        else
        {
            followCamera.GetComponent<CameraFollow>().enabled = false;
        }
    }
}

关键修改点说明

  • 改用AddForce:让物理引擎自动处理斜坡上的速度分量分配,车辆会自然获得沿斜坡向上的速度,不会被强制卡方向。
  • Vector3.ProjectOnPlane:把前进方向约束在地面平面上,确保车辆始终贴合地面移动,不会出现浮空或穿模问题。
  • 速度限制:避免车辆因持续受力无限加速,保留了你原有的不同转向状态下的速度差异逻辑。
  • 平滑旋转:用MoveRotation结合插值实现自然转向,不会和物理引擎的旋转计算冲突。

内容的提问来源于stack exchange,提问作者Incoherent Thoughts

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:30:56