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

如何平滑计算非Rigidbody对象速度并实现平滑旋转?

平滑计算非Rigidbody对象速度以解决车辆旋转抖动问题

我开发了一款简易卷轴赛车游戏,通过手指拖拽控制车辆移动,希望车辆移动时能左右旋转。此前通过直接修改transform实现了旋转,但效果存在抖动,排查后发现问题源于速度计算环节。尝试过多种平滑速度计算方法,但始终存在抖动问题。请问有没有办法平滑计算非Rigidbody对象的速度?

现有代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerTouchControls : MonoBehaviour
{
    float deltaX, deltaY;
    float targetX, targetY;
    Rigidbody2D rigidbody;
    float velocityH;
    [SerializeField] Vector2 velocity;

    [Header("Speed")]
    [SerializeField] float speedCap;
    [Header("Sway")]
    [SerializeField] float swayMultiplier;
    [SerializeField] float swaySmoothness;
    [SerializeField] float velocitySmoothness;
    [SerializeField] float velocitySpeed;

    private void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        Physics.IgnoreLayerCollision(3, 7, true);
    }

    private void Update()
    {
        TouchMovement();
        StartCoroutine(CalculateVelocity());
    }
    private void FixedUpdate()
    {
        Sway();
        
    }

    void TouchMovement()
    {
        if (Input.touchCount > 0)
        {
            
            Touch touch = Input.GetTouch(0);
            Vector2 touchPos = Camera.main.ScreenToWorldPoint(touch.position);

            switch (touch.phase)
            {
                case TouchPhase.Began:
                    deltaX = touchPos.x - transform.position.x;
                    deltaY = touchPos.y - transform.position.y;
                    break;

                case TouchPhase.Moved:
                    targetX = touchPos.x - deltaX;
                    targetY = touchPos.y - deltaY;
                    Vector2 targetPos = new Vector2(targetX, targetY);

                    rigidbody.MovePosition(Vector2.MoveTowards(transform.position, targetPos, speedCap));
                    break;

                case TouchPhase.Ended:
                    rigidbody.velocity = Vector2.zero;
                    break;
            }
        }
    }
    IEnumerator CalculateVelocity()
    {
        Vector2 lastPos = new Vector2(transform.position.x, transform.position.z);
        yield return new WaitForFixedUpdate();
        Vector2 vel = Vector2.SmoothDamp(lastPos, transform.position, ref velocity, velocitySmoothness, velocitySpeed, Time.deltaTime);

        velocityH = vel.x;
    }
    void Sway()
    {
        float movementX = velocityH * swayMultiplier;

        Quaternion rotationZ = Quaternion.AngleAxis(movementX, Vector3.forward);

        Quaternion targetRotation = rotationZ;

        transform.GetChild(0).localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, swaySmoothness * Time.fixedDeltaTime);
    }

    private void OnDrawGizmos()
    {
        #region Target Gizmo
        Vector3 targetPos = new Vector3(targetX, targetY, 0);
        
        Gizmos.color = Color.blue;
        Gizmos.DrawSphere(targetPos, 0.05f);
        #endregion
    }
}

问题分析与解决方案

你的抖动问题主要来自这几个点:

  1. 协程滥用:在Update中每次调用StartCoroutine(CalculateVelocity())会生成大量并行协程,导致速度计算逻辑混乱,数值跳变。
  2. 速度计算逻辑错误:Vector2.SmoothDamp的用法错误,它是用于平滑位置过渡而非计算速度;同时2D场景中错误使用了transform.position.z,应该取y轴。
  3. 旋转插值起点错误:旋转插值时误用了父物体的transform.localRotation作为起点,应该基于子物体当前的旋转状态进行插值。

优化后的代码

using UnityEngine;

public class PlayerTouchControls : MonoBehaviour
{
    float deltaX, deltaY;
    float targetX, targetY;
    Rigidbody2D rigidbody;
    // 缓存上一帧位置
    Vector2 lastPosition;
    // 平滑后的水平速度
    float smoothedVelocityH;
    [SerializeField] Vector2 currentVelocity;

    [Header("Speed")]
    [SerializeField] float speedCap;
    [Header("Sway")]
    [SerializeField] float swayMultiplier;
    [SerializeField] float swaySmoothness;
    [SerializeField] float velocitySmoothTime = 0.1f;

    private void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        Physics.IgnoreLayerCollision(3, 7, true);
        // 初始化上一位置
        lastPosition = transform.position;
    }

    private void Update()
    {
        TouchMovement();
    }

    private void FixedUpdate()
    {
        CalculateVelocity();
        Sway();
    }

    void TouchMovement()
    {
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);
            Vector2 touchPos = Camera.main.ScreenToWorldPoint(touch.position);

            switch (touch.phase)
            {
                case TouchPhase.Began:
                    deltaX = touchPos.x - transform.position.x;
                    deltaY = touchPos.y - transform.position.y;
                    break;

                case TouchPhase.Moved:
                    targetX = touchPos.x - deltaX;
                    targetY = touchPos.y - deltaY;
                    Vector2 targetPos = new Vector2(targetX, targetY);

                    rigidbody.MovePosition(Vector2.MoveTowards(transform.position, targetPos, speedCap * Time.fixedDeltaTime));
                    break;

                case TouchPhase.Ended:
                    rigidbody.velocity = Vector2.zero;
                    smoothedVelocityH = 0;
                    break;
            }
        }
    }

    void CalculateVelocity()
    {
        // 计算原始速度:(当前位置 - 上一位置) / 固定时间步长
        Vector2 rawVelocity = (transform.position - lastPosition) / Time.fixedDeltaTime;
        // 平滑水平速度
        smoothedVelocityH = Mathf.SmoothDamp(smoothedVelocityH, rawVelocity.x, ref currentVelocity.x, velocitySmoothTime);
        // 更新上一位置
        lastPosition = transform.position;
    }

    void Sway()
    {
        float targetRotationAngle = smoothedVelocityH * swayMultiplier;
        Quaternion targetRotation = Quaternion.AngleAxis(targetRotationAngle, Vector3.forward);
        
        // 基于子物体当前旋转进行平滑插值
        Transform carModel = transform.GetChild(0);
        carModel.localRotation = Quaternion.Slerp(carModel.localRotation, targetRotation, swaySmoothness * Time.fixedDeltaTime);
    }

    private void OnDrawGizmos()
    {
        Vector3 targetPos = new Vector3(targetX, targetY, 0);
        Gizmos.color = Color.blue;
        Gizmos.DrawSphere(targetPos, 0.05f);
    }
}

关键优化点说明

  • 移除协程:直接在FixedUpdate中计算速度,避免多协程冲突,保证计算频率和物理更新同步。
  • 正确计算速度:通过位置差除以固定时间步长得到原始速度,再用Mathf.SmoothDamp单独平滑水平速度分量,避免数值跳变。
  • 修正旋转插值:以子物体当前的localRotation作为插值起点,确保旋转过渡自然平滑。
  • 时间步长修正:MoveTowards的步长乘以Time.fixedDeltaTime,保证不同帧率下移动速度一致。

如果是纯非Rigidbody对象(不用物理组件),只需把rigidbody.MovePosition替换为直接修改transform.position,速度计算逻辑保持不变即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:24:54