Unity拖拽(Drag&Drop)系统对象抖动问题修复方案咨询
Unity拖拽系统抖动问题的修复方案与思路
我开发了一套Unity平台的拖拽(Drag&Drop)系统,但在拖拽对象过程中发现对象存在明显抖动,严重影响用户交互体验,急需可行的修复方案与解决思路。以下是我的实现代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(Collider))] [RequireComponent(typeof(Rigidbody))] // 添加Rigidbody组件 public class DragDrop : MonoBehaviour { private Vector3 mouseOffset; private Camera mainCamera; private float zDistance; private Rigidbody rb; // Rigidbody引用 private void Awake() { mainCamera = Camera.main; rb = GetComponent<Rigidbody>(); // 获取Rigidbody rb.isKinematic = true; // 初始设置为运动学 } private void OnMouseDown() { // Z mesafesini tıklama anında belirliyoruz zDistance = Vector3.Distance(mainCamera.transform.position, transform.position); mouseOffset = Input.mousePosition - mainCamera.WorldToScreenPoint(transform.position); rb.isKinematic = true; // Sürükleme sırasında fizik motorunun müdahale etmemesi için kinematic yapıyoruz } private void OnMouseDrag() { Vector3 mousePosition = Input.mousePosition - mouseOffset; mousePosition.z = zDistance; // Z değerini ayarlıyoruz Vector3 worldMousePos = mainCamera.ScreenToWorldPoint(mousePosition); // Fiziksel hareketle taşımak için Rigidbody.MovePosition kullanıyoruz rb.MovePosition(worldMousePos); // Rotasyonu sabit tutmak için rb.rotation = Quaternion.Euler(0f, rb.rotation.eulerAngles.y, 0f); // Y ekseninde mevcut rotayı koruyoruz } private void OnMouseUp() { rb.isKinematic = false; // Sürükleme bittiğinde fizik motorunun devreye girmesini sağlıyoruz } private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Player")) { Physics.IgnoreCollision(collider, GetComponent<Collider>()); } } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { Physics.IgnoreCollision(other, GetComponent<Collider>()); } } }
抖动核心原因分析
- 更新频率不同步:
OnMouseDrag在Update帧执行(帧率不固定),而Rigidbody.MovePosition属于物理逻辑,依赖固定间隔的FixedUpdate执行,两者节奏不匹配导致位置跳变。 - 坐标转换误差:透视相机下固定Z轴距离的计算方式,会随相机视角/位置变化产生坐标偏差,引发抖动。
- 碰撞逻辑错误:原代码
OnCollisionEnter中Physics.IgnoreCollision(collider, GetComponent<Collider>())是忽略自身碰撞,完全无效,拖拽时多余的碰撞检测会干扰物理状态。 - 直接赋值冲突:在
OnMouseDrag中直接设置rb.rotation,绕开物理系统更新逻辑,容易出现帧间旋转跳变。
修复方案与优化代码
以下是整合所有修复点的优化代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(Collider))] [RequireComponent(typeof(Rigidbody))] public class DragDrop : MonoBehaviour { private Vector3 mouseOffset; private Camera mainCamera; private Rigidbody rb; private Vector3 targetPosition; // 缓存目标位置,解决更新不同步问题 private void Awake() { mainCamera = Camera.main; rb = GetComponent<Rigidbody>(); rb.isKinematic = true; targetPosition = transform.position; } private void OnMouseDown() { // 用屏幕点的Z值替代相机距离,提升透视相机下坐标转换精度 zDistance = mainCamera.WorldToScreenPoint(transform.position).z; mouseOffset = Input.mousePosition - mainCamera.WorldToScreenPoint(transform.position); rb.isKinematic = true; // 拖拽时临时关闭碰撞检测,彻底避免干扰 collider.enabled = false; } private void OnMouseDrag() { Vector3 mousePosition = Input.mousePosition - mouseOffset; mousePosition.z = zDistance; // 计算目标位置,固定Y轴高度(按需调整) targetPosition = mainCamera.ScreenToWorldPoint(mousePosition); targetPosition.y = transform.position.y; } private void FixedUpdate() { // 在物理帧内平滑更新位置,确保与物理系统同步 if (rb.isKinematic) { rb.MovePosition(Vector3.Lerp(transform.position, targetPosition, Time.fixedDeltaTime * 15f)); // 用物理API更新旋转,避免直接赋值冲突 rb.MoveRotation(Quaternion.Euler(0f, rb.rotation.eulerAngles.y, 0f)); } } private void OnMouseUp() { rb.isKinematic = false; collider.enabled = true; // 恢复碰撞检测 } private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Player")) { // 修正碰撞忽略逻辑:忽略当前对象与Player的碰撞 Physics.IgnoreCollision(collider, collision.collider); } } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { Physics.IgnoreCollision(other, collider); } } }
关键修复点说明
- 同步物理与输入:用
targetPosition缓存输入计算结果,在FixedUpdate内通过Vector3.Lerp平滑过渡到目标位置,消除帧间跳变。 - 优化坐标转换:使用屏幕点的Z值计算世界坐标,适配透视相机的视角变化,避免固定距离带来的偏差。
- 修复碰撞逻辑:修正
OnCollisionEnter的参数错误,拖拽时临时关闭碰撞检测,彻底避免物理干扰。 - 规范物理操作:用
Rigidbody.MoveRotation替代直接赋值旋转,确保操作在物理帧内执行,避免冲突。
内容的提问来源于stack exchange,提问作者Yasin Kardeş
相关产品推荐
相关产品推荐

