Unity物体在平面上根据鼠标位置进行拖拉操作的实现


  首先是场景构建

  然后分别给要被拖拉的物体与平面增添Tag或者是Layer(选哪个都可以,只要射线能够分层级检测出来就可以,不过地面我感觉最好用Layer)

  先给任意一个gameobject上一个脚本DragTest.cs

  1 public class DragTest : MonoBehaviour
  2 {
  3     private Camera mainCamera;
  4 
  5     public bool isDraggingModel;                //是否拖拉物体
  6     public bool isDraggingStart;                //拖拉是否是刚开始
  7     public Vector3 draggingModelPos;            //被拖拉物体实际三维坐标
  8     public Vector3 draggingStartPos;            //被拖拉物体被拖拉使脚下的地板坐标
  9     public Vector3 draggingLastPos;             //拖拉时,被拖拉物体上一时刻位置
 10     public Vector3 draggingFollowPos;           //被拖拉物体移动方向
 11     public Vector2 mouseModelOffset;            //鼠标与物体的偏移量
 12 
 13     private void Start()
 14     {
 15         mainCamera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent();
 16     }
 17 
 18     private void Update()
 19     {
 20         //鼠标事件监听
 21         if (Input.GetKeyDown(KeyCode.Mouse0))
 22         {
 23             OnMouseLeftDown();
 24         }
 25         if (Input.GetKeyUp(KeyCode.Mouse0))
 26         {
 27             OnMouseLeftUp();
 28         }
 29 
 30         draggingFollowPos = Vector3.zero;
 31         if (isDraggingModel)
 32         {
 33             //将偏移量加到鼠标位置上,确保物体移动起始点是在物体postion处
 34             Vector3 modelScreenPos = MousePercentPostion(Input.mousePosition) + mouseModelOffset;
 35             RaycastHit hit = RayCast(modelScreenPos, 1 << LayerMask.NameToLayer("CanWalk"));
 36             if(hit.collider != null)
 37             {
 38                 Vector3 hitPoint = hit.point;
 39                 //刚开始拖拉时,鼠标拖拉超过一段距离才会触发
 40                 if (!isDraggingStart)
 41                 {
 42                     draggingFollowPos = hitPoint - draggingLastPos;
 43                     draggingLastPos = hitPoint;
 44                 }
 45                 else
 46                 {
 47                     float distance = Vector3.Distance(hitPoint, draggingStartPos);
 48                     if (distance > 0.2f)
 49                     {
 50                         draggingFollowPos = hitPoint - draggingStartPos;
 51                         draggingLastPos = hitPoint;
 52                         isDraggingStart = false;
 53                     }
 54                 }
 55             }
 56 
 57         }
 58     }
 59 
 60     //当鼠标左键抬起的时候
 61     //结束拖拉
 62     private void OnMouseLeftUp()
 63     {
 64         isDraggingModel = false;
 65     }
 66 
 67     //当鼠标左键按下的时候
 68     private void OnMouseLeftDown()
 69     {
 70         RaycastHit hit = RayCast(MousePercentPostion(Input.mousePosition), 1);
 71 
 72         //如果摄像机-鼠标射线碰到想要拖动的物体
 73         //先记录鼠标位置与拖动物体在屏幕坐标占比上的偏移量
 74         //然后再发射一条摄像机-地板射线,将碰撞到的地板坐标设为拖动起始坐标
 75         if(hit.collider != null)
 76         {
 77             if (hit.collider.gameObject.tag == "Player")
 78             {
 79                 isDraggingModel = true;
 80                 draggingModelPos = hit.collider.gameObject.transform.position;
 81 
 82                 Vector2 modelScreenPos = mainCamera.WorldToScreenPoint(draggingModelPos);
 83                 modelScreenPos = new Vector2(modelScreenPos.x / Screen.width, modelScreenPos.y / Screen.height);
 84                 mouseModelOffset = modelScreenPos - MousePercentPostion(Input.mousePosition);
 85 
 86                 RaycastHit groundHit = RayCast(modelScreenPos, 1 << LayerMask.NameToLayer("CanWalk"));
 87                 if(groundHit.collider != null)
 88                 {
 89                     draggingStartPos = groundHit.point;
 90                     isDraggingStart = true;
 91                 }
 92             }
 93         }
 94     }
 95 
 96     /// 
 97     /// 根据鼠标在屏幕上的位置计算鼠标在屏幕的xy坐标百分比
 98     /// 
 99     ///  当前鼠标的屏幕二维坐标 
100     ///  百分比结果 
101     private Vector2 MousePercentPostion(Vector3 curMousePos)
102     {
103         Vector3 tempPos = curMousePos;
104         Vector2 mousePos = new Vector2(tempPos.x / Screen.width, tempPos.y / Screen.height);
105 
106         return mousePos;
107     }
108 
109     /// 
110     /// 根据屏幕百分比坐标来判断摄像机射线有无碰撞到collider
111     /// 
112     ///  屏幕百分比坐标 
113     ///  LayerMask 
114     /// 
115     private RaycastHit RayCast(Vector2 screenPos, int layerMask)
116     {
117         RaycastHit hit;
118         Vector3 rayDir = new Vector3(0, 0, 1);
119         float clippingPlaneHeight = 2.0f * rayDir.z * Mathf.Tan(Mathf.Deg2Rad * mainCamera.fieldOfView / 2.0f);
120         float clippingPlaneWidth = mainCamera.aspect * clippingPlaneHeight;
121 
122         rayDir.x = (screenPos.x - 0.5f) * clippingPlaneWidth;
123         rayDir.y = (screenPos.y - 0.5f) * clippingPlaneHeight;
124         rayDir = mainCamera.transform.TransformVector(rayDir);
125         if (layerMask != -1)
126             Physics.Raycast(mainCamera.transform.position, rayDir, out hit, Mathf.Infinity, layerMask);
127         else
128             Physics.Raycast(mainCamera.transform.position, rayDir, out hit);
129 
130         return hit;
131     }
132 }

  主要思路就是通过鼠标在屏幕上的坐标屏幕分辨率占比来判断鼠标位置,这样可以避免分辨率的影响。

  鼠标屏幕位置确定后,通过射线进行碰撞检测,碰撞到物体则继续发射一条针对地面平面的射线(LayerMask)来找到被拖拉物体的起始地面坐标,然后给一个状态以便在Update里计算鼠标接下来(按住不放)的每帧移动方向。为了防止误触,可以设置一个鼠标拉动一定距离后才开始拖动的功能,距离达到要求后,计算摄像机-地面(鼠标射线)当前坐标与上一帧物体坐标的方向向量,将这个向量交给被拖拉物体实现位移。

  被拖拉物体的脚本

 1 public class Cube : MonoBehaviour
 2 {
 3     private DragTest dragTest;
 4 
 5     private void Start()
 6     {
 7         dragTest = GameObject.Find("Plane").GetComponent();
 8     }
 9 
10     void Update()
11     {
12         if(dragTest != null)
13         {
14             if (dragTest.isDraggingModel)
15             {
16                 transform.position += dragTest.draggingFollowPos;
17             }
18         }
19     }
20 
21     
22 }

  最终效果