Unity 中 Screen坐标 转 Canvas坐标

一个常见的需求: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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NRatel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值