美文网首页
UGUI笔记——Canvas,CanvasScaler,Grap

UGUI笔记——Canvas,CanvasScaler,Grap

作者: 莫忘初心_倒霉熊 | 来源:发表于2021-05-16 17:04 被阅读0次

我们在Unity创建一个Canvas游戏对象,会默认添加Canvas,CanvasScaler,GraphicRaycaster这三个组件,下面会详细介绍一下各个组件的作用。


默认组件

1.0 Canvas组件

Canvas 组件是UI布局和渲染的抽象空间,所有的UI都必须在此元素之下(子物件),简单来说 Canvas 就是渲染 UI 的组件。
UI渲染的方式(Render Mode),有以下三种

  1. Screen Space – Overlay:屏幕空间 – 覆盖


    Screen Space – Overlay

    在此模式下不会参照到Camera,UI直接显示在任何图形之上

  • Pixel Perfect:可以使图像更清晰,但是有额外的性能开销,如果在有大量UI动画时,动画可能会不平顺
  • Sort Order:深度值,该值越高显示越前面。
  • Target Display:该设置指示画布渲染到指定的显示中。 支持的最大辅助显示(例如显示器)数量为 8
  • Additional Shader Channels:获取或设置要在创建 Canvas网格时使用的附加着色器通道的遮罩
  1. 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属于的排序层下的顺序,该值越高显示越前面
  1. World Space:世界坐标空间


    World Space

    把物体当作世界座标中的平面(GameObject),也就是当作3D物件,显示3D UI

  • Event Camera:处理UI事件(Click、Drag)的Camera,所设定的Camera才能触发事件

2.0 GraphicRaycaster组件

GraphicRaycaster

GraphicRaycaster会观察 Canvas下所有图形,并检测是否被击中,射线检测其实就是指定位置与方向后,投射一条隐形线并判断是否有碰撞体在线上,用于判断是否点选到UI上。

  • Ignore Reversed Graphics
    这个属性是用来决定当交互部件水平或者垂直翻转到背面对着屏幕(不一定是180度,只要翻转到背面对着屏幕)的时候,是否会受到点击响应

  • Blocked Objects
    Blocked Objects 阻碍射线的 Object 类型,决定了当有物体遮挡在UI前面,并且点击了遮挡部分的时候,是否应该忽略这次点击,下面的讨论在Blocking Mask是EveryThing的情况下成立

    1. None: 不忽略,照常进行UI点击响应
    2. 3D: 当点击带有3DCollider的物体遮挡部分的时候就忽略点击UI的响应
    3. 2D: 当点击带有2DCollider的物体遮挡部分的时候就忽略点击UI的响应
    4. 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:缩放模式
  1. 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
  1. 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
  1. 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)

相关文章

网友评论

      本文标题:UGUI笔记——Canvas,CanvasScaler,Grap

      本文链接:https://www.haomeiwen.com/subject/kclrjltx.html