關于在Unity中拖動三維物體的實作
目錄
文章目錄
- 關于在Unity中拖動三維物體的實作
-
- 目錄
- 問題
- 錯誤的解決方案
- 正确的解決方案
問題
在遊戲制作過程中常常我們需要做類似平移物體的操作,但是應該如何實作這個效果呢?
錯誤的解決方案
在百度大概搜尋了下發現一種解決方案,但是這種方案存在着嚴重的問題或者說局限性.
其方案如下:
- 擷取滑鼠點選的螢幕坐标.
- 然後根據螢幕的點選坐标發射一條射線獲得點選物體(設物體名為A)的世界坐标.
- 根據物體A的世界坐标獲得物體A的螢幕坐标.
- 當滑鼠拖動後獲得滑鼠新的螢幕點選坐标.
- 将新的滑鼠螢幕坐标的Z軸值改成物體A的螢幕坐标的Z軸值.
- 再将修改後的螢幕坐标轉化成世界坐标便是物體A應該移動到的坐标.
此方案有個很嚴重的問題,此方案當且僅當适用于相機與地面平行的情況.
其原理如圖

這在相機與地面平行的情況下完全沒有問題,但是如果我們我們的相機與平面不平行呢?
那麼結果會像這樣
實際上應該在的位置和計算的位置完全不一緻,采用這種方式當我們向上移動時,物體會飛起來,當我們向下移動時物體會沉入地下.
正确的解決方案
那麼如果我們需要移動物體最合理的方式是什麼呢?
其實我們可以計算我們滑鼠的點選射線到到物體中心點與地面平面平行的平面的交點.其擷取方法如下
public static Vector3 GetPlaneInteractivePoint(Vector3 screenPosition, float plane = 0, Camera camera = null)
{
if (camera == null)
{
camera = Camera.main;
}
var ray = camera.ScreenPointToRay(screenPosition);
Vector3 dir = ray.direction;
if (dir.y.Equals(0)) return Vector3.zero;
float num = (plane - ray.origin.y) / dir.y;
return ray.origin + ray.direction * num;
}
其中參數的plane表示Unity世界坐标系中y軸的坐标,即表示一個與地面平行的平面,plane值表示平面高度(需要注意的是此方法僅适用于地面與Unity世界坐标系的y軸垂直,若不垂直,便不可用此方法計算.比如傾斜的平面的物體移動是不能采用這種方式的).
根據上面的方法我們就可以根據滑鼠射線來獲得物體移動後的坐标.
貼下我寫的移動地圖和移動物體的部分代碼.
private GameObject mSelectedBuilding;
private GameObject mSelectedMap;
private Vector3 mScreenDownPosition;
private Vector3 mWorldDownPosition;
private Vector3 mCameraDownPosition;
private void Update()
{
if (mSelectedBuilding)
{
var down = Vector3Utils.GetPlaneInteractivePoint(mScreenDownPosition,
mSelectedBuilding.transform.position.y);
var move = Vector3Utils.GetPlaneInteractivePoint(Input.mousePosition,
mSelectedBuilding.transform.position.y);
var offset = move - down;
mSelectedBuilding.transform.position = mWorldDownPosition + offset;
}
else if (mSelectedMap)
{
var down = Vector3Utils.GetPlaneInteractivePoint(mScreenDownPosition,
mSelectedMap.transform.position.y);
var move = Vector3Utils.GetPlaneInteractivePoint(Input.mousePosition,
mSelectedMap.transform.position.y);
var offset = move - down;
Camera.main.transform.position = mCameraDownPosition - offset;
}
if (Input.GetMouseButtonDown(0) && !mSelectedBuilding && !mSelectedMap)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
string tag = hit.collider.gameObject.tag;
switch (tag)
{
case TagDefine.BUILDING:
mSelectedBuilding = hit.collider.gameObject;
break;
case TagDefine.MAP:
mSelectedMap = hit.collider.gameObject;
break;
}
mScreenDownPosition = Input.mousePosition;
mWorldDownPosition = hit.collider.gameObject.transform.position;
mCameraDownPosition = Camera.main.transform.position;
}
}
else if (Input.GetMouseButtonUp(0))
{
mSelectedBuilding = null;
mSelectedMap = null;
}
}
}
最終效果如下