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

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>());
        }
    }
}

抖动核心原因分析

  1. 更新频率不同步:OnMouseDrag在Update帧执行(帧率不固定),而Rigidbody.MovePosition属于物理逻辑,依赖固定间隔的FixedUpdate执行,两者节奏不匹配导致位置跳变。
  2. 坐标转换误差:透视相机下固定Z轴距离的计算方式,会随相机视角/位置变化产生坐标偏差,引发抖动。
  3. 碰撞逻辑错误:原代码OnCollisionEnter中Physics.IgnoreCollision(collider, GetComponent<Collider>())是忽略自身碰撞,完全无效,拖拽时多余的碰撞检测会干扰物理状态。
  4. 直接赋值冲突:在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ş

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:27:06