如何把一张张图片合成一张精灵图
你的动画软件导出一堆编号 PNG,你的引擎要的是一张贴图。 把它们拼起来很容易;把它们拼成引擎还能再拆回去的样子, 细节都在这一步里。
1. 为什么非要合成一张图
两个理由,其中只有一个是「整洁」。真正的理由是draw call:渲染器每换一次贴图,就得向 GPU 发一个新批次。 五十个精灵放在五十个文件里,就是每帧五十次切换; 同样这五十个精灵放在一张图里,可以在一个批次里画完。 在任何移动平台上,这个差别就是整个帧预算。
第二个理由是加载。一次 HTTP 请求或一次文件读取胜过五十次, 在网页上尤其如此——每一个请求都自带一份延迟。
2. 毁掉精灵图最多的,是帧顺序
你的导出器写出了 run_1.png 到 run_12.png。 按文本排序——文件对话框、shell 通配符和多数打包工具干的就是这件事—— 顺序会变成 1、10、11、12、2、3…… 这样打包出来,动画的播放顺序就是一团乱麻, 而且在你按下播放之前,这张图看上去没有任何不对。
两个修法,你两个都要。导出时给序号补零:run_01.png 在哪儿、什么时候排序都是对的。 另外用一个按数字而不是按字母排序的工具, 这样没补零的一批也照样出来是对的。精灵图生成器在载入时就做数字排序,之后你还可以把任意一帧拖到新位置。
3. 单元格尺寸:由最大的那一帧说了算
网格图集里每个格子都一模一样,尺寸取自这批帧里最大的那一张。 这不是偷懒——正是它让这张图可以被索引。 动画播放器找第 n 帧靠的是算术:x = (n % 列数) × 单元格宽。 格子尺寸一旦不一致,这条就不成立了,你得改用清单。
所以真正有意思的问题是:较小的帧在格子里摆在哪。居中是默认值,也适合特效、投射物和任何对称的东西。底部居中才是角色需要的: 走路循环各帧的高度会有差别——只要你加了上下起伏,就一定会—— 居中会让精灵在播放时上下抖,因为每一帧里脚落的位置都不一样。 对齐到格子底部就把它们钉住了。
如果这些帧带着大量空白边,打包前先把每一帧裁到可见像素, 单元格尺寸会缩小,整张图往往也会小一大截。 代价是每一帧的原点位置变了,所以只有当读取方会从清单里取逐帧偏移时才裁。
4. 间距,以及精灵边上那条杂线
如果一个精灵的某条边上露出了隔壁帧的一条细边,那是贴图渗色。 它发生在 GPU 以小数坐标采样这张图的时候——被缩放的精灵、被旋转的精灵、 停在半个像素上的摄像机——采样点越过了帧的边界。
格子之间留一两个透明像素的间距就能彻底解决: 采歪了也只会采到透明,而不是隔壁的精灵。 如果整张图本身还会被缩放,就在图的四周也留同样的外边距。 它花掉一点面积,省下一下午莫名其妙的调试。
另一个常被问到的选项是 2 的幂尺寸。它现在远没有以前重要—— 桌面和现代移动 GPU 都接受任意尺寸—— 但对较老的移动平台、某些压缩贴图格式, 以及任何你打算做 mipmap 的贴图,它仍然值得设上。
5. 网格还是紧凑排布
帧属于同一段动画时用网格。 格子统一、按序号索引、严格来说不需要清单—— Godot 的 AnimatedSprite2D、Unity 的 Grid By Cell Size 切片器、 Phaser 的 load.spritesheet() 要的都是这个。
精灵彼此无关且大小不一时用紧凑排布—— UI 图标、地块、一次性的道具。 打包器会把它们拼在一起以尽量少浪费空间, 这意味着没有网格可供索引,那份记录每帧坐标的 JSON 清单也就不是可选项。 用 this.load.atlas() 或者你的引擎里对应的方法加载它。
别把一段动画拿去紧凑排布。 省那几百 KB,不值得失去「播放第 4 到第 9 帧」这种说法的能力。
6. 在引擎里把它切回去
Godot:把 PNG 指给 AnimatedSprite2D 的 SpriteFrames 资源,打开 Select Frames, 填入报出来的列数和行数。像素画记得把导入过滤设成 Nearest, 否则精灵一被缩放,引擎就会把每一帧弄糊。
Unity:Sprite Mode 选 Multiple, 然后 Sprite Editor → Slice → Grid By Cell Size, 填入单元格尺寸和你用过的间距。Filter Mode 选 Point (no filter),Compression 选 None。
Phaser:网格图集用 this.load.spritesheet(key, url, { frameWidth, frameHeight }) 加载。如果你留了间距,在同一个配置对象里传 spacing 和 margin——不传,正是带间距的图被切偏一个像素的常见原因。
常见故障
动画的播放顺序是乱的
没补零的名字被按字母排序了。重新做数字排序,或者给序号补零后重新导出。
每一帧的某条边上都露出下一帧的一条细边
贴图渗色。加一到两个像素的间距——并且告诉引擎你加了, 否则切片器会正好偏这么多。
角色走路时上下抖
高度不一的帧在格子里居中了。改成底部居中对齐。
就几个精灵,图却大得离谱
有一帧特别大,把所有格子的尺寸都撑上去了。把帧裁一下,或者把那个异类单独放一张图。
引擎在紧凑排布的图里找不到帧
那里没有网格可找。紧凑排布的图需要 JSON 清单和一个图集加载器。