• 【Unity实战技巧】教你4招计算UI物体的包围盒(Bounds)


    👉一、前言

    使用Unity做UI的框选、对齐等功能时常用到包围盒(Bounds)的计算。如果UI在无旋转的情况下,如图:(紫色框是其bounds)
    在这里插入图片描述
    只要知道UI的中心坐标和其大小(UI矩形宽高)就很容易计算出它的包围盒;
    ;一旦UI经过旋转之后,如图:(红色框是其bounds)
    在这里插入图片描述
    此时的包围盒的size并不等于该UI矩形的宽高,所以是需要经过一定的数学运算才能求出此时包围盒的size。本人在实际开发过程中,总结了4种Unity中如何计算UI物体的包围盒的方法,详见后文博客内容。

    👉二、四种UI包围盒的获取方法

    (注:以下方法的UI的中心坐标均指的是局部坐标系下,锚点为(0.5,0.5)时的中心原点坐标)

    1、法一:通过Unity自带API(UI有无旋转均适用)

    RecTransformUtility.CalculateRelativeRectTransformBounds(Transfrom root,Transform child)
    
    • 1

    使用此方法通过计算子物体在父物体的局部坐标下的bounds并返回。

    2、法二:通过UI宽高和中心坐标计算(适用于无旋转UI)

    1. 实例化Bounds后设置center和size
    RectTransform rect;//UI矩形对象
    Bounds bounds=new Bounds();
    bounds.center=rect.transform.localPosition;
    bounds.size=rect.rect.size;
    
    • 1
    • 2
    • 3
    • 4
    1. 将center和size在构造Bounds时作为实参传入
    RectTransform rect;
    Bounds bounds=new bounds(rect.transform.localPosition,rect.rect.size);
    
    • 1
    • 2

    以上两个方法都是通过设置包围盒bounds的中心坐标center和大小size来得到rect对象的包围盒。

    3、法三:通过UI的中心坐标、大小和锚点计算(适用于无旋转UI)

    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);
    
    • 1
    • 2
    • 3
    • 4

    此方法通过设置包围盒bounds的最小点min和最大点max来得到rect对象的包围盒

    4、法四:通过UI的四个边角顶点坐标计算(适用于旋转一定角度后的UI)

    (1)、计算UI旋转后的顶点坐标(自身坐标系下)
    1. 已知条件
      已知中心点(x0,y0)、某点坐标(x,y)、旋转角度θ;求旋转后(x’,y’)的坐标
    2. 计算原理

    x’=cos(θ)(x-x0)-sin(θ)(y-y0)+x0;
    y’=sin(θ)(x-x0)+cos(θ)(y-y0)+y0
    ;

    详细推导过程可看:在平面中,一个点绕任一点旋转θ角度后的点的坐标

    1. 计算UI旋转θ角度后的四个顶点坐标
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    (2)、计算UI旋转后的包围盒

    如图:
    在这里插入图片描述
    我们要计算旋转后的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);
    
    • 1
    • 2

    bounds即为UI旋转θ角度后的包围盒。

    👉三、实战应用demo

    1、搭建测试demo场景

    如图:
    在这里插入图片描述
    添加一张Image作为测试包围盒的对象,分别添加三个按钮测试有无旋转情况下的UI包围盒。新建脚本CalculateUIBounds写测试代码,挂载到Canvas上,拖拽对象如上图所示,代码如下。

    2、核心代码

    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);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    3、测试结果

    (1)UI无旋转情况下的包围盒

    在这里插入图片描述

    通过控制台输出,我们可以看到通过方法二和三得出的包围盒数据与方法一Unity自带API算出来的结果是一致。

    (2)UI有旋转情况下的包围盒
    1. 顺时针旋转45度
      在这里插入图片描述
      可以看出,通过我自写算法实现的方法四计算出来的顺时针旋转45度后的UI包围盒数据与方法一Unity自带API算出来的结果一致。

    2. 逆时针旋转120度
      在这里插入图片描述
      同理,逆时针旋转120度后的UI包围盒数据与方法一Unity自带API算出来的结果一致。

    (3)结论

    1.在UI无旋转的情况下用方法一、二、三获取的包围盒是一样的。
    2.在UI有旋转的情况下用方法一、四获取的包围盒是一样的。
    3.以上demo测试结果证明了我的四种方法均可获取到正确的UI包围盒(分有无旋转情况),具体用那种方法看你项目需求,最简单的还是方法一,一句代码就完事了。但是注意只是能获取到包围盒,并不是设置;设置UI包围盒的话,是需要设置其中心坐标center和大小size,或min(最小点)和max(最大点)才行。

    4、demo源工程

    四种计算UI包围盒的方法demo包
    基于Unity 2018.4.36

    👉四、包围盒(Bouns)知识点

    Bounds:表示一个轴对齐的包围盒(简称AABB),是与坐标轴对齐并且完全包围某个对象的盒体。

    1、变量

    属性说明
    center包围盒的中心
    extents包围盒的范围,始终是包围盒的size的一半
    max包围盒的最大点,始终等于center+extents
    min包围盒的最小点,始终等于center-extents
    size包围盒的总大小,始终是extents的两倍

    2、函数

    方法说明
    ClosestPoint该包围盒上最近的点
    Containspoint是否包含在该包围盒中
    Encapsulate合并增大包围盒,以便可以包裹 某个点point或多个对象
    Expand通过沿每一侧将边界的size增加amount,扩展这些边界
    IntersectRayray是否与该包围盒交叠
    Intersects另一个包围盒是否与该包围盒交叠
    SetMinMax将这些边界设置为该盒体的min和max值
    SqrDistance该点与该包围盒之间的最小平方距离
    ToString返回边界的格式化字符串
  • 相关阅读:
    如何利用Git中的tag管理项目版本号
    BiSeNet v2
    将一维数组转为三维数组,html引入swiper并且自定义左右切换按钮
    Windows下安装Nginx
    网站备案如何不关站访问教程
    linux中运行springboot jar包,内存占用多运行时报错
    TCP--拥塞控制
    Python程序设计实例 | 条形码图片识别
    java毕业设计软件B2C婚纱摄影网站的设计与实现S2SH[包运行成功]
    目标检测YOLO实战应用案例100讲-基于机器视觉的输电线路小目标检测和缺 陷识别(下)
  • 原文地址:https://blog.csdn.net/qq_42437783/article/details/126229973