【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

一、前言

嗨,大家好,我是林新发。
之前有同事找我问我能不能通过json逆向切分图集,我之前写过一个python脚本,但是没有存起来,
在这里插入图片描述
直到最近又有同事找我,给我发了jsonpng图集,
在这里插入图片描述
我决定写篇文章讲一下,也方便下次又有同事问我的时候直接丢出这篇文章~

二、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

如果在pythonimport 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
在这里插入图片描述

关于PILAPI可以参见官方文档: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的人,共勉~

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
### 回答1: TexturePacker是一个强大的工具,用于制作和编辑图像的精灵表或图集,这使得游戏开发者可以有效地管理和提高游戏的性能。TexturePacker还提供了一种方便的方法,通过拆分图集,将大型图像资源分解为更小的部件,以便在游戏中更好地使用。 拆分图集可以通过多种方式实现,其中一个流行的方法是使用TexturePacker的命令行选项。通过使用命令行选项,可以自动化拆分图集的过程,并根据需要生成多个子图像资源。这对于大型游戏项目特别有用,因为游戏需要处理大量的图像资源,而拆分图集可以大大减少应用程序的加载和处理时间。 使用TexturePacker进行图集拆分时,可以选择多种拆分选项。例如,可以将图像资源按行或列分割,在固定的边框或网格上分割,或使用自定义割边框。这意味着开发者可以完全控制拆分过程,以确保生成最优化的子图像资源。 总之,TexturePacker图集拆分功能提供了一种完全可定制的方法,以在游戏中轻松管理和加载大量图像资源。它不仅可以提高游戏的性能和加载速度,还可以减轻开发者的负担,使其更专注于游戏的核心功能。 ### 回答2: TexturePacker是一款常用的纹理打包工具,可以将多个小图合并成一张图集进行优化,提高游戏或应用的性能和加载速度。而拆分图集则是将一个大的图集按照指定的规则进行切分,分离出多张小的图集。 使用TexturePacker拆分图集可以使得游戏或应用更加灵活,可以根据场景需要加载不同的图集,减少资源浪费。同时也可以方便地进行图集的维护和优化,提高整个项目的可维护性和效率。 拆分图集的方法有多种,可以按照文件名、大小、引用次数等规则进行切分。在TexturePacker工具中,可以通过选择“多文件输出”来实现拆分图集功能,选择合适的规则进行配置即可。在导出图集时,生成多张小的图集,每张图集包含指定的纹理,可以根据需要分别加载。 需要注意的是,拆分图集虽然提高了灵活性和可维护性,但也会增加文件数量和加载次数,可能会影响到游戏或应用的性能。因此在使用时需要根据实际情况进行权衡和优化。 ### 回答3: TexturePacker是一个非常方便的工具,它可以将多张图片合并成一个大的图集,以提高游戏或应用程序的性能和加载速度。但是,当图集非常大时,可能会导致内存占用过高或加载速度变慢。在这种情况下,可以使用TexturePacker的拆分功能,将大图集分成多个小图集。 拆分图集的步骤非常简单。首先打开TexturePacker,选择要拆分的图集并点击“Split”按钮。然后,选择一个合适的输出目录和文件名前缀,并定义拆分的数量和方式。可以按像素大小或图像数量拆分,并设置间隙和边距。最后,单击“Publish”按钮,TexturePacker会根据你的设置自动拆分图集生成多个小图集。 拆分后,你的代码需要进行相应的更改,以便正确加载拆分后的图集。在访问图集时,你应该使用一个列表或数组,并遍历所有拆分的图集,并将它们合并成一个最终的纹理。这可以通过loadTextures方法或类似方法实现,其中,你需要指定分别加载每个小图集的路径和文件名,并将它们组合成一个单个的纹理开发包。 总之,使用TexturePacker拆分图集是提高游戏或应用程序性能和加载速度的好方法。虽然拆分后的图集需要一些额外的代码来加载和管理,但这可以保证你的应用程序在运行时更有效率并降低内存占用。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值