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

深入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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容