UGUI中的anchor和canvas(屏幕适配)
ugui的布局功能比css更为灵活,但相应也导致更多概念,但是如果能理解下边几个问题,有助于更好地使用ugui布局
- Rect Transform上的锚点(anchor)和支点(pivot),作用是什么?对自身位置有什么影响?
- CanvasScaler原理?和anchor和pivot有关系没有?
- LayoutElement如何影响Rect Transform?
RectTransform继承自Transform并添加了一些布局属性,最重要的就是anchor和pivot。Transform表示一个点,RectTransform表示一个矩形区域。值得注意的是Canvas节点也是一个RectTransform组件上挂了一个Canvas组件,它也是一个Rect
另外需要知道的是,UGUI的屏幕坐标系左下角为(0,0),右上角为(1,1)
Anchor
内部表示为2个点:
1 | public Vector2 anchorMin { get; set; } |
是在父矩形的坐标,0-1之间,即父矩形的左下角、右上角,构成了一个矩形区域,用来表示自身在父组件的定位。因为一个矩形可以理解成四条边、四个顶点,所以会有不同的理解。
根据anchor中x,y不同,RectTransform组件会展现不同的属性:
当anchor中anchorMin.x != anchorMax.x时,x横向位置和组件宽由left,right决定,当相等时,表示为posX和width
当anchor中anchorMin.y != anchorMax.y时,y纵向位置和组件高由top,bottom决定,当相等时,表示为posY和height
这会让人有些费解:
pos X, pos Y为pivot点与anchor点在父坐标系中的差值,width,height为自身Rect的大小,当父Rect大小变化时,自身的大小和相对位置不会变,不会自适应
当为left,right, top,bottom时是自身Rect的四边与anchor在父坐标系的距离,由于这四个相对距离不会变化,当父Rect大小变化时,子Rect的width,height就会跟着变化以维持不变的相对距离
Pivot
Pivot即上图中空心圆环的位置,是在自身Rect中规一化的坐标。表示的是在自己坐标系中的的位置
它是旋转和缩放的中心点,同时也是Rect定位到父级时pos X, pos Y的定位点(当anchorMin, anchorMax重合时)
RectTransform组件的蓝图和原始编辑模式
蓝图模式
按下之后忽略RectTransform上的旋转和缩放,看下图就明白了,只是用于方便编辑
原始编辑模式(Raw Edit Mode)
默认情况下,调整Pivot和Anchor会维持当前Rect的位置和大小,当按下Raw Edit Mode时,Rect的大小和位置会随着调整变化
CanvasScaler
会随着Canvas组件Render Mode会有不同情况:
World Space ui会通过Canvas上指定的相机绘制,通常用于3d ui,这时CanvasScaler组件 UI Scale Mode变为 World 且不可更改。Canvas组件上的位置、大小都可以自行调节。此时ui组件的单位为Unity单位,可视范围受相机的可视范围影响
当为 Screen Space - Overlay 和 Screen Space - Camera 时,ui都在屏幕空间绘制,ui一定遮挡场景的物体。这时的Canvas组件时,它的大小是不能人为调整的,受CanvasScaler组件控制,UI Scale Mode会有三个选项
UI Scale Mode
Canvas组件的scale * width和height为屏幕的像素宽高,scale表示将canvas中的元素缩放scale值
Constant Pixel Size: Canvas中的ui 大小不会随屏幕改变,保持固定大小的像素值。但因为不同设备有不同的分辨率,ui 在屏幕在就会显示过大或者过小(占据了不同的比例)
Constant Physical Size: 与Constant Pixel Size类似,不过通过指定一个物理单位(cm, mm, inch, points等),ui元素的width,height是按这个单位计算,实际的像素会乘以不同设备的DPI
比如在1920x1080分辨率,dpi为96的设备上,屏幕为宽:1920 / 96 = 20inch, 高1080 / 96 = 11.25inch,当设置image with=10,2个即会占满整个屏幕:
- Scale With Screen Size: 响应式ui会用这个,会随着屏幕缩放,ui元素占据屏幕的比例是不变的。这时会出现一个参照分辨率的选项(Reference Resolution),也叫设计分辨率,因为在做ui设计时会按照一个固定的分辨率设计ui。比如设置参照分辨率为1920x1080时,摆放的ui在4k屏幕上(3840x2160)看不出有什么不同,因为Canvas的scale会设置成2 ,即当屏幕大于设计分辨率时,scale大小1,反之小于1。这时根据 Screen Match Mode scale又有不同的计算策略:
Expand,增大Canvas的size保证UI都能显示
1
scaleFactor = Mathf.Min(screenSize.x / referenceresolution.x, screenSize.y / referenceresolution.y);
假设referenceresolution = (800,600), screenSize=(1920,1080)
screenSize.x / referenceresolution.x = 2.4
screenSize.y / referenceresolution.y = 1.8
所以scale=1.8当screenSize 大于 referenceresolution时会尽可小的放大Canvas上的元素,保证在屏幕内
当screenSize 小于 referenceresolution时,会尽可能地缩小Canvas上的UI元素,保证在屏幕内因为
canvasSize = screenSize / min(scale)
,所以Canvas的Size总是大于referenceresolutionShrink,缩小Canvas可能会裁剪掉UI,但是不会出色空白
这种情况和 Expand相反1
scaleFactor = Mathf.Max(screenSize.x / referenceresolution.x, screenSize.y / referenceresolution.y);
上例中的scale=2.4,canvas的size就会小于referenceresolution
Match Width Or Height
1
2
3
4
5const float kLogBase = 2;
float logWidth = Mathf.Log(screenSize.x / m_ReferenceResolution.x, kLogBase);
float logHeight = Mathf.Log(screenSize.y / m_ReferenceResolution.y, kLogBase);
float logWeightedAverage = Mathf.Lerp(logWidth, logHeight, m_MatchWidthOrHeight);
scaleFactor = Mathf.Pow(kLogBase, logWeightedAverage);在上例子中:
当m_MatchWidthOrHeight=0,以width为参照,即scale=2.4; 这时x方向的分辨率变化UI都能完全显示,但y方向可能会被裁剪
当m_MatchWidthOrHeight=1,以height为参照,即scale=1.8;这时y方向的分辨率变化UI都能完全显示,但x方向可能会被裁剪在2.4-1.8之间并不是线性的,因为这样的效果更好:
// We take the log of the relative width and height before taking the average.
// Then we transform it back in the original space.
// the reason to transform in and out of logarithmic space is to have better behavior.
// If one axis has twice resolution and the other has half, it should even out if widthOrHeight value is at 0.5.
// In normal space the average would be (0.5 + 2) / 2 = 1.25
// In logarithmic space the average is (-1 + 1) / 2 = 0anchor和pivot会作用于CanvasScaler,当Canvas变化时,会因anchor和pivot不同有影响没有?
准确说没有直接关系,父rect的变化(包括Canvas)会因为anchor原因影响子rect的大小
Reference Pixels Per Unit 作用
sprite 有一个属性,Pixels Per Unit 表示每unity单位占多少像素,默认是100,当修改为50时,相同的图片占的unity单位*2,所以变大了
Reference Pixels Per Unit 是用于将像素转换到UI的单位时计算ui的content size。当导入的sprite size为100, Pixels Per Unit 为100,Reference Pixels Per Unit 为 100,则把这个图片放到ui上的size为 100 * (Reference Pixels Per Unit / Pixels Per Unit) = 100。当_Reference Pixels Per Unit_ 为200时,同样的图片在ui中的size = 200
RectTransform进阶
理解RectTransform中的属性很有帮助,可以在编程中修改:
RectTransform.position 是unity的世界坐标系中的位置,即使不是Screen Space绘制在世界中也有一个坐标
RectTransform.localPosition 是pivot点在父元素坐标系中的位置(父元素的pivot),是有z值的。当父子元素pivot相同时,此时localPostion的值和anchoredPositon相等
RectTransform.anchoredPosition 无z值,是pivot相对于anchor的坐标(anchor重合),往右为正,往上为正。可以理解成锚定后的相对偏移量
RectTransform.sizeDelta 并不能直接获取到Rect的长宽(除非它的anchor点重合),因为sizeDelta真正含义是RECT的大小比anchor的矩形大小差值
即图中红色两段相加为sizeDelta.x, 蓝色两段相加为sizeDelta.y,因为ui比anchor矩形小,所以为负。
sizeDelta = (-63,-89)
内部
1 | sizeDelta = offsetMax - offsetMin |
当需要获取ui大小时,可以使用rect.size而不是sizeDelta。但是rect.size是只读的,如果要设置大小的话最方便的是使用RectTransform.SetSizeWithCurrentAnchors
函数
下边是计算代码和关系
1 | RectTransform rect = GetComponent<RectTransform>(); |
LayoutElement
RectTransform能实现往往依赖父元素的进行布局。但是有些情况父元素又需要子元素来进行布局,或者说容器大小在具体元素内容确定前是无法提前预知的,是动态变化的。这时会用到ILayoutElement,它会根据LayoutConroller有不同的行为,间接影响RectTransform。很多组件都实现了ILayoutElemnt接口,比如Image,Text,LayoutGroup,LayoutElement可以去覆盖这些默认行为。简单说和anchor、pivot之类并没有直接关系,是两套系统,可以单独去理解,并不详细说明了
UGUI中的anchor和canvas(屏幕适配)