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)

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Unity,可以通过以下步骤在世界坐标使用射线点击Canvas渲染模式为Screen space-Camera的UI: 1. 创建一个摄像机,将其设置为Canvas的渲染摄像机。 2. 将Canvas的渲染模式设置为Screen space-Camera,并将其渲染摄像机设置为步骤1创建的摄像机。 3. 在需要进行射线点击的对象上添加Collider组件,如Box Collider或Sphere Collider等。 4. 使用Camera.ScreenPointToRay()方法获取从摄像机发出的射线,并使用Physics.Raycast()方法检测射线是否与Collider相交。 5. 如果射线与Collider相交,则可以在代码执行所需的操作,例如触发UI的点击事件。 以下是示例代码: ``` public class RaycastUI : MonoBehaviour { public Camera camera; void Update() { if (Input.GetMouseButtonDown(0)) { Ray ray = camera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { if (hit.collider.gameObject.GetComponent<Button>()) { //执行按钮点击事件 hit.collider.gameObject.GetComponent<Button>().onClick.Invoke(); } } } } } ``` 在这个例子,我们创建了一个名为RaycastUI的脚本,并将其挂载在摄像机上。在Update()方法,我们使用GetMouseButtonDown(0)检测鼠标左键是否按下,并通过ScreenPointToRay()方法获取从摄像机发出的射线。然后使用Physics.Raycast()方法检测射线是否与Collider相交,如果相交并且该物体具有Button组件,则执行按钮的点击事件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

NRatel

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

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

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

打赏作者

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

抵扣说明:

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

余额充值