天天看點

Unity全方位拖拽物體攻略

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爪爪學院查找。