关于在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;
}
}
}
最终效果如下