一个常见的需求:UI上的物体跟随鼠标移动。
必须要进行坐标转换的原因是,
1、设备实际的 “屏幕宽高 ” 和 “Canvas宽高” 不同,不能直接使用 “屏幕宽高” 参与计算。
2、设备实际的 “屏幕宽高比” 和 “Canvas宽高比” 不同,使得在一些屏幕适配方案下,“Canvas宽高” 不等于 “设计分辨率的宽高” ,所以也不能直接使用 “设计分辨率的宽高” 参与计算。
---------------------------------------------------------NRatel割---------------------------------------------------------
已知:鼠标位置 Input.mousePosition 是在 Screen坐标系下,
Screen坐标系,以屏幕左下角为原点,向右为X轴正方向,向上为Y轴正方向;
Canvas坐标系,以屏幕中心为原点,向右为X轴正方向,向上为Y轴正方向。
---------------------------------------------------------NRatel割---------------------------------------------------------
转换方式一:
自行推算,
RectTransform canvasRT = GameObject.Find("Canvas").GetComponent<RectTransform>();
//先缩放,即,先保持原点不变,让坐标从屏幕坐标系到Canvas坐标系缩放
float cX = Input.mousePosition.x / Screen.width * canvasRT.rect.width;
float cY = Input.mousePosition.y / Screen.height * canvasRT.rect.height;
//后平移,即,将原点向右上平移 1/2的Canvas宽高
float pX = cX - canvasRT.rect.width / 2;
float pY = cY - canvasRT.rect.height / 2;
转换方式二:
使用Unity API, 将屏幕坐标转换到某一个RectTransform下
Vector2 p;
RectTransform canvasRT = GameObject.Find("Canvas").GetComponent<RectTransform>();
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRT, Input.mousePosition, Camera.main, out p);
源码分析(摘选版本 Unity 2020.1.0a11 ):
//将 Screen坐标 转为 RectTransform平面上的 Local坐标。
public static bool ScreenPointToLocalPointInRectangle(RectTransform rect, Vector2 screenPoint, Camera cam, out Vector2 localPoint)
{
localPoint = Vector2.zero;
Vector3 worldPoint;
//先将 Screen坐标 转为 rect平面上的 World坐标。
if (ScreenPointToWorldPointInRectangle(rect, screenPoint, cam, out worldPoint))
{
//再将 World坐标 转为rect平面上的 Local坐标
localPoint = rect.InverseTransformPoint(worldPoint);
return true;
}
return false;
}
//将 Screen坐标 转为 RectTransform平面上的 World坐标。
public static bool ScreenPointToWorldPointInRectangle(RectTransform rect, Vector2 screenPoint, Camera cam, out Vector3 worldPoint)
{
worldPoint = Vector2.zero;
//获取相机到屏幕点的射线
Ray ray = ScreenPointToRay(cam, screenPoint);
//根据法线和中心点,创建 rect所在平面
//疑问!为什么rect.rotation * Vector3.back 代表法线?
//试答(待确认):UGUI中 Vector3.back 本身就是法线,如果面板发生过旋转,让法线也进行同样的旋转。
var plane = new Plane(rect.rotation * Vector3.back, rect.position);
//射线源 与 射线和平面的交点 的距离
float dist;
if (!plane.Raycast(ray, out dist))
return false;
//根据 射线源坐标、射线方向、沿着射线方向的距离,推算出 交点的World坐标
//m_Origin + m_Direction * distance
worldPoint = ray.GetPoint(dist);
return true;
}
//相机到屏幕点的射线
public static Ray ScreenPointToRay(Camera cam, Vector2 screenPos)
{
if (cam != null)
return cam.ScreenPointToRay(screenPos);
//若无相机,
//默认为射线源与屏幕坐标XY相同,Z向后100。
Vector3 pos = screenPos;
pos.z -= 100f;
//默认射线方向为Vector3.forward。
return new Ray(pos, Vector3.forward);
}
---------------------------------------------------------NRatel割---------------------------------------------------------
补充一个需求:拖拽时,被拖拽物体跟着鼠标移动,但要保持鼠标按下时鼠标与被拖拽物体的相对位置。
即:按着脚拖 或 按着头拖。。
需要在拖拽时执行:被拖拽物体的本地坐标 + new Vector2(eventData.delta.x, eventData.delta.y) 转到 被拖拽物体所在父物体的坐标系下 的坐标。。
上面已经说过一个坐标点怎么转换,那向量怎么转到UI坐标系下?
其实转两个点 new Vector2(0, 0) 和 new Vector2(eventData.delta.x, eventData.delta.y) 减一下就行了。
Vector2 pZero;
Vector2 pDelta;
RectTransform canvasRT = GameObject.Find("Canvas").GetComponent<RectTransform>();
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRT, Input.mousePosition, Camera.main, out pZero);
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRT, Input.mousePosition, Camera.main, out pDelta);
Vector2 localDelta = pDelta - pZero;
---------------------------------------------------------NRatel割---------------------------------------------------------
补充:localPosition 和 anchoredPosition 互转。(实际可能不应该有这样的需求,因为这样计算后,若屏幕比例发生变化会出问题,相当于使屏幕自适应逻辑失效了)
1、localPosition:自身中心点相对父物体中心点的位移。
2、anchoredPosition:自身中心点相对自身在父物体上的锚点(四角平均后)的位移。
故,只要求出:“父物体中心点与” 与 “自身在父物体上的锚点” 的位移差即可。
这个位移差为:(parentWidth * childAnchorX, parentHeight * childAnchorY)