Unity中UGUI控件和3D物體拖拽實作
基本原理
Unity拖拽的基本原理:射線檢測,滑鼠位置增量轉換為統一空間的位置增量,将位置增量添加到拖拽物體原位置上。
統一空間指的是将所有向量轉換為同一空間下再進行計算。
項目示範
左測:UGUI Button
中間:UGUI Image
右側:3D物體
UGUI拖拽實作
方式有兩種:其一直接繼承拖拽三個接口IBeginDragHandler,IDragHandler,IEndDragHandler,重寫内部函數。 其二通過EventSystem實作。
其一:腳本繼承了拖拽三個接口IBeginDragHandler,IDragHandler,IEndDragHandler直接上代碼,在開始拖拽的函數中初始化拖拽物和滑鼠的位置,在拖拽過程中,不斷的将滑鼠的位置增量轉換到畫布空間,并附加給拖拽物。代碼如下(項目示範中中間image是用此種方法拖拽):
public class DragTest : MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler
{
private Vector3 pos; //控件初始位置
private Vector2 mousePos; //滑鼠初始位置(畫布空間)
private Vector3 mouseWorldPos; //滑鼠初始位置(世界空間)
private RectTransform canvasRec; //控件所在畫布
private void Start()
canvasRec = this.GetComponentInParent().transform as RectTransform;
}
//開始拖拽
public void OnBeginDrag(PointerEventData eventData)
//控件所在畫布空間的初始位置
pos = this.GetComponent().anchoredPosition;
Camera camera = eventData.pressEventCamera;
//将螢幕空間滑鼠位置eventData.position轉換為滑鼠在畫布空間的滑鼠位置
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRec, eventData.position, camera, out mousePos);
//拖拽過程中
public void OnDrag(PointerEventData eventData)
Vector2 newVec = new Vector2();
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRec, eventData.position, camera, out newVec);
//滑鼠移動在畫布空間的位置增量
Vector3 offset = new Vector3(newVec.x - mousePos.x, newVec.y - mousePos.y, 0);
//原始位置增加位置增量即為現在位置
(this.transform as RectTransform).anchoredPosition = pos + offset;
//結束拖拽(此處沒做任何處理,可自行拓展)
public void OnEndDrag(PointerEventData eventData)
}
當然也可以轉換到世界空間進行計算,相關代碼如下:
//開始拖拽函數
//控件的世界坐标初始位置
pos = this.transform.position;
//将螢幕空間滑鼠位置eventData.position轉換為滑鼠在世界空間的滑鼠位置
RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRec, eventData.position, camera, out mouseWorldPos);
//拖拽中函數
Vector3 newVec = new Vector3();
RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRec, eventData.position, camera, out newVec);
//滑鼠移動在世界空間的位置增量
Vector3 offset = newVec - mouseWorldPos;
this.transform.position = pos + offset;
其二通過EventSystem實作:控件添加EventTrigger元件,在代碼中EventTrigger添加EventTriggerType.BeginDrag,EventTriggerType.Drag,EventTriggerType.EndDrag事件,并給各事件綁定函數,左側的button就是用這種方式實作的,代碼如下(其實核心子產品的邏輯與上面方法無異):
public class EventSystemDrag : MonoBehaviour {
public Camera theCamera; //UI錄影機
public RectTransform canvas; //控件所在畫布
private EventTrigger trigger; //事件觸發元件
Vector3 mouseOriPos; //滑鼠原始位置(世界空間)
Vector3 myOriPos; //控件原始位置(世界空間)
// Use this for initialization
void Start () {
trigger = this.GetComponent();
//事件觸發器添加開始拖拽事件并添加開始拖拽函數
EventTrigger.Entry entry2 = new EventTrigger.Entry();
entry2.eventID = EventTriggerType.BeginDrag;
entry2.callback = new EventTrigger.TriggerEvent();
entry2.callback.AddListener((eventData) => { BeginDrag(eventData as PointerEventData); });
trigger.triggers.Add(entry2);
//事件觸發器添加拖拽事件并添加拖拽函數
EventTrigger.Entry entry3 = new EventTrigger.Entry();
entry3.eventID = EventTriggerType.Drag;
entry3.callback = new EventTrigger.TriggerEvent();
entry3.callback.AddListener((eventData) => { OnDrag(eventData as PointerEventData); });
trigger.triggers.Add(entry3);
//事件觸發器添加拖拽結束事件并添加拖拽結束函數
EventTrigger.Entry entry4 = new EventTrigger.Entry();
entry4.eventID = EventTriggerType.EndDrag;
entry4.callback = new EventTrigger.TriggerEvent();
entry4.callback.AddListener((eventData) => { EndDrag(eventData as PointerEventData); });
trigger.triggers.Add(entry4);
public void BeginDrag(PointerEventData eventData)
Vector2 vec = eventData.position;
RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas, vec, theCamera,out mouseOriPos);
myOriPos = this.transform.position;
void OnDrag(PointerEventData eventData)
RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas, vec, theCamera, out newVec);
this.transform.position = myOriPos + newVec - mouseOriPos;
void EndDrag(PointerEventData eventData)
或者可以直接在Unity編輯器中添加事件和綁定函數,效果是一樣的,如圖:
111.png
3D物體拖拽
由于UI拖拽,關于射線部分,Unity底層已經封裝好了接口,我們隻用實作響應的接口即可。但是3D物體,需要我們自己寫代碼實作。
項目示範中右側小球的部分屬性如下圖(設定了Tag,友善射線檢測,小球必須添加碰撞體元件,否則射線無法檢測到):
qiu.png
首先我們實作射線檢測部分,代碼如下:
//按下左鍵開始發出射線
if (Input.GetMouseButtonDown(0))
//射線由主錄影機發出,射向螢幕點選的點
Ray ray = theCamera.ScreenPointToRay(Input.mousePosition);
//射線撞擊點
RaycastHit hit;
//如果射線撞擊到碰撞體,且碰撞體的标簽是我們設定需要拖拽的物體,那麼進行主邏輯
if (Physics.Raycast(ray, out hit))
if (hit.collider.tag == "Drag")
//記錄下目前滑鼠位置
mousePos = Input.mousePosition;
isDrag = true;
go = hit.collider.gameObject;
//記錄下拖拽物的原始螢幕空間位置
oriScreenPos = theCamera.WorldToScreenPoint(go.transform.position);
接着是移動的邏輯:
//左鍵一直處于按下狀态,即為拖拽過程
if (Input.GetMouseButton(0))
//如果拖拽狀态處于true,且有拖拽物
if (isDrag&& go)
//擷取螢幕空間滑鼠增量,并加上拖拽物原始位置(螢幕空間計算)
Vector3 newPos = oriScreenPos + Input.mousePosition - mousePos;
//将螢幕空間坐标轉換為世界空間
Vector3 newWorldPos = theCamera.ScreenToWorldPoint(newPos);
//将世界空間位置賦予拖拽物
go.transform.position = newWorldPos;
移動結束,還原拖拽狀态:
//松開左鍵
if (Input.GetMouseButtonUp(0))
isDrag = false;
go = null;
本文使用的螢幕空間計算,當然使用其他空間也是可以的,比如世界空間,但要注意坐标Z軸的處理。原因如下:世界空間坐标是三維向量(世界空間),而滑鼠點選螢幕的坐标(螢幕空間),其實為二維向量,z方向為0值。那麼拖拽中實際上拖拽物隻有x,y值具有增量,而z值不變。或者開發者也可以根據自己的需求來修改z值。
更多unity2018的功能介紹請到paws3d爪爪學院查找。