【Unity开发字典】简单实现Unity拖动物体代码简解

概要

本文会简单展示一个比较简单的拖动脚本, 适合和我一样的初学者进行大概的学习, 也可以在添加更多细节, 让脚本更有趣.

整体流程

这是个3d项目, 所以拖拽的时候, 要确定方块所在的深度

具体代码

using UnityEngine;

[RequireComponent(typeof(Rigidbody))] // 确保游戏对象有Rigidbody组件 
public class PhysicsDragController : MonoBehaviour
{
    
    
    // 参数配置区域 
    [Header("拖拽参数")]
    [Tooltip("拖拽平滑系数")] // 在Inspector面板显示的提示 
    public float lerpFactor = 10f; // 插值平滑系数 
    [Tooltip("最大拖拽距离")]
    public float maxDragDistance = 10f; // 最大拖拽距离限制 
    [Tooltip("启用物理模拟")]
    public bool usePhysics = true; // 是否使用物理系统 

    // 私有变量 
    private Vector3 _offset; // 鼠标点击点与物体中心的偏移量 
    private float _zCoord; // 物体在摄像机视野中的深度值 
    private Rigidbody _rb; // 物体的刚体组件引用 
    private Camera _mainCam; // 主摄像机引用 

    // 初始化方法 
    void Start()
    {
    
    
        _rb = GetComponent<Rigidbody>();
        _mainCam = Camera.main;  // 获取主摄像机 
        InitializePhysics(); // 初始化物理参数 
    }

    // 鼠标按下事件 
    void OnMouseDown()
    {
    
    
        CalculateInteractionPoint(); // 计算交互点 
        if (usePhysics) _rb.velocity = Vector3.zero;  // 如果使用物理,重置速度 
    }

    // 鼠标拖拽事件 
    void OnMouseDrag()
    {
    
    
        Vector3 targetPos = CalculateTargetPosition(); // 计算目标位置 
        ExecuteMovement(targetPos); // 执行移动 
    }

    // 核心算法模块 
    #region 核心算法模块 
    private void InitializePhysics()
    {
    
    
        _rb.interpolation = RigidbodyInterpolation.Interpolate; // 设置插值模式 
        _rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; // 设置碰撞检测模式 
    }

    private void CalculateInteractionPoint()
    {
    
    
        _zCoord = _mainCam.WorldToScreenPoint(transform.position).z;  // 获取物体深度 
        Vector3 mouseWorldPos = GetMouseWorldPosition(); // 获取鼠标世界坐标 

        _offset = transform.position - mouseWorldPos; // 计算偏移量 
    }

    private Vector3 GetMouseWorldPosition()
    {
    
    
        // 三维坐标重建(含深度值)
        Vector3 mousePos = Input.mousePosition;  // 获取鼠标屏幕坐标 
        mousePos.z = _zCoord; // 使用预存的物体深度 

        // 带空值检查的坐标转换 
        if (_mainCam != null)
        {
    
    
            return _mainCam.ScreenToWorldPoint(mousePos); // 转换为世界坐标 
        }
        else
        {
    
    
            Debug.LogError("主摄像机未初始化");
            return Vector3.zero;
        }
    }

    private Vector3 CalculateTargetPosition()
    {
    
    
        Vector3 mousePos = Input.mousePosition;  // 当前鼠标位置 
        mousePos.z = _zCoord; // 使用存储的深度值 
        Vector3 newPos = _mainCam.ScreenToWorldPoint(mousePos) + _offset; // 计算新位置 

        // 距离限制 
        Vector3 camToObj = newPos - _mainCam.transform.position;  // 摄像机到物体的向量 
        return _mainCam.transform.position +
               camToObj.normalized * Mathf.Min(camToObj.magnitude, maxDragDistance); // 限制最大距离 
    }
    #endregion

    // 运动执行策略 
    #region 运动执行策略 
    private void ExecuteMovement(Vector3 targetPos)
    {
    
    
        if (usePhysics) // 使用物理系统 
        {
    
    
            // 使用刚体的MovePosition进行平滑移动 
            _rb.MovePosition(Vector3.Lerp(
                _rb.position,
                targetPos,
                lerpFactor * Time.fixedDeltaTime));  // 基于物理时间的插值 
        }
        else // 不使用物理系统 
        {
    
    
            // 直接修改transform.position  
            transform.position = Vector3.Lerp(
                transform.position,
                targetPos,
                lerpFactor * Time.deltaTime);  // 基于帧时间的插值 
        }
    }
    #endregion 
}

代码简解

->Start()

void Start()
{
    
    
    _rb = GetComponent<Rigidbody>(); // 获取当前游戏对象的刚体组件 
    _mainCam = Camera.main;  // 获取场景中的主摄像机 
    InitializePhysics(); // 初始化物理参数 
}
  • 在游戏对象初始化时执行一次

