目录
1、RectTransform和Debug
2、轴心(Pivot)
3、锚点(Anchors)
4、SizeDelta
5、代码
6、总结
首先大家有没有遇到过这样的问题,像下面这样在代码中直接给,轴心、锚点赋值。
anchors.x = 1;
anchors.y = 1;
rtf.pivot = new Vector2(1,1)
看起来是没有什么问题,但是实际会造成UI元素位置大小的改变,并不能达到我们单纯改变锚点、轴心的目的。但是这是为什么呢?
1、RectTransform和Debug
在Unity中除了Inspector面板上的属性,点击右上角的小箭头可以打开一个Debug模式,这里有组件更详细的属性。切换到Debug模式,看到的是这两组数据,现在虽然看起来是一样的,但是这只是一种特殊情况,本质上是不同的,RectTransform只是提供了一个可视化的编辑,实际控制UI元素的是右边的
当用代码改变Pivot ,AnchorMin,AnchorMax的时,是没有连带改变 AnchoredPosition和SizeDelta的,所以造成了图像位置大小的异常
2、轴心(Pivot)
轴心的改变只会影响AnchoredPosition的计算,而AnchoredPosition的值由锚点和轴心共同影响,具体计算方法为锚点到轴心的向量,因为涉及到锚点一会提到锚点再详细说明。
图中从锚点到轴心的向量就是AnchoredPosition啦
3、锚点(Anchors)
锚点是确定UI元素和它父物体的相对位置,计算方面既会影响到AnchoredPosition,也会影响到SizeDelta。锚点是由AnchorMin和AnchorMax控制,首先说下锚点的两种情况。
当Min和Max相等的时候为锚点:
当Min和Max不相等的时候为锚框:
而当处于锚框状态时,AnchoredPosition的值为锚框中的点P1与轴心P2的向量,P1是在锚框中对RectTransform的Pivot属性取插值得到的。可能不是很好理解我们举个例子这里RectTransform的Pivot为(0.75,0.75),P1为锚框坐标XY做0.75插值的位置,所以之前说的锚点状态只是锚框的一种特殊情况
4、SizeDelta
这个东西就比较奇怪了,有时候值是物体的宽高,有时候又是一组奇怪的数字。但是了解过后就能知道他的意义,也可以明白为什么Unity会给它起名叫做SizeDelta
亮公式
OffsetMin = LeftBottom - AnchoredMin
OffsetMax = RightTop - AnchoredMax
SizeDelta = OffsetMax - OffsetMin
乍一看这公式,就只知道AnchoredMin,AnchoredMax。其他的是个啥子东西嘛,这个不急我们先从这个Offset开始看,它是一个Vector2的变量,其实是可以直接从RectTransform里面获取到
其中的RightTop 和LeftBottom 则是代表UI元素右上和左下的坐标,AnchoredMin和AnchoredMax则代表锚框左下和右上的坐标,那求出来的是个什么呢?
先看这个锚点的情况
这个时候算出来的SizeDelta是这个样子,这个时候数值正好就是UI元素的宽高再看这个锚框的情况
可以看出Offset所表达的物理意义其实就是UI元素的边距离锚框的偏移量
而SizeDelta表示的是UI元素和他锚框区域的一个增减量的关系,可以理解成是UI元素的尺寸减去锚框的尺寸,比锚框小的时候就是负值,比锚框大就是正值
物体的实际尺寸则是用它的锚框尺寸加上它的SizeDelta,这也就是为什么设置成锚框可以做到自适应的效果
5、代码
既然知道了AnchoredPosition和SizeDelta的计算方法,那自然可以通过参数计算出正确的数值,我在这就朴实无华的把公式套了进去,大家仅做参考
//轴心的
private static void StayPosition(RectTransform rtf,Vector2 pivot){
//计算物体轴心位置
Vector3[] world_corners = new Vector3[4];
rtf.GetWorldCorners(world_corners);
float pivot_pos_x = Mathf.Lerp(world_corners[0].x,world_corners[2].x,pivot.x);
float pivot_pos_y = Mathf.Lerp(world_corners[0].y,world_corners[2].y,pivot.y);
Vector2 temp_pos = new Vector2(pivot_pos_x,pivot_pos_y);
//计算锚框轴心位置
Vector2[] anchors_world_pos = GetAnchorsWorldPosition(rtf);
float anchor_x = Mathf.Lerp(anchors_world_pos[0].x,anchors_world_pos[1].x,pivot.x);
float anchor_y = Mathf.Lerp(anchors_world_pos[0].y,anchors_world_pos[1].y,pivot.y);
Vector2 anchor_pos = new Vector2(anchor_x,anchor_y);
//求两点的向量
rtf.anchoredPosition = temp_pos - anchor_pos;
}
//锚点的
private static void StayPosition(RectTransform rtf,Vector2 anchor_min,Vector2 anchor_max){
//设置anchoredPosition
Vector2 pivot = rtf.pivot;
Vector2[] anchors_world_pos = GetAnchorsWorldPosition(rtf,anchor_min,anchor_max);
float anchor_x = Mathf.Lerp(anchors_world_pos[0].x,anchors_world_pos[1].x,pivot.x);
float anchor_y = Mathf.Lerp(anchors_world_pos[0].y,anchors_world_pos[1].y,pivot.y);
Vector2 anchor_pos = new Vector2(anchor_x,anchor_y);
rtf.anchoredPosition = (Vector2)rtf.position - anchor_pos;
//设置sizeDelta
Vector3[] world_corners = new Vector3[4];
rtf.GetWorldCorners(world_corners);
Vector2 cur_offset_max = (Vector2)world_corners[2] - anchors_world_pos[1];
Vector2 cur_offset_min = (Vector2)world_corners[0] - anchors_world_pos[0];
Vector2 cur_size_delta = cur_offset_max - cur_offset_min;
rtf.sizeDelta = cur_size_delta;
}
//获得rtf锚框位置的世界坐标
private static Vector2[] GetAnchorsWorldPosition(RectTransform rtf,Vector2? anchor_min=null,Vector2? anchor_max=null){
RectTransform praent_rtf = rtf.parent.GetComponent<RectTransform>();
Vector3[] p_world_corners = new Vector3[4];
praent_rtf.GetWorldCorners(p_world_corners);
if(anchor_min == null) anchor_min = rtf.anchorMin;
if(anchor_max == null) anchor_max = rtf.anchorMax;
Vector2 min = (Vector2)p_world_corners[0]+(p_world_corners[2]-p_world_corners[0])*(Vector2)anchor_min;
Vector2 max = (Vector2)p_world_corners[0]+(p_world_corners[2]-p_world_corners[0])*(Vector2)anchor_max;
return new Vector2[]{min,max};
}
6、总结
RectTransform是根据锚点和轴心,再配合AnchoredPosition和SizeDelta控制UI元素其相对父物体的位置和尺寸。但是单纯的改变锚点和轴心的位置不会一起改变这两个值,Unity里虽然有计算方法,但是封装在了Editor中不让我们直接用,我在这提供了一个简单的转换方法,但总觉得不是最优解。就当我抛砖引玉,希望得到大佬们的指点