作者:EnigmaJJ


 

在Unity中使用NGUI时,为了减少draw call,我们会将美术用到的小图打成一张图集,如图:

unity texturepack unity texturepacker_ATLAS

 

打包图集有很多种方式,这里介绍的是使用TexturePacker工具来打包图集。TexturePacker支持的游戏引擎相当的广泛,主要包含了Unity、Cocos2D、SpriteKit、LibGDX等等,导出的图片格式也十分丰富,可以导出为PSD、PNG、TGA、JPG等等,并且可以通过命令行方式来使用它。

 

在游戏开发过程中,美术的小图资源是不需要存放到游戏工程中的,因为游戏工程中我们只会使用打包之后的图集。例如美术的小图资源存放在UIResources文件夹下,与Assets同级:

unity texturepack unity texturepacker_数据_02

而在UIResources文件夹中存放的每个子文件夹中的小图最终都会被打包成一张图集,例如UIResources下有Battle文件夹,Battle文件夹中的小图如下:

unity texturepack unity texturepacker_unity texturepack_03

最终这些小图就会被打包成一张图集,存放在Assets/Resources/UIAtlas文件夹下:

unity texturepack unity texturepacker_unity texturepack_04

 

这里我们要介绍的就是这样的自动化打包脚本,使用python开发。我们定义的texture_packer.py文件内容如下:

1 import os
 2 
 3 IMAGE_EXT = [".jpg", ".jpeg", ".png", ".tga", ".bmp"]
 4 ATLAS_OUTPUT_PATH = os.path.abspath("..\\..") + "\\Assets\\Resources\\UIAtlas"
 5 def export_atlas(atlas_name, images_path):
 6     file_list = ""
 7     for dir_name in os.listdir(images_path):
 8         path_name = os.path.join(images_path, dir_name)
 9         if not os.path.isfile(path_name):
10             continue
11 
12         # 判断文件类型
13         file_ext = os.path.splitext(path_name)[1].lower()
14         if not (file_ext in IMAGE_EXT):
15             print(file_ext)
16             continue
17 
18         # 有些图片可能是弃用的但是美术不想删掉的,或者是美术用来给程序拼界面辅
19         # 助行的,对于这些图片我们要过滤掉,可以给这些图片名字里加上中文来区分
20         for character in dir_name:
21             if (u'\u4e00' <= character <= u'\u9fff'):
22                 continue
23 
24         file_list = file_list + path_name + " "
25 
26     if not os.path.exists(ATLAS_OUTPUT_PATH):
27         os.makedirs(ATLAS_OUTPUT_PATH)
28 
29     arguments = "--format unity "\
30                 "--max-width 1024 --max-height 1024 "\
31                 "--size-constraints POT "\
32                 "--sheet " + ATLAS_OUTPUT_PATH + "\\" + atlas_name + ".png "\
33                 "--data " + ATLAS_OUTPUT_PATH + "\\" + atlas_name + ".txt "\
34                 + file_list
35 
36     result = os.system("texturepacker " + arguments)
37     if (0 != result):
38         # 打图集失败,可能是图片在指定大小的一张图集中打不下,尝试打成多个图集
39         arguments_multi = "--format unity " \
40                     "--max-width 1024 --max-height 1024 " \
41                     "--multipack " \
42                     "--sheet " + ATLAS_OUTPUT_PATH + "\\" + atlas_name + "_{n}.png " \
43                     "--data " + ATLAS_OUTPUT_PATH + "\\" + atlas_name + "_{n}.txt " \
44                     + file_list
45         os.system("texturepacker " + arguments_multi)

代码的第3、4行我们定义了图片可能的后缀格式以及最终打包后的图集输出到工程中的文件路径。export_atlas函数主要做的事情就是遍历由images_path变量指定的文件路径下所有的文件,如果是我们要找的图片,就添加到待打包的图片列表中(file_list变量)。代码的18到22行我们过滤掉了被废弃以及不需要打包的图片,这些图片的名字中包含了中文。代码的29到34行以及39到44行为最终提交给TexturePacker的打包参数,区别在于后者包含了"--multipack",用于指明可以打成多个图集。

将texture_packer.py文件放在UIResources文件夹中,在包含小图的子文件夹Battle中,新建一个Battle.py,用于真正的执行打包操作,代码如下:

1 import os
2 import sys
3 
4 sys.path.append("..")
5 import texture_packer
6 
7 atlas_name = os.path.basename(__file__)
8 atlas_name = os.path.splitext(atlas_name)[0]
9 texture_packer.export_atlas(atlas_name, os.path.abspath("."))

这个文件的代码很简单,提取文件名作为图集的名称,并将文件所在的文件夹作为参数传递给export_atlas函数。这样,自动化打包脚本就完成了。

 

在打包图集时,我们可以通过"--disable-rotation"参数来禁止sprite的旋转,一般都会使用这个参数,这是因为NGUI默认是不支持旋转的sprite的。但是我们知道,使用旋转功能来打包图集会使得图集更紧凑,有利于节约空间,特别是对于使用旋转功能可以打包到一张图集中 vs 不使用旋转功能需要打包到两张图集中。下面左图是没有启用旋转功能,右图启用了旋转功能,可以发现右图更紧凑,且预留的空间更大:

unity texturepack unity texturepacker_数据文件_05

unity texturepack unity texturepacker_unity texturepack_06

 

为了使用带旋转的图集,我们就需要调整NGUI的源代码:

1. 在UISpriteData.cs中,我们添加以下变量,用于标记sprite是否被旋转:

unity texturepack unity texturepacker_ATLAS_07

 

2. 在NGUIJson.cs的LoadSpriteData(UIAtlas atlas, Hashtable decodedHash)函数中,添加从数据文件中读取sprite是否被旋转:

unity texturepack unity texturepacker_unity texturepack_08

 

3. 接着还是在NGUIJson.cs的LoadSpriteData(UIAtlas atlas, Hashtable decodedHash)函数中,添加sprite数据的处理代码:

unity texturepack unity texturepacker_unity texturepack_09

这里需要说明下,首先就是长和宽,sprite的旋转都是顺时针旋转90度的,这是因为任何其他角度的旋转都不可能产生比旋转90度更小的矩形范围。而在图集的数据文件中,记录的是sprite未旋转的长和宽,因此,在sprite被旋转90度后,我们就需要对换他的长和宽:

unity texturepack unity texturepacker_数据_10

unity texturepack unity texturepacker_unity texturepack_11

对于Padding数据也是如此,数据文件中记录的都是sprite未旋转的值,因此在sprite旋转90度后,left padding的值应该赋值给top padding,依次类推。

 

在这些处理完毕后,sprite数据已经全部正确了,但是NGUI显示的效果仍旧是旋转的sprite,如图:

unity texturepack unity texturepacker_unity texturepack_12

接下来就很简单了,没错,我们只需要:转!回!去!

 

4. 在UISprite.cs的SetAtlasSprite函数中,添加如下代码:

unity texturepack unity texturepacker_unity texturepack_13

以及在UISprite.cs的OnFill函数中,添加如下代码:

unity texturepack unity texturepacker_数据_14

 

OK,大功告成:

unity texturepack unity texturepacker_数据_15