天天看点

UGUI-RectTransform的位置研究

在进行使用 UGUI 进行时,经常会遇到需要在代码中控制 UI 的位置的情况。

比如,我们要实现一个通用的TIPS显示:

将显示 TIPS 的控件假设是一个简单的文本框 label,放到一个专用的 Canvas A 上,并且,为了始终显示在最前面,我们将 Canvas 的 Render Mode 设置为:Screen Space - Overlay

在另一个 Canvas B 上,创建一个图片控件 image,该 Canvas 的 Render Mode 为常用的 Screen Space - Camera。现在我们要实现的是,点击该图片,弹出对该图片的说明

所以我们响应图片点击,显示 Canvas A 上的文本,同时 Canvas A 上的文本位置,要和 Canvas B 上的图片位置一致,这并不复杂,我们要做的是:

  1. 将图片位置转换到屏幕空间
  2. 对屏幕控件位置进行操作,比如我们需要加点偏移
  3. 将新的位置转换到屏幕空间,并赋值给文本

代码:

Camera uiCamera = GetUICamera(image.transform); // 这是我们自定义的获取UICamera 的方法,会贴在下面
Vector3 imgPos = image.transform.position;
Vector3 imgScreenPos = uiCamera.WorldToScreenPoint(imgPos);
imgScreenPos += new Vector3(10,0,0); // 这里假设我们要在 x 轴方向上进行一定的偏移
Vector3 labelPos = uiCamera.ScreenToWorldPoint(imgScreenPos);
label.GetComponent<RectTransform>().position = labelPos;
           

就是这么简单。

这里,可能有同学发现一个问题,我们将 image 的 世界空间位置转换到 UI 摄像机的屏幕空间,进行计算后,将它赋值给 label 时,没有转换回世界空间啊?因为, image 和 label 所在的 Canvas 的 Render Mode 不同,对RectTransform.position 的解释也不同:

  • Render Mode = Screen Space - Overlay ,控件的 RectTransform.position 是屏幕位置。
  • Render Mode = Screen Space - Camera 下,控件的RectTransform.position如其字面意思,是世界空间位置。这引出另一个问题,就是我们在计算控件偏移时,用的肯定是基于屏幕控件的位置,而 position 是世界空间,直接应用偏移是错的,一定要先将偏移也换算到屏幕空间

上面的代码是 Render Mode 不一致, camera -> overlay 的转换过程,下面我们将都是 camera的情况考虑进来:

Canvas canvasTips = GetCanvas(label.transform);	// 这是我们自定义的获取 Canvas 的方法,会贴在最后
Canvas canvasImage = GetCanvas(image.transform);
Camera uiCamera = canvasImage.worldCamera;
Vector3 imgPos = image.transform.position;
if (canvasTips.renderMode == RenderMode.ScreenSpaceOverlay)
{
    Vector3 imgScreenPos = uiCamera.WorldToScreenPoint(imgPos);
    imgScreenPos += new Vector3(10, 0, 0); // 这里假设我们要在 x 轴方向上进行一定的偏移
    Vector3 labelPos = uiCamera.ScreenToWorldPoint(imgScreenPos);
    label.GetComponent<RectTransform>().position = labelPos;
}
else
{
    // 将偏移换算到世界空间
    Vector3 offsetWorld = uiCamera.ScreenToWorld(new Vector3(10, 0, 0));
    Vector3 imgPos = image.GetComponent<RectTransform>().position;
    label.GetComponent<RectTransform>().position = imgPos + offset;
}
           

获取 Canvas 的代码:

Canvas GetCanvas(Transform trans)
{
    Canvas canvas = null;
    while(trans != null)
    {
        canvas = trans.GetComponent<Canvas>();
        if (canvas != null)
            break;
        trans = trans.parent;
    }

    return canvas;
}
           

获取 UI Camera 的代码:

Camera GetUICamera(Transform trans)
{
    Canvas canvas = GetCanvas(trans);
    return canvas != null ? canvas.worldCamera : null;
}
           

最后附一篇对RectTransform的解释的连接:

https://blog.csdn.net/SerenaHaven/article/details/78826851