最近想换工作,在准备面试,突然想起了三年前一个没有答上来的问题,就是Unity的UI有几种适配模式,都是什么意思。当时支支吾吾半天没讲出来,今天必须搞懂它。
我们知道,Unity里的UI元素都要绘制在Canvas上,那么就先来看Canvas是个什么东西:
如上图所示,Canvas 渲染模式有三种:Screen Space-Overlay、Screen Space-Camera、World Space。下面就分别进行进行解析:
属性 | 功能 |
Event Camera | 响应事件的相机 |
Sorting Layer | 画布的深度,指定了相机的渲染顺序 |
Order in Layer | 值越大,该UI越显示在前面 |
Addtional Shader Channels | 附加着色通道,决定Shader可以读取哪些相关数据,比如 法线、 切线 等数据。 |
在世界空间渲染通常用于显示人物头顶的血条,NPC头上的名字等等。这种情况下,UI位于世界坐标系中,我习惯称它为3DUI,3DUI是游戏中负责渲染3d模型的相机来渲染的,如果要做适配,也是针对3d相机适配,通常都是调节下fov,来保证不同长宽比的屏幕看到的画面大致一样。并不需要针对3dui做适配。
属性 | 功能 |
Pixel Perfect | 使UI元素像素对应,效果就是边缘清晰不模糊 |
Sort Order | 多个Canvas时,数值越大越后渲染。值大的画布,会挡住值小的 |
Target Display | 目标显示器,如果有多个屏幕的话可以选择 |
Addtional Shader Channels | 附加着色通道,决定Shader可以读取哪些相关数据,比如 法线、 切线 等数据。 |
这个屏幕空间覆盖模式渲染模式,就是表示不管有没有相机去渲染场景,Canvas下的所有UI永远位于屏幕的前面,覆盖掉渲染场景显示的元素。
属性 | 功能 |
Pixel Perfect | 使UI元素像素对应,效果就是边缘清晰不模糊 |
Render Camera | 渲染的相机 |
Sort Order | 多个Canvas时,数值越大越后渲染。值大的画布,会挡住值小的 |
Order In Layer | Canvas属于的排序层,在 Edit->Project Setting->Tags and Layers->Sorting Layers 进行新增,越下方的层显示越前面 |
Plane Distance | Canvas与相机之间的距离 |
这种渲染模式 适用于场景模型太多太大,在调整UI的时候挡住UI,让UI和渲染的相机移动到比较远的位置,就可以避免遮挡。并且Canvas 和 摄像机之间有一定的距离 , 可以在摄像机和 Canvas之间放置一些模型或粒子特效。
Screen Space-Camera和Overlay很相似,不同点就在于,Overlay只会根据屏幕的尺寸、分辨率和CanvasScaler的设置,来自动调节画布长宽和缩放。而使用Camera的画布除了受上述因素影响,还会额外受到Camera参数设置影响。
Overlay的性能也会稍微好点,使用Overlay时,UI元素会简单粗暴的绘制在最上层,Unity不需要考虑剔除和排序。但这点性能损耗也无伤大雅,大家知道就好。
我们可以观察到,Canvas物体上面,除了Canvas组件,还有一个组件叫做Canvas Scaler,这个组件就是专门来调节Canvas大小的,以达到UI适配的目的。如下图所示:
Canvas 缩放模式有三种:Constant Pixer Size、Scale With Screen Size、Constant Physical Size
下面就分别进行进行解析:
属性 | 功能 |
Scale Factor | 缩放因子 |
Reference Pixels Per Uit | 单位面积像素数量,默认100 |
Scale Factor参数
首先,来看官方代码对于这个参数的设置:
- protected void SetScaleFactor(float scaleFactor)
- {
- if (scaleFactor == m_PrevScaleFactor)
- return;
-
- m_Canvas.scaleFactor = scaleFactor;
- m_PrevScaleFactor = scaleFactor;
- }
用代码可以看出来,Canvas Scaler 透过设定Canvas下的Scale Factor参数来缩放所有在此Canvas下的UI元素的大小,下面就举个例子说明一下。
例子:
当前分辨率为1920 X 1080,将Scale Factor设为1:
Canvas的长宽等于整成的屏幕的长宽(1920 X 1080),缩放是1倍。
将ScaleFactor设置为2:
Canvas的长宽变成了缩减了一半,缩放变成了2倍。
而图片大小也变成了原来的2倍大。
假如我们维持 ScaleFactor 不变,使用不同的分辨率进行游戏,又会怎样呢?
下面是ScaleFactor为1时,分辨率为1280x720的图片大小:
我们将分辨率调成100x100:
可以观察到,图片本身大小没有变化。但是已经充满了整个屏幕。也就是说,在这种适配模式下,如果我们想将图片等比缩小,就需要自己手动调节ScaleFactor。
Reference Pixels Per Unit参数
这里,我们详细的介绍一下Constant Pixer Size模式下的Reference Pixels Per Unit属性跟图片的Pixels Per Unit的关系。
举个例子
导入项目一张图片,将图片的Pixels Per Unit设置为100:
场景中有一个标准大小的Cube和一个标准大小的Sprite,两者的缩放比例都为1,大小一致:
当图片的Pixels Per Unit为100,每单位由100Pixels组成,那么这个Sprite在世界坐标就是
图片大小 = (100/100) =1 Unit
然后,将图片的Pixels Per Unit设置为10:
图片大小 = (100/10) =10 Unit
结论:
让我们回到 Reference Pixels Per Unit,官方解释是,如果图片档有设定Pixels Per Unit,则会将Sprite 的 1 pixel 转换成 UI 中的 1 pixel,让我们来看一下官方代码:
- public float pixelsPerUnit
- {
- get
- {
- float spritePixelsPerUnit = 100;
- if (sprite)
- spritePixelsPerUnit = sprite.pixelsPerUnit;
-
- float referencePixelsPerUnit = 100;
- if (canvas)
- referencePixelsPerUnit = canvas.referencePixelsPerUnit;
-
- return spritePixelsPerUnit / referencePixelsPerUnit;
- }
- }
- public override void SetNativeSize()
- {
- if (overrideSprite != null)
- {
- float w = overrideSprite.rect.width / pixelsPerUnit;
- float h = overrideSprite.rect.height / pixelsPerUnit;
- rectTransform.anchorMax = rectTransform.anchorMin;
- rectTransform.sizeDelta = new Vector2(w, h);
- SetAllDirty();
- }
- }
上面的代码,可以看出 Image 是通过 spritePixelsPerUnit / referencePixelsPerUnit 方式算出新的 pixelsPerUnit大小。
在设定 Image 图片大小时,是把 宽高 / Pixels Per Unit。
实测一下,建立一个Canvas参数如下:
Canvas下面新建一个Image,参数如下:
通过修改Canvas Scaler的Reference Pixels Per Unit参数 与 图片的Pixels Per Unit参数,来做4组测试,然后来看图片的不同变化:
■ 上表可以看出当数值改变时,图片预设大小也会改变
■ 由此可以推导出公式
UI大小 = 原图大小(Pixels) / Pixels Per Unit * Reference Pixels Per Unit