关键点:
获取刚体组件是必须的,因为有[RequireComponent(typeof(Rigidbody))]属性;
Camera.main 是Unity提供的快捷方式,获取标记为"MainCamera"的摄像机;
初始化物理参数确保物理行为更平滑;

-> InitializePhysics()

private void InitializePhysics()
{
    
    
    _rb.interpolation  = RigidbodyInterpolation.Interpolate; // 设置插值模式 
    _rb.collisionDetectionMode  = CollisionDetectionMode.ContinuousDynamic; // 设置碰撞检测模式 
}
  • 这些设置可以防止快速移动的物体穿过其他碰撞体 使拖拽运动看起来更流畅

关键点:
Interpolate: 在物理更新之间插值,使运动更平滑
ContinuousDynamic: 连续动态碰撞检测,适合高速移动的物体

->OnMouseDown()

void OnMouseDown()
{
    
    
    CalculateInteractionPoint(); // 计算交互点 
    if (usePhysics) _rb.velocity  = Vector3.zero;  // 如果使用物理,重置速度 
}
  • 当鼠标在物体碰撞体上按下时触发

关键点:
计算鼠标点击点与物体中心的偏移量(_offset)
如果使用物理系统,重置物体速度,防止惯性影响拖拽

-> CalculateInteractionPoint()

private void CalculateInteractionPoint()
{
    
    
    _zCoord = _mainCam.WorldToScreenPoint(transform.position).z;  // 获取物体深度 
    Vector3 mouseWorldPos = GetMouseWorldPosition(); // 获取鼠标世界坐标 
 
    _offset = transform.position  - mouseWorldPos; // 计算偏移量 
}
  • 深度值(_zCoord):

将物体世界坐标转换为屏幕坐标,并提取z值(深度)
用于后续将鼠标2D坐标转换为正确的3D世界坐标

  • 偏移量(_offset):

记录鼠标点击点与物体中心的差值
确保拖拽时物体不会突然跳到鼠标位置

-> GetMouseWorldPosition()

private Vector3 GetMouseWorldPosition()
{
    
    
    Vector3 mousePos = Input.mousePosition;  // 获取鼠标屏幕坐标 
    mousePos.z = _zCoord; // 使用预存的物体深度 
 
    if (_mainCam != null)
    {
    
    
        return _mainCam.ScreenToWorldPoint(mousePos); // 转换为世界坐标 
    }
    else 
    {
    
    
        Debug.LogError("主摄像机未初始化");
        return Vector3.zero; 
    }
}

鼠标的Input.mousePosition 只有x,y值,z值为0
使用之前存储的_zCoord作为深度值
将屏幕坐标转换为世界坐标
检查主摄像机是否存在,防止空引用异常

-> CalculateTargetPosition()

private Vector3 CalculateTargetPosition()
{
    
    
    Vector3 mousePos = Input.mousePosition;  // 当前鼠标位置 
    mousePos.z = _zCoord; // 使用存储的深度值 
    Vector3 newPos = _mainCam.ScreenToWorldPoint(mousePos) + _offset; // 计算新位置 
 
    // 距离限制 
    Vector3 camToObj = newPos - _mainCam.transform.position;  // 摄像机到物体的向量 
    return _mainCam.transform.position  +
           camToObj.normalized  * Mathf.Min(camToObj.magnitude,  maxDragDistance); // 限制最大距离 
}
  • 获取当前鼠标位置
  • 添加存储的深度值
  • 计算从摄像机到物体的向量
  • 限制向量长度不超过maxDragDistance

距离限制:
防止物体被拖拽到无限远的地方
基于摄像机位置计算,确保物体不会超出指定范围

private void ExecuteMovement(Vector3 targetPos)
{
    
    
    if (usePhysics) // 使用物理系统 
    {
    
    
        _rb.MovePosition(Vector3.Lerp(
            _rb.position, 
            targetPos,
            lerpFactor * Time.fixedDeltaTime));  // 基于物理时间的插值 
    }
    else // 不使用物理系统 
    {
    
    
        transform.position  = Vector3.Lerp(
            transform.position, 
            targetPos,
            lerpFactor * Time.deltaTime);  // 基于帧时间的插值 
    }
}

最后一段就是移动啦, 请自行阅读理解 ;P

结尾

可以直接挂载在一个方块上边, 可以拖动使用
可以看另我一篇文章 , 写一个追踪这个小方块的方块, 拖着方块遛弯 :p
【Unity开发字典】简单跟踪效果初学者版代码简解
还可以给小方块加上松开后原路返回的时间回溯效果
【Unity开发字典】新手入门伪时间回溯效果
请添加图片描述