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 通过指定UISpriteData
和Texture2D
来获得
static SpriteEntry ExtractSprite(UISpriteData es, Texture2D tex)
2.3 通过指定UIAtlas
, 提供一个List<SpriteEntry>
来存放图集内部所有的图片信息
static public void ExtractSprites(UIAtlas atlas, List<SpriteEntry> finalSprites)
2.4 通过指定UISpriteData
和Texture2D
的配置来获取图片
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
方法的描述