使用Unity做UI的框选、对齐等功能时常用到包围盒(Bounds)的计算。如果UI在无旋转的情况下,如图:(紫色框是其bounds)
只要知道UI的中心坐标和其大小(UI矩形宽高)就很容易计算出它的包围盒;
;一旦UI经过旋转之后,如图:(红色框是其bounds)
此时的包围盒的size并不等于该UI矩形的宽高,所以是需要经过一定的数学运算才能求出此时包围盒的size。本人在实际开发过程中,总结了4种Unity中如何计算UI物体的包围盒的方法,详见后文博客内容。
(注:以下方法的UI的中心坐标均指的是局部坐标系下,锚点为(0.5,0.5)时的中心原点坐标)
RecTransformUtility.CalculateRelativeRectTransformBounds(Transfrom root,Transform child)
使用此方法通过计算子物体在父物体的局部坐标下的bounds并返回。
RectTransform rect;//UI矩形对象
Bounds bounds=new Bounds();
bounds.center=rect.transform.localPosition;
bounds.size=rect.rect.size;
RectTransform rect;
Bounds bounds=new bounds(rect.transform.localPosition,rect.rect.size);
以上两个方法都是通过设置包围盒bounds的中心坐标center和大小size来得到rect对象的包围盒。
RectTransform rect;
Bounds bounds=new Bounds();
bounds.min=new Vector3(rect.transform.localPostion.x-rect.pivot.x*rect.rect.size.x,rect.transform.localPostion.y-rect.pivot.y*rect.rect.size.y,0);
bounds.max=new Vector3(rect.transform.localPostion.x+rect.pivot.x*rect.rect.size.x,rect.transform.localPostion.y+rect.pivot.y*rect.rect.size.y,0);
此方法通过设置包围盒bounds的最小点min和最大点max来得到rect对象的包围盒
x’=cos(θ)(x-x0)-sin(θ)(y-y0)+x0;
y’=sin(θ)(x-x0)+cos(θ)(y-y0)+y0;
详细推导过程可看:在平面中,一个点绕任一点旋转θ角度后的点的坐标
RectTransform rect;
Vector3[] corners=new Vector3[4];
rect.GetLocalCoreners(corners);//获取原局部坐标系下的四个边角坐标
float radian=θ*Mathf.PI / 180;//角度转弧度
//corners[0]'坐标
float a = Mathf.Cos(radian) * corners[0].x - Mathf.Sin(radian) * corners[0].y;
float b = Mathf.Sin(radian) * corners[0].x + Mathf.Cos(radian) * corners[0].y;
//corners[2]'坐标
float c = Mathf.Cos(radian) * corners[2].x - Mathf.Sin(radian) * corners[2].y;
float d = Mathf.Sin(radian) * corners[2].x + Mathf.Cos(radian) * corners[2].y;
//corners[1]'坐标
float e = Mathf.Cos(radian) * corners[1].x - Mathf.Sin(radian) * corners[1].y;
float f = Mathf.Sin(radian) * corners[1].x + Mathf.Cos(radian) * corners[1].y;
//corners[3]'坐标
float g = Mathf.Cos(radian) * corners[3].x - Mathf.Sin(radian) * corners[3].y;
float h = Mathf.Sin(radian) * corners[3].x + Mathf.Cos(radian) * corners[3].y;
如图:
我们要计算旋转后的UI的包围盒,只需要知道紫色框的size即可。从图中可看出,size.x等于corners[0]’ 与 corners[2]’在X轴上的绝对距离;size.y=corners[1]’ 与 corners[3]’在Y轴上的绝对距离。即:
Vector2 size=new Vector2(Mathf.Abs(a-c),Mathf.Abs(f-h));
Bounds bounds=new Bounds(rect.transform.localPosition,size);
bounds即为UI旋转θ角度后的包围盒。
如图:
添加一张Image作为测试包围盒的对象,分别添加三个按钮测试有无旋转情况下的UI包围盒。新建脚本CalculateUIBounds写测试代码,挂载到Canvas上,拖拽对象如上图所示,代码如下。
using UnityEngine;
using UnityEngine.UI;
public class CalculateUIBounds : MonoBehaviour
{
public GameObject imgObj;
private RectTransform imgRect;
public Button clockWiseBtn;
public Button antiClockWiseBtn;
public Button notRotateBtn;
Vector3[] corners = new Vector3[4];
void Start()
{
imgRect = imgObj.GetComponent<RectTransform>();
clockWiseBtn.onClick.AddListener(() =>
{
CalculateRotateUIBounds(-45);
});
antiClockWiseBtn.onClick.AddListener(() =>
{
CalculateRotateUIBounds(120);
});
notRotateBtn.onClick.AddListener(CalculateNotRotateUIBounds);
imgRect.GetLocalCorners(corners);//获取局部坐标系下的UI边角坐标
foreach (var item in corners)
{
Debug.Log("旋转前UI矩形四个顶点坐标:" + item);
}
}
///
/// 计算旋转的一定角度后的UI包围盒
///
///
private void CalculateRotateUIBounds(float angle)
{
imgObj.transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle));
float radian = angle * Mathf.PI / 180;//角度转弧度
//计算corner[0]'坐标的xy值
float _corner0X = Mathf.Cos(radian) * corners[0].x - Mathf.Sin(radian) * corners[0].y;
float _corner0Y = Mathf.Sin(radian) * corners[0].x + Mathf.Cos(radian) * corners[0].y;
Vector2 _corner0 = new Vector2(_corner0X, _corner0Y);
//计算corner[1]'坐标的xy值
float _corner1X = Mathf.Cos(radian) * corners[1].x - Mathf.Sin(radian) * corners[1].y;
float _corner1Y = Mathf.Sin(radian) * corners[1].x + Mathf.Cos(radian) * corners[1].y;
Vector2 _corner1 = new Vector2(_corner1X, _corner1Y);
//计算corner[2]'坐标的xy值
float _corner2X = Mathf.Cos(radian) * corners[2].x - Mathf.Sin(radian) * corners[2].y;
float _corner2Y = Mathf.Sin(radian) * corners[2].x + Mathf.Cos(radian) * corners[2].y;
Vector2 _corner2 = new Vector2(_corner2X, _corner2Y);
//计算corner[3]'坐标的xy值
float _corner3X = Mathf.Cos(radian) * corners[3].x - Mathf.Sin(radian) * corners[3].y;
float _corner3Y = Mathf.Sin(radian) * corners[3].x + Mathf.Cos(radian) * corners[3].y;
Vector2 _corner3 = new Vector2(_corner3X, _corner3Y);
Debug.Log("旋转后UI矩形四个顶点坐标:左下角:" + _corner0+" 左上角:"+ _corner0+" 右上角:"+_corner0+" 右下角:"+_corner0);
Bounds modelBounds = new Bounds();
modelBounds.center = imgRect.localPosition;
modelBounds.size = new Vector2(Mathf.Abs(_corner0X- _corner2X), Mathf.Abs(_corner1Y- _corner3Y));
Debug.Log("方法四:通过UI顶点坐标计算的UI矩形包围盒:" + modelBounds);
Bounds viewBounds = RectTransformUtility.CalculateRelativeRectTransformBounds(imgRect.parent, imgRect);
Debug.Log("方法一:通过Unity自带API计算的UI矩形包围盒:" + viewBounds);
}
///
/// 计算无旋转UI包围盒
///
private void CalculateNotRotateUIBounds()
{
Bounds notRotate1 = new Bounds(imgRect.transform.localPosition,imgRect.rect.size);
Debug.Log("方法二:通过计算的无旋转UI矩形包围盒:" + notRotate1);
Bounds notRotate2 = new Bounds();
notRotate2.center = imgRect.transform.localPosition;
notRotate2.min = new Vector3(imgRect.transform.localPosition.x - imgRect.pivot.x * imgRect.rect.size.x, imgRect.transform.localPosition.y - imgRect.pivot.y * imgRect.rect.size.y);
notRotate2.max= new Vector3(imgRect.transform.localPosition.x + imgRect.pivot.x * imgRect.rect.size.x, imgRect.transform.localPosition.y + imgRect.pivot.y * imgRect.rect.size.y);
Debug.Log("方法三:通过计算的无旋转UI矩形包围盒:" + notRotate2);
Bounds viewBounds = RectTransformUtility.CalculateRelativeRectTransformBounds(imgRect.parent, imgRect);
Debug.Log("方法一:通过Unity自带API计算的无旋转UI矩形包围盒:" + viewBounds);
}
}
通过控制台输出,我们可以看到通过方法二和三得出的包围盒数据与方法一Unity自带API算出来的结果是一致。
顺时针旋转45度
可以看出,通过我自写算法实现的方法四计算出来的顺时针旋转45度后的UI包围盒数据与方法一Unity自带API算出来的结果一致。
逆时针旋转120度
同理,逆时针旋转120度后的UI包围盒数据与方法一Unity自带API算出来的结果一致。
1.在UI无旋转的情况下用方法一、二、三获取的包围盒是一样的。
2.在UI有旋转的情况下用方法一、四获取的包围盒是一样的。
3.以上demo测试结果证明了我的四种方法均可获取到正确的UI包围盒(分有无旋转情况),具体用那种方法看你项目需求,最简单的还是方法一,一句代码就完事了。但是注意只是能获取到包围盒,并不是设置;设置UI包围盒的话,是需要设置其中心坐标center和大小size,或min(最小点)和max(最大点)才行。
四种计算UI包围盒的方法demo包
基于Unity 2018.4.36
Bounds:表示一个轴对齐的包围盒(简称AABB),是与坐标轴对齐并且完全包围某个对象的盒体。
属性 | 说明 |
---|---|
center | 包围盒的中心 |
extents | 包围盒的范围,始终是包围盒的size的一半 |
max | 包围盒的最大点,始终等于center+extents |
min | 包围盒的最小点,始终等于center-extents |
size | 包围盒的总大小,始终是extents的两倍 |
方法 | 说明 |
---|---|
ClosestPoint | 该包围盒上最近的点 |
Contains | point是否包含在该包围盒中 |
Encapsulate | 合并增大包围盒,以便可以包裹 某个点point或多个对象 |
Expand | 通过沿每一侧将边界的size增加amount,扩展这些边界 |
IntersectRay | ray是否与该包围盒交叠 |
Intersects | 另一个包围盒是否与该包围盒交叠 |
SetMinMax | 将这些边界设置为该盒体的min和max值 |
SqrDistance | 该点与该包围盒之间的最小平方距离 |
ToString | 返回边界的格式化字符串 |