如何平滑计算非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 } }
问题分析与解决方案
你的抖动问题主要来自这几个点:
- 协程滥用:在
Update中每次调用StartCoroutine(CalculateVelocity())会生成大量并行协程,导致速度计算逻辑混乱,数值跳变。 - 速度计算逻辑错误:
Vector2.SmoothDamp的用法错误,它是用于平滑位置过渡而非计算速度;同时2D场景中错误使用了transform.position.z,应该取y轴。 - 旋转插值起点错误:旋转插值时误用了父物体的
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
相关产品推荐
相关产品推荐

