概要
本文会简单展示一个比较简单的拖动脚本, 适合和我一样的初学者进行大概的学习, 也可以在添加更多细节, 让脚本更有趣.
整体流程
这是个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开发字典】新手入门伪时间回溯效果