在进行使用 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 上的图片位置一致,这并不复杂,我们要做的是:
- 将图片位置转换到屏幕空间
- 对屏幕控件位置进行操作,比如我们需要加点偏移
- 将新的位置转换到屏幕空间,并赋值给文本
代码:
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