ngui源码中关于图集(UIAtlas)一些有用的方法记录

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jingangxin666/article/details/80711871

0. 说明:

texture表示图集使用的大图片, 例如:

这里写图片描述

sprite表示被打包进图集的小图片, 例如:

这里写图片描述

1. 计算一个图集中的texture打包率 / 使用率 / sprite入住率

在ngui内置的打包图集脚本UITexturePacker中, 有一个方法Occupancy, 可以用来计算图集中的sprite入住率

    /// Computes the ratio of used surface area.
    public float Occupancy()
    {
        ulong usedSurfaceArea = 0;
        for (int i = 0; i < usedRectangles.Count; ++i)
            usedSurfaceArea += (uint)usedRectangles[i].width * (uint)usedRectangles[i].height;

        return (float)usedSurfaceArea / (binWidth * binHeight);
    }

代码中的usedSurfaceArea是一张Texture中已经被占用的矩形的列表(public List<Rect> usedRectangles = new List<Rect>();), 通过遍历List中的矩形, 计算它们总面积, 再除以Texture的总面积, 就得到sprite的入住率.

注意, 计算一种图集的占有率, 要考虑的是一张张sprite所占的矩形而不是它的透明通道比率, 即使sprite内部是空的.

但是以上方法只能在使用UITexturePacker打包图集的时候才能知道一张texture的入住率, 如果图集是通过其它打包工具获得的呢?

可以在UIAtlas代码中mSprites保存的是该图集的保存的所有sprite信息List<UISpriteData> , UISpriteData的部分成员如下:

    public string name = "Sprite";
    public int x = 0;
    public int y = 0;
    public int width = 0;
    public int height = 0;

    public int borderLeft = 0;
    public int borderRight = 0;
    public int borderTop = 0;
    public int borderBottom = 0;

    public int paddingLeft = 0;
    public int paddingRight = 0;
    public int paddingTop = 0;
    public int paddingBottom = 0;

其中的width, height就是我们需要的数据, 可以拿它们来计算sprite的矩形面积

在UIAtlas中加入代码中添加代码, 计算该图集的占有率.

    public float Occupancy()
    {
        ulong usedSurfaceArea = 0;
        for (int i = 0; i < mSprites.Count; i++)
        {
            usedSurfaceArea += (uint)mSprites[i].width * (uint)mSprites[i].height;
        }
        return = (float)usedSurfaceArea / (texture.width * texture.height);
    }

2. 获得图集中的小图

UIAtlasMaker中为我们提供了四种获取图集中图片的方法

2.1 通过spriteName获得

    static public SpriteEntry ExtractSprite(UIAtlas atlas, string spriteName)
    {
        if (atlas.texture == null) return null;
        UISpriteData sd = atlas.GetSprite(spriteName);
        if (sd == null) return null;

        Texture2D tex = NGUIEditorTools.ImportTexture(atlas.texture, true, true, false);
        SpriteEntry se = ExtractSprite(sd, tex);
        NGUIEditorTools.ImportTexture(atlas.texture, false, false, !atlas.premultipliedAlpha);
        return se;
    }

SpriteEntry类继承自UISpriteData, 除了保存该sprite的基本UV信息和基本成员方法之外, 还记录了该sprite的原始图片, 类似于一个暂时的图片缓存

        // Sprite texture -- original texture or a temporary texture
        public Texture2D tex;
        ...

GetSprite方法是通过spriteName来获得该sprite的数据UISpriteData

NGUIEditorTools.ImportTexture方法是修改指定图片的导入设置import settings, 方便图片像素的读写

其中比较重要的是readable, 设置为false的话, 可以节省游戏运行时的内存, 当图像绘制出来之后就释放掉原先图片的缓存

public bool readable;

Description

Is texture data readable from scripts.

Texture has to be set as “readable” in order for Texture2D.GetPixel, Texture2D.GetPixels and similar functions to work. Textures are not set as readable by default.

When texture is not readable, it consumes much less memory, because a system-memory copy does not have to be kept around after texture is uploaded to the graphics API.

2.2 通过指定UISpriteDataTexture2D来获得

static SpriteEntry ExtractSprite(UISpriteData es, Texture2D tex)

2.3 通过指定UIAtlas, 提供一个List<SpriteEntry>来存放图集内部所有的图片信息

static public void ExtractSprites(UIAtlas atlas, List<SpriteEntry> finalSprites)

2.4 通过指定UISpriteDataTexture2D的配置来获取图片

static SpriteEntry ExtractSprite(UISpriteData es, Color32[] oldPixels, int oldWidth, int oldHeight)

3. 将零散的小图打包成一张大图

UIAtlasMaker中有一个方法PackTextures, 可以通过提供的零散小图的信息List<SpriteEntry>和存放被打包后的小图的单一大图Texture2D, 来完成图片打包.

当然, 这只是生成图片而已, 如果要生成UIAtlas, 还需要生成相对于的prefab, materil, spriteList

将小图打包成大图的算法有好几种, 如果在Atlas Maker界面勾选了Unity Packer选项, 则采用Unity提供的图集打包机制Texture2D.PackTextures , 否则采用NGUI提供的算法, 其中又内置了好几种算法:


RectBestShortSideFit, ///< -BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
RectBestLongSideFit, ///< -BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
RectBestAreaFit, ///< -BAF: Positions the rectangle into the smallest free rect into which it fits.
RectBottomLeftRule, ///< -BL: Does the Tetris placement.
RectContactPointRule ///< -CP: Choosest the placement where the rectangle touches other rects as much as possible.

但是UITexturePacker中默认采用的是RectBestAreaFit算法. 如果我们对当前打包图集的算法不是很满意, 可以上juj/RectangleBinPack来获得算法的介绍, 再选择适合的算法.

4. 将图集指向另外一张图集

如果美术做出来的图集不是很符合规范, 或者图集的透明通道很多, 我们可以将不同的稀松的图集合并成大的较为密集的图像, 从而达到降低DrawCall的目的. 那么怎么将UISprite对原先图集的引用修改成新的图集呢?

我们可以手动一个一个拖…但是, UIAtlas中为我们贴心地准备了一个成员replacement, 我们可以直接修改图集的引用. 即一行代码搞定:oldAtlas.replacement = newAtlas

但是需要注意的是, 新旧图集中相同的图片的UISprite名字应该是一样的, 因为UIAtlas中是通过名字来获取对应的信息UISpriteData, 从而正确地绘制出来. 相关的方法签名为public UISpriteData GetSprite(string name)

5. 对小图片的列表进行大小排序

UIAtlasMaker.PackTextures,中sprites.Sort(Compare), 即完成了排序.

sprites是自定义类SpriteEntry列表, NGUI中实现了一个默认比较器Compare:

    static int Compare(SpriteEntry a, SpriteEntry b)
    {
        // A is null b is not b is greater so put it at the front of the list
        if (a == null && b != null) return 1;

        // A is not null b is null a is greater so put it at the front of the list
        if (a != null && b == null) return -1;

        // Get the total pixels used for each sprite
        int aPixels = a.width * a.height;
        int bPixels = b.width * b.height;

        if (aPixels > bPixels) return -1;
        else if (aPixels < bPixels) return 1;
        return 0;
    }

补充: MSDN中对List<T>.Sort 方法的描述

猜你喜欢

转载自blog.csdn.net/jingangxin666/article/details/80711871