我们在Unity创建一个Canvas游戏对象,会默认添加Canvas,CanvasScaler,GraphicRaycaster这三个组件,下面会详细介绍一下各个组件的作用。
默认组件
1.0 Canvas组件
Canvas 组件是UI布局和渲染的抽象空间,所有的UI都必须在此元素之下(子物件),简单来说 Canvas 就是渲染 UI 的组件。
UI渲染的方式(Render Mode),有以下三种
-
Screen Space – Overlay:屏幕空间 – 覆盖
Screen Space – Overlay
在此模式下不会参照到Camera,UI直接显示在任何图形之上
- Pixel Perfect:可以使图像更清晰,但是有额外的性能开销,如果在有大量UI动画时,动画可能会不平顺
- Sort Order:深度值,该值越高显示越前面。
- Target Display:该设置指示画布渲染到指定的显示中。 支持的最大辅助显示(例如显示器)数量为 8
- Additional Shader Channels:获取或设置要在创建 Canvas网格时使用的附加着色器通道的遮罩
-
Screen Space – Camera:屏幕空间 – 摄影机
Screen Space – Camera
使用一个Camera作为参照,将UI平面放置在Camera前的一定距离,因为是参照Camera,如果屏幕大小、分辨率、Camera视锥改变时UI平面会自动调整大小。如果Scene中的物件(GameObject)比UI平面更靠近摄影机,就会遮挡到UI平面。
- Render Camera:用于渲染的摄影机
- Plane Distance:与Camera的距离
- Sorting Layer:Canvas属于的排序层,在 Edit->Project Setting->Tags and Layers->Sorting Layers 进行新增,越下方的层显示越前面
- Order in Layer:Canvas属于的排序层下的顺序,该值越高显示越前面
-
World Space:世界坐标空间
World Space
把物体当作世界座标中的平面(GameObject),也就是当作3D物件,显示3D UI
- Event Camera:处理UI事件(Click、Drag)的Camera,所设定的Camera才能触发事件
2.0 GraphicRaycaster组件
GraphicRaycasterGraphicRaycaster会观察 Canvas下所有图形,并检测是否被击中,射线检测其实就是指定位置与方向后,投射一条隐形线并判断是否有碰撞体在线上,用于判断是否点选到UI上。
-
Ignore Reversed Graphics
这个属性是用来决定当交互部件水平或者垂直翻转到背面对着屏幕(不一定是180度,只要翻转到背面对着屏幕)的时候,是否会受到点击响应 -
Blocked Objects
Blocked Objects 阻碍射线的 Object 类型,决定了当有物体遮挡在UI前面,并且点击了遮挡部分的时候,是否应该忽略这次点击,下面的讨论在Blocking Mask是EveryThing的情况下成立- None: 不忽略,照常进行UI点击响应
- 3D: 当点击带有3DCollider的物体遮挡部分的时候就忽略点击UI的响应
- 2D: 当点击带有2DCollider的物体遮挡部分的时候就忽略点击UI的响应
- All: 当点击有带有任意Collider的物体遮挡部分的时候都忽略
-
Blocking Mask
Blocking Mask 勾选的 Layer 将会阻碍射线
这个属性一般和Blocked Objects参数一起调节起作用,默认是EveryThing
举例:如果画面上有一个 Button 与 Cube 位置故意重叠,现在点击重叠之处会发现 Button 还是会被触发。
Blocked Objects为None
Blocked Objects 设定为 Three D,再次点选重叠区域,会发现 Cube 会阻碍射线检测,此时按钮不会有反应
Blocked Objects为Three D
Blocked Objects 、 Blocking Mask:
主要用于当Canvas 组件Render Mode 使用 World Space 或 Camera Space 时,UI 前有 3D 或是 2D Object 时,将会阻碍射线传递到 UI 图形
3.0 Canvas Scaler组件
Canvas Scaler是Unity UI系统中,控制UI元素大小和像素密度的组件,Canvas Scaler的缩放比例影响Canvas下的元素,包含字体大小和图像边界。
3.1前置概念
-
Reference Resolution:参考分辨率(设计分辨率),仅当Canva Scaler的UI Scale Mode设置为Scale With Screen Size时,会出现该属性。下文会对各个UI Scale Mode有详细介绍。
-
Screen Size:屏幕分辨率。
Screen Size -
Canvas Size:Canvas Rect Transform 宽高,会随着Canva Scaler的UI Scale Mode的改变而改变。
Canvas Size -
Canvas.scaleFactor:用于缩放整个画布,同时使其适合屏幕。 仅适用于renderMode为Screen Space - Overlay 和 Screen Space – Camera模式。源码如下:
/// <summary>
/// Sets the scale factor on the canvas.
/// </summary>
/// <param name="scaleFactor">The scale factor to use.</param>
protected void SetScaleFactor(float scaleFactor)
{
if (scaleFactor == m_PrevScaleFactor)
return;
m_Canvas.scaleFactor = scaleFactor;
m_PrevScaleFactor = scaleFactor;
}
可以看出,Canvas Scaler 通过设置Canvas下的Scale Factor,缩放所有在此Canvas下的元素
Reference Resolution,Screen Size,Canvas Size,Canvas.scaleFactor关系举例:
当Scale Factor为1时,Screen Size (800*600)、Canvas Size(800*600),图片大小1倍
Canvas Size(800*600)
图片尺寸
当Scale Factor为2时,Screen Size (800*600)、Canvas Size(400*300),图片大小2倍
Canvas Size(400*300)
图片尺寸放大1倍
3.2 UI Scale Mode
3.2.1 Constant Pixel Size
Canvas Size 始终等于 Screen Size,通过Scale Factor直接缩放所有UI元素
Constant Pixel Size
- Scale Factor:通过此Factor缩放所有在此Canvas下的元素
源码如下:
protected virtual void HandleConstantPixelSize()
{
SetScaleFactor(m_ScaleFactor);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit);
}
- Reference Pixels Per Unit
介绍Reference Pixels Per Unit之前需要先介绍图片设置中的Pixels Per Unit(表示一张Sprite,在世界坐标中一单位由几个Pixel组成)
Pixels Per Unit
这里使用的测试图片为原始尺寸100*100 的图片
图片原始尺寸
举例来说,场景中有一个1*1 Cube ,与一个2D Sprite(注意这里不是UI中Image)指定为测试图,两者的Transform Scale 都为 1。
当 Pixels Per Unit=100,表示每单位由 100 Pixel组成,Sprite 是100*100 Pixels,那 Sprite 在世界座标中大小就会变成 100/100 * 100/100 = 1*1 Unit
Pixels Per Unit=100 左(Sprite) 右(Cube)
当 Pixels Per Unit=10,表示每单位由 10 Pixel组成,Sprite 是100*100 Pixels,那 Sprite 在世界座标中大小就会变成 100/10 * 100/10 = 10*10 Unit
Pixels Per Unit=10 左(Sprite) 右(Cube)
由此可以推导出公式:Sprite 在世界坐标中大小 = 原图大小(Pixels) / Pixels Per Unit
让我们回到 Reference Pixels Per Unit,官方解释是,如果图片有设置Pixels Per Unit,则会将Sprite 的 1 pixel 转换成 UI 中的 1 pixel,下面将以Image源码说明:
public float pixelsPerUnit
{
get
{
float spritePixelsPerUnit = 100;
if (activeSprite)
spritePixelsPerUnit = activeSprite.pixelsPerUnit;
float referencePixelsPerUnit = 100;
if (canvas)
referencePixelsPerUnit = canvas.referencePixelsPerUnit;
return spritePixelsPerUnit / referencePixelsPerUnit;
}
}
可以看出 Image 通过过 spritePixelsPerUnit / referencePixelsPerUnit 方式算出新的 pixelsPerUnit
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 图片大小时,是把 Sprite宽高 / pixelsPerUnit
举例来说,建立一个Canvas参数如下
Canvas
Canvas底下建立一个Image,Sprite设定为测试图片,参数如下
Image
这边做4种不同的测试:测试方式是修改 Reference Pixels Per Unit 与 Pixels Per Unit 后,点下 Image Compoent 的 Set Native Size来设定图片原始大小,以此看到图片变化
测试数据
由此可以推导出公式:UI大小 = 原图大小(Pixels) / (Pixels Per Unit / Reference Pixels Per Unit)
或者 UI大小 = Sprite 在世界坐标中大小 * Reference Pixels Per Unit
3.2.2 Scale With Screen Size
通过设定的Reference Resolution(预设屏幕大小)来缩放
Scale With Screen Size
- Reference Resolution:预设屏幕大小
- Screen Match Mode:缩放模式
- Expand(扩大):将Canvas Size进行宽或高扩大,让他高于Reference Resolution,计算如下:
scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
举例来说,Reference Resolution为1280*720,Screen Size为800*600
ScaleFactor Width: 800/1280=0.625
ScaleFactor Height:600/720=0.83333
套用ScaleFactor公式:Canvas Size = Screen Size / Scale Factor
Canvas Width:800 / 0.625 = 1280
Canvas Height:600 / 0.625 = 960
Canvas Size 为 1280*960,高度从720变成了960,最大程度的放大(显示所有元素)
Expand
- Shrink(收缩):将Canvas Size进行宽或高收缩,让他低于Reference Resolution,计算如下
scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
举例来说,Reference Resolution为1280*720,Screen Size为800*600
ScaleFactor Width: 800/1280=0.625
ScaleFactor Height:600/720=0.83333
套用ScaleFactor公式:Canvas Size = Screen Size / Scale Factor
Canvas Width:800 / 0.83333 = 960
Canvas Height:600 / 0.83333 = 720
Canvas Size 为 960*720,宽度从1280变成了960,最大程度的缩小
Shrink
- Match Width or Height:根据Width或Height进行混合缩放,计算如下
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);
分别对ScaleFactor Width、Height取对数后,再进行平均混合,那为什麽不直接使用Match对Width、Height进行混合呢???让我们来比较一下
举例来说,假设Reference Resolution为400*300,Screen Size为200*600 大小关系是
Reference Resolution Width 是 Screen Size Width的2倍
Reference Resolution Height 是 Screen Size 的0.5倍
看起来会像下图
Match Width or Height
当Match为0.5时,ScaleFactor应该是 1
ScaleFactor Width: 200/400=0.5
ScaleFactor Height:600/300=2
一般混合:
ScaleFactor = Match * ScaleFactor Width + Match * ScaleFactorHeight
ScaleFactor = 0.5 * 0.5 + 0.5 * 2 = 1.25
对数混合:
logWidth:log2(0.5) = -1
logHeight:log2(2) = 1
logWeightedAverage:0
ScaleFactor:pow(2,0) = 1
scaleFactor一般混合为1.25,对数混合为1,结果很明显,使用对数混合能更完美的修正大小
完整源码算法:
protected virtual void HandleScaleWithScreenSize()
{
Vector2 screenSize = new Vector2(Screen.width, Screen.height);
// Multiple display support only when not the main display. For display 0 the reported
// resolution is always the desktops resolution since its part of the display API,
// so we use the standard none multiple display method. (case 741751)
int displayIndex = m_Canvas.targetDisplay;
if (displayIndex > 0 && displayIndex < Display.displays.Length)
{
Display disp = Display.displays[displayIndex];
screenSize = new Vector2(disp.renderingWidth, disp.renderingHeight);
}
float scaleFactor = 0;
switch (m_ScreenMatchMode)
{
case ScreenMatchMode.MatchWidthOrHeight:
{
// 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 = 0
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);
break;
}
case ScreenMatchMode.Expand:
{
scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
break;
}
case ScreenMatchMode.Shrink:
{
scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
break;
}
}
SetScaleFactor(scaleFactor);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit);
}
3.2.3 Constant Physical Size
通过硬体设备的Dpi(Dots Per Inch 每英吋点数),进行缩放
Constant Physical Size
-
Physical Unit:使用的单位种类
Physical Unit - Fallback Screen DPI:备用Dpi,当找不到设备Dpi时,使用此值
- Default Sprite DPI:预设的图片Dpi
源码如下:
protected virtual void HandleConstantPhysicalSize()
{
float currentDpi = Screen.dpi;
float dpi = (currentDpi == 0 ? m_FallbackScreenDPI : currentDpi);
float targetDPI = 1;
switch (m_PhysicalUnit)
{
case Unit.Centimeters: targetDPI = 2.54f; break;
case Unit.Millimeters: targetDPI = 25.4f; break;
case Unit.Inches: targetDPI = 1; break;
case Unit.Points: targetDPI = 72; break;
case Unit.Picas: targetDPI = 6; break;
}
SetScaleFactor(dpi / targetDPI);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit * targetDPI / m_DefaultSpriteDPI);
}
- ScaleFactor 为 “目前硬体dpi” 与 “目标单位” 的比
- ReferencePixelsPerUnit 要与targetDPI和m_DefaultSpriteDPI计算求出新的值,再传入Canvas中求出大小,公式如下:
最终的Reference Pixels Per Unit = Reference Pixels Per Unit * Physical Unit / Default Sprite DPI
- UI大小计算公式:
UI大小 = 原图大小(Pixels) / (Pixels Per Unit / 最终的Reference Pixels Per Unit)
网友评论