【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
文章目录
一、前言
嗨,大家好,我是林新发。
之前有同事找我问我能不能通过json
逆向切分图集,我之前写过一个python
脚本,但是没有存起来,
直到最近又有同事找我,给我发了json
和png
图集,
我决定写篇文章讲一下,也方便下次又有同事问我的时候直接丢出这篇文章~
二、TexturePacker
1、特别说明
首先,本文的python
代码是针对TexturePacker
工具Phaser 3
框架生成出来的图集进行逆向切分,不过只要你懂了原理,其他格式的配置都可以一通百通。
如何知道图集是使用TexturePacker
工具制作的呢?我们可以打开图集同名的配置文件,如果能看到texturepacker
的字样,就说明是使用TexturePacker
工具生成的,例:
2、TexturePacker下载
TexturePacker
官网地址:https://www.codeandweb.com/texturepacker
下载下来后,我们进入bin
目录,可以看到TexturePackerGUI.exe
,双击即可打开,
如下
为了演示,我先使用搞一些散图,然后使用TexturePacker
打个图集出来。
3、准备精灵散图(小图)
以下是我随便搜的一些精灵小图,
4、使用TexturePacker打图集
首先点击界面右侧的 框架
,根据需求选择一个框架,
它支持非常多的框架,如下
我选择的是通用的Phaser 3
,如下,(因为我那两个同事发给我的图集格式都是用的这个-_-,那我就用这个来做演示吧)
接着,我们把小图的整个文件夹拖到界面左侧中,它自动帮我们执行了图集排版,如下,
接着,我们点击发布精灵表
,
填写保存路径,即可生成图集png
和一个json
配置文件,如下
5、json结构分析
其中test.atlas.json
配置表内容如下:
{
"textures": [
{
"image": "test_atlas.png",
"format": "RGBA8888",
"size": {
"w": 309,
"h": 118
},
"scale": 1,
"frames": [
{
"filename": "car.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 128,
"h": 128
},
"spriteSourceSize": {
"x": 0,
"y": 6,
"w": 128,
"h": 116
},
"frame": {
"x": 1,
"y": 1,
"w": 128,
"h": 116
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "bookcase.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 64,
"h": 64
},
"spriteSourceSize": {
"x": 8,
"y": 0,
"w": 55,
"h": 64
},
"frame": {
"x": 131,
"y": 1,
"w": 55,
"h": 64
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "table_tennis.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 48,
"h": 48
},
"frame": {
"x": 131,
"y": 67,
"w": 48,
"h": 48
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "pencil.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 3,
"y": 3,
"w": 42,
"h": 42
},
"frame": {
"x": 188,
"y": 1,
"w": 42,
"h": 42
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "cate.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 5,
"y": 3,
"w": 39,
"h": 41
},
"frame": {
"x": 232,
"y": 1,
"w": 39,
"h": 41
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "stone.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 6,
"y": 4,
"w": 35,
"h": 40
},
"frame": {
"x": 273,
"y": 1,
"w": 35,
"h": 40
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "brinjaul.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 2,
"y": 6,
"w": 42,
"h": 36
},
"frame": {
"x": 188,
"y": 45,
"w": 42,
"h": 36
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "carota.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 5,
"y": 6,
"w": 38,
"h": 36
},
"frame": {
"x": 232,
"y": 44,
"w": 38,
"h": 36
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "bulb.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 6,
"y": 4,
"w": 36,
"h": 40
},
"frame": {
"x": 272,
"y": 44,
"w": 36,
"h": 40
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "pizza.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 4,
"y": 6,
"w": 41,
"h": 34
},
"frame": {
"x": 181,
"y": 83,
"w": 41,
"h": 34
},
"anchor": {
"x": 0.5,
"y": 0.5
}
},
{
"filename": "coconut.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 48,
"h": 48
},
"spriteSourceSize": {
"x": 3,
"y": 10,
"w": 42,
"h": 27
},
"frame": {
"x": 224,
"y": 83,
"w": 42,
"h": 27
},
"anchor": {
"x": 0.5,
"y": 0.5
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:79dd9a57d14f6938f1ea38d2225ac0ce:6ec25dea8a4c4491ba119c28bbdefaf3:2a084c904c80f72428569743fdf0f51d$"
}
}
我们分析一下json
配置的结构,画个图方便一目了然,
(注意:这个json
结构仅针对Phaser 3
这个框架,其他框架的格式需要自行分析)
知道了json
的结构,以及每个字段的含义,我们就可以开始逆向了~
三、图集逆向切分精灵图
1、环境准备
人生苦短,我用python
,不要跟我争辩~
如果你没有python
环境,安装一下,我用的是python 3
。
另外因为要进行图形处理,需要安装Pillow
库(PIL
)。
通过pip
命令进行安装
pip install Pillow
如果在python
中import PIL
没有报错,则说明安装PIL
库成功。
2、python代码:TextureUnpacker.py
这里我先直接上代码,注释我写得比较详细,大家应该能看懂,下文我会讲解核心的步骤,
# TexturePacker图集逆向工具,本代码只针对Phaser 3框架的json配置
# 只要你知道原理,其他配置都可自行分析处理,希望你能有自行修改和优化的能力
# Author: 林新发 https://blog.csdn.net/linxinfa
# Create: 2022-03-24
import os
from PIL import Image
import json
# 封装一个TextureUnpacker类
class TextureUnpacker(object):
@classmethod
def split_with_json(cls, f_json, save_dir=None):
f_json = os.path.abspath(f_json)
if save_dir is None:
save_dir = f_json + '_split'
else:
save_dir = os.path.abspath(save_dir)
# 读取json配置表
f = open(f_json, 'r')
txt = f.read()
dt = json.loads(txt)
f.close()
# 大图集文件名
big_texture_file_name = dt['textures'][0]['image']
# 小图序列
frames = dt['textures'][0]['frames']
# 打开大图
big_img = Image.open(big_texture_file_name)
# 遍历生成小图
for index in range(0, len(frames)):
info = frames[index]
# 解析配置
info = cls.parse_as_json(info)
print(info)
# 小图的保存路径
little_image_save_path = os.path.join(save_dir, info['filename'])
# 生成小图
cls.generate_little_image(big_img, info, little_image_save_path)
@classmethod
def generate_little_image(cls, big_img, info, path):
# 创建小图
little_img = Image.new('RGBA', info['sz'])
# PIL.Image.crop()方法用于裁剪任何图像的矩形部分
# box –定义左,上,右和下像素坐标的4元组
region = big_img.crop(info['box'])
if info['rotated']:
region = region.transpose(Image.ROTATE_90)
# 把裁剪出来的图片粘贴到小图上
little_img.paste(region, info['xy'])
save_dir = os.path.dirname(path)
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 保存
little_img.save(path)
@classmethod
def parse_as_json(cls, info):
"""
"filename": "player_1.png",
"rotated": false,
"trimmed": false,
"sourceSize": { "w": 1, "h": 1 },
"spriteSourceSize": { "x": 0, "y": 0, "w": 1, "h": 1 },
"frame": { "x": 1, "y": 1, "w": 1, "h": 1 }
"""
# 小图宽高
width = info['sourceSize']['w']
height = info['sourceSize']['h']
# 小图矩形信息
frame = info['frame']
# 是否旋转 (顺时针方向90度)
rotated = info['rotated']
if rotated:
# box 定义左、上、右和下像素坐标的4元组
box = (frame['x'], frame['y'],
frame['x'] + frame['h'],
frame['y'] + frame['w'])
else:
box = (frame['x'], frame['y'],
frame['x'] + frame['w'],
frame['y'] + frame['h'])
# 图形在小图中的偏移
x = int((width - frame['w']) / 2)
y = int((height - frame['h']) / 2)
return {
'box': box,
'rotated': rotated,
'sz': [width, height],
'xy': (x, y),
'filename' : info['filename']
}
if __name__ == '__main__':
unpacker = TextureUnpacker()
unpacker.split_with_json('test_atlas.json')
print('done')
3、代码讲解
核心就是封装的TextureUnpacker
类,它做了以下几件事情,其中用到的PIL
库的API
我标注了黄色出来,如下
其中json
配置的小图解析,我封装在parse_as_json
方法中,如下,需要注意rotated
旋转,
生成小图的逻辑封装在generate_little_image
方法中,这个方法中用到了PIL
库的一些API
,
关于PIL
的API
可以参见官方文档:https://pillow.readthedocs.io/en/latest/
这里我帮大家贴以下我用到的API
的文档吧。
3.1、PIL.Image.open方法
打开图像,返回图像对象,
示例:
from PIL import Image
big_img = Image.open('test_atlas.png')
3.2、PIL.Image.new方法
创建image
对象,示例:
from PIL import Image
little_img = Image.new('RGBA', (48, 48))
3.3、PIL.Image.crop方法
裁剪图像的某个矩形区域,示例
from PIL import Image
with Image.open("test_atlas.png") as im:
(left, upper, right, lower) = (20, 20, 100, 100)
im_crop = im.crop((left, upper, right, lower))
3.4、PIL.Image.paste方法
粘贴另一个image
到自身image
上,示例:
this_img.paste(other_image)
3.5、PIL.Image.save方法
讲image
保存为文件,示例:
img_obj.save('test.png')
4、执行python脚本
把python
代码保存为TextureUnpacker.py
,放在图集文件同级目录中,
执行python
脚本,它会解析test_atlas.json
并生成小图精灵,
if __name__ == '__main__':
unpacker = TextureUnpacker()
unpacker.split_with_json('test_atlas.json')
print('done')
执行输出log
如下
{'box': (1, 1, 129, 117), 'rotated': False, 'sz': [128, 128], 'xy': (0, 6), 'filename': 'car.png'}
{'box': (131, 1, 186, 65), 'rotated': False, 'sz': [64, 64], 'xy': (4, 0), 'filename': 'bookcase.png'}
{'box': (131, 67, 179, 115), 'rotated': False, 'sz': [48, 48], 'xy': (0, 0), 'filename': 'table_tennis.png'}
{'box': (188, 1, 230, 43), 'rotated': False, 'sz': [48, 48], 'xy': (3, 3), 'filename': 'pencil.png'}
{'box': (232, 1, 271, 42), 'rotated': False, 'sz': [48, 48], 'xy': (4, 3), 'filename': 'cate.png'}
{'box': (273, 1, 308, 41), 'rotated': False, 'sz': [48, 48], 'xy': (6, 4), 'filename': 'stone.png'}
{'box': (188, 45, 230, 81), 'rotated': False, 'sz': [48, 48], 'xy': (3, 6), 'filename': 'brinjaul.png'}
{'box': (232, 44, 270, 80), 'rotated': False, 'sz': [48, 48], 'xy': (5, 6), 'filename': 'carota.png'}
{'box': (272, 44, 308, 84), 'rotated': False, 'sz': [48, 48], 'xy': (6, 4), 'filename': 'bulb.png'}
{'box': (181, 83, 222, 117), 'rotated': False, 'sz': [48, 48], 'xy': (3, 7), 'filename': 'pizza.png'}
{'box': (224, 83, 266, 110), 'rotated': False, 'sz': [48, 48], 'xy': (3, 10), 'filename': 'coconut.png'}
done
可以看到生成了一个文件夹,
文件夹里就可以看到生成出来的精灵小图啦
完美,收工~
我是林新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity
开发者,希望可以帮助更多想学Unity
的人,共勉~
更多推荐
所有评论(0)