美文网首页
Unity_新手必懂知识点|显示原生态Image

Unity_新手必懂知识点|显示原生态Image

作者: riki_tree | 来源:发表于2019-06-06 15:52 被阅读0次

    深入UGUI源码去认识Image。

    作为Graphic家族最重要的成员之一,我相信你的UI里面Image是必不可少的元素。我也相信大部分使用者都能够熟练的应用Image。这一篇文章并不是Image的使用教程,只是从源码角度的对Image的剖析,以及总结(源码请自行下载)。

    属性

    首先简单介绍一下image面板上各属性:


    pro.png

    Source Image:图片资源,支持精灵贴图;
    Color:图片颜色,默认为白色;
    Material:材质;
    Raycast Target:是否是射线投射目标;是——此Image可以接受射线投射,并且会遮挡被覆盖UI的事件调用;否——射线忽视Image,可以穿透Image。
    建议:普通Image选择否,需要添加事件调用的Image选择是。
    Image Type:图片显示方式,总共有4种:Simple,Sliced,Tiled,Filled(本文的介绍重点,此处不在解释)。
    Preserve Aspect:图片是否以原比例显示;
    Set Native Size:设置图片以原尺寸显示。

    源码剖析

    接下来主要通过Image几个重要的函数来解读Image:

    1.  private Vector4 GetDrawingDimensions(bool shouldPreserveAspect);
    

    返回的向量就是图片去掉padding之后中间部分的x,y,z,w坐标。源码如下:

            /// Image's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top.
            ///shouldPreserveAspect为是否按精灵的原比例显示
            ///rect (x,y是左下角相对于中心点的坐标,weight和height分别为宽,高
            private Vector4 GetDrawingDimensions(bool shouldPreserveAspect)
            {
                //当前精灵的填充内边框(left,bottom,right,top),一般情况下都是(0,0,0,0)
                var padding = activeSprite == null ? Vector4.zero : Sprites.DataUtility.GetPadding(activeSprite);
                //当前精灵的大小(包含了边框的大小)
                var size = activeSprite == null ? Vector2.zero : new Vector2(activeSprite.rect.width, activeSprite.rect.height);
                //目标绘制区域的坐标及大小(x,y为该UI相对于轴心的坐标,width,height为UI宽高)
                Rect r = GetPixelAdjustedRect();
    
                int spriteW = Mathf.RoundToInt(size.x);
                int spriteH = Mathf.RoundToInt(size.y);
                //计算出一种比率,为显示出来的图片剔除内边框做准备
                var v = new Vector4(
                        padding.x / spriteW,
                        padding.y / spriteH,
                        (spriteW - padding.z) / spriteW,
                        (spriteH - padding.w) / spriteH);
    
                if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
                {
                    //原图宽高比,目标绘制区域宽高比
                    var spriteRatio = size.x / size.y;
                    var rectRatio = r.width / r.height;
                    //原图更宽则按宽调整高度大小,以及重新计算坐标位置,反之则按高度调整
                    if (spriteRatio > rectRatio)
                    {
                        var oldHeight = r.height;
                        r.height = r.width * (1.0f / spriteRatio);
                        r.y += (oldHeight - r.height) * rectTransform.pivot.y;
                    }
                    else
                    {
                        var oldWidth = r.width;
                        r.width = r.height * spriteRatio;
                        r.x += (oldWidth - r.width) * rectTransform.pivot.x;
                    }
                }
                //重新计算x,y,z,w的大小
                v = new Vector4(
                        r.x + r.width * v.x,
                        r.y + r.height * v.y,
                        r.x + r.width * v.z,
                        r.y + r.height * v.w
                        );
    
                return v;
            }
    

    计算最后的向量如图所示:


    v.png
    2.  private void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect);
    

    简单模式下的顶点信息。源码如下:

            void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect)
            {
                //获得图片的位置信息
                Vector4 v = GetDrawingDimensions(lPreserveAspect);
                //获得精灵的uv坐标
                var uv = (activeSprite != null) ? Sprites.DataUtility.GetOuterUV(activeSprite) : Vector4.zero;
    
                var color32 = color;
                vh.Clear();
                //添加顶点
                vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(uv.x, uv.y));
                vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(uv.x, uv.w));
                vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(uv.z, uv.w));
                vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(uv.z, uv.y));
                //添加三角形
                vh.AddTriangle(0, 1, 2);
                vh.AddTriangle(2, 3, 0);
            }
    

    可以看得出来,网格是由4个顶点,两个三角形构成。在Unity中如图所示:


    simple.png
    simple.gif

    在这个模式下,无论图片怎么变化,网格永远是由4个顶底,两个三角形构成。这种模式也是最少消耗性能的模式。但是带来的问题是,如果图形需要非等比例缩放,那么就会引起图片显示比例失调而失真。

    3.       private void GenerateSlicedSprite(VertexHelper toFill)
    

    裁剪模式下的顶点信息。源码如下:

            static readonly Vector2[] s_VertScratch = new Vector2[4];
            static readonly Vector2[] s_UVScratch = new Vector2[4];
    
            /// <summary>
            /// 得到9个区域(用边框裁剪开的9个区域)
            /// </summary>
            /// <param name="toFill"></param>
            private void GenerateSlicedSprite(VertexHelper toFill)
            {
                //如果没有边框则跟普通精灵顶点三角形是一样的
                if (!hasBorder)
                {
                    GenerateSimpleSprite(toFill, false);
                    return;
                }
                Vector4 outer, inner, padding, border;
                if (activeSprite != null)
                {
                    outer = Sprites.DataUtility.GetOuterUV(activeSprite);
                    inner = Sprites.DataUtility.GetInnerUV(activeSprite);
                    padding = Sprites.DataUtility.GetPadding(activeSprite);
                    border = activeSprite.border;
                }
                else
                {
                    outer = Vector4.zero;
                    inner = Vector4.zero;
                    padding = Vector4.zero;
                    border = Vector4.zero;
                }
                Rect rect = GetPixelAdjustedRect();
                //调整后的边框大小
                Vector4 adjustedBorders = GetAdjustedBorders(border / pixelsPerUnit, rect);
                padding = padding / pixelsPerUnit;
                //图片的真实坐标和大小
                s_VertScratch[0] = new Vector2(padding.x, padding.y);
                s_VertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
    
                s_VertScratch[1].x = adjustedBorders.x;
                s_VertScratch[1].y = adjustedBorders.y;
    
                s_VertScratch[2].x = rect.width - adjustedBorders.z;
                s_VertScratch[2].y = rect.height - adjustedBorders.w;
    
                for (int i = 0; i < 4; ++i)
                {
                    s_VertScratch[i].x += rect.x;
                    s_VertScratch[i].y += rect.y;
                }
    
                s_UVScratch[0] = new Vector2(outer.x, outer.y);
                s_UVScratch[1] = new Vector2(inner.x, inner.y);
                s_UVScratch[2] = new Vector2(inner.z, inner.w);
                s_UVScratch[3] = new Vector2(outer.z, outer.w);
                toFill.Clear();
                //生成9个矩形区域
                for (int x = 0; x < 3; ++x)
                {
                    int x2 = x + 1;
                    for (int y = 0; y < 3; ++y)
                    {
                        if (!m_FillCenter && x == 1 && y == 1)
                            continue;
                        int y2 = y + 1;
                        AddQuad(toFill,
                            new Vector2(s_VertScratch[x].x, s_VertScratch[y].y),
                            new Vector2(s_VertScratch[x2].x, s_VertScratch[y2].y),
                            color,
                            new Vector2(s_UVScratch[x].x, s_UVScratch[y].y),
                            new Vector2(s_UVScratch[x2].x, s_UVScratch[y2].y));
                    }
                }
            }
    

    假设上下左右都存在外边框的话(即九宫格的图片格式),那么顶点的个数是固定的16个,如果去除中心的话,三角形是16个,带有中心的话,三角形是18个。从源码可以看出,网格的边框4个角部是永远不会被拉伸,一直是边框大小原比例显示(除非是显示的尺寸小于边框的大小),边框的中部,以及图片去除边框的中心是会随着图形的拉伸而变化。如图所示:


    sliced.gif

    接下来,把目光放在这张图片的圆角上:


    simple1.gif

    上图为简单模式下的图片拉伸


    sliced1.gif
    上图为裁剪模式下的图片拉伸,你觉得那个更具原生态。而且我们也可以使用三宫格,以及更特殊的裁剪边框的模式来处理特殊的图片。
    4.         void GenerateTiledSprite(VertexHelper toFill); 
    

    平铺模式下的顶点信息。源码过长就不放了,其构造方式与裁剪模式相似,不过对于平铺模式下,除了网格边框的4个与裁剪模式一样,他的边框中部以及图像中间会按尺寸比例像瓦片一样平铺产生,因此会构造大量的顶点和三角形,因此这种情况除非是特殊需求,尽量不要使用。如图所示:


    tiled.gif
    5.        void GenerateFilledSprite(VertexHelper toFill, bool preserveAspect)
    

    覆盖模式的顶点信息。其内部通过FillAmount的值来控制需要构建的顶点的数量。这种模式对于处理进度类似的效果非常有效。除此之外他与简单模式具有一样的特点。如图所示:


    filled.gif

    小结

    作为UGUI最重要的控件之一:Image,我们不仅仅要会使用,还要懂得他背后的原理。Image这几种模式,各有各的特点,比如icon我们更偏向使用简单模式,并按比例显示,对于需要拉伸的图片,我们往往会使用裁剪模式。很多时间,我们的项目中需要显示的是一张原生态的图片,那么如果使我们的图片显得更加自然,相信在文中,你能找到答案。关于顶点这一块有什么不懂的地方,请参考之前文章:Unity_UGUI|通向UGUI源码的入口VertexHelper 链接:https://www.jianshu.com/p/2245969a9173

    相关文章

      网友评论

          本文标题:Unity_新手必懂知识点|显示原生态Image

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