Toolkitly

怎么修精灵表切图错位

切出来的帧整体偏了、边上被切掉一条,或者带进了隔壁精灵的一条边。 把网格往旁边挪一个像素再看一眼,是解决这件事最慢的办法。 有一个对比能在十秒内把范围缩到一个原因上。

1. 拿第一帧和最后一帧比

用你手上的数先切一遍,然后只看两帧:左上角那一帧,和右下角那一帧。 看到的是下面哪一种,决定了后面所有的事。

第一帧是对的,越往后错得越厉害。最后一帧错得很离谱,中间的错一点点。这是单元格尺寸算错了—— 每一格差那么零点几个像素,累加起来的。几乎总是因为这张图的帧之间有间距, 而单元格尺寸是拿除法算出来的。

每一帧都偏一样多,包括第一帧。把整个网格挪一下,它们会一起对齐。这是外边距: 图的最外面有一圈边,而没有人把它算进去。

错得没有规律。有几帧刚好对上,另外几帧完全不知所云,而且没有任何一套网格能同时修好它们。 这张图不是网格——它是紧凑排布的图集,根本不存在一个单元格尺寸。

每一帧都切得完全正确,动起来还是不对。这一种最费时间,因为问题不在切图上,重切多少次都没用。见第 4 节。

2. 误差越来越大:单元格尺寸错了

第一反应是拿图宽除以列数。只有当帧与帧之间严丝合缝、中间什么都没有的时候, 这么算才是对的——而给地块和 UI 导出的图集,通常不是这样。

四个 32 像素的帧、每两帧之间空 4 像素,这张图宽 140 像素——128 是精灵,12 是空隙。 拿 140 除以 4 得到 35,每一格差 3 个像素,而且它一开始藏得很好。 下面三列分别是帧号、这一帧实际所在的位置、按 35 切出来的位置:

0    0 - 31        0 - 34      多带 3px 空隙
1    36 - 67       35 - 69     早了 1px
2    72 - 103      70 - 104    早了 2px
3    108 - 139     105 - 139   早了 3px,切进第 2 帧

第 0 帧看着几乎没问题。第 3 帧的左边带着隔壁三个像素。 这种越往后越大的误差本身就是诊断结论——没有别的原因会产生它。

能算对的算法是:

单元格宽 =(图宽 − 外边距×2 − 间距×(列数−1))÷ 列数

如果这一除除不尽,那这三个数里一定有一个是错的。单元格尺寸永远是整数个像素,所以算出小数是最省事的一次自检, 说明你把间距或者列数猜错了。这时候该去试下一个可能的间距值,而不是四舍五入。

如果这三个数你一个都不知道,那就反过来从帧的尺寸推。 精灵尺寸是有惯例的——8、16、24、32、48、64—— 拿图宽依次去除,看哪一个能得到整数列。精灵图切割工具在载入时就会猜一次,并且把切割线画在图上, 这样差一个像素是看得见的,而不是靠想。

3. 误差是常数:外边距,或者压根不是网格

恒定的偏移就是外边距。有些导出工具会在整张图外面留一圈边,通常一到两个像素,有时候只在上边和左边。 它让每一帧偏移同样多,所以「第一帧错的量和最后一帧一模一样」就是它的指纹。 要去设那个外边距,而不是把单元格改小去凑—— 凑出来的对齐,你一改别的参数,误差立刻就回来了。

外边距和间距是两个不同的数,而且各家工具的叫法不一样。 外边距是整个网格外面那一圈;间距是相邻两帧之间的空。 Phaser 叫 marginspacing, Godot 的 TileSet 叫 marginseparation, 还有些打包工具把间距叫作 padding

完全没有规律,说明它不是网格。紧凑排布的图集是把每一帧塞到能塞下的地方去,每帧多大就占多大, 根本不存在一个能描述它的单元格尺寸。在这种图上试网格参数,注定失败。

对这种图,别再猜了,让文件告诉你答案。 Aseprite 会在图集旁边写一份 JSON,逐帧写明矩形的精确位置; TexturePacker 和大多数打包工具也一样。 导进去就完全不需要「识别」这一步——精灵图切割工具可以同时接收 PNG 和 JSON,直接按文件里写的矩形切,连动画标签一起读进来。 实在没有 JSON,就用自动识别去找非透明像素的连通块,帧不相邻的时候效果很好。

4. 切对了,播放时还是不对

帧是对的。你一帧一帧看过了。播起来角色还是在抖—— 上下浮动一两个像素,或者整个循环里在横向漂移。

通常是裁剪造成的。裁剪会把每一帧缩到它自己可见的像素范围, 所以角色抬手的那一帧会比没抬手的那一帧高。 尺寸不一样,就意味着原点在每一帧里的位置不一样, 于是动画没动,精灵却在动。这些帧不是切错了,是被弄成了不可比较的。

要么把裁剪关掉,让每一帧都保留完整的格子; 要么保留裁剪,但把原始尺寸一起带上。后面这条正是清单里 sourceSizespriteSourceSize 这两个字段的用途: 它们记录这一帧被裁之前有多大、留下的那部分坐落在里面的什么位置, 引擎据此就能把它还原回去。 一张裁剪过、而清单里丢了这两个数的图集,是同一个毛病多绕了一道弯。

另一种原因是原点。所有帧尺寸一致、切得完美,角色还是在动—— 因为它们在格子里画的位置本来就不一样,而精灵是按中心对齐的。 一帧里角色站得偏左、下一帧站在正中,它就会滑, 这跟切图怎么切没有任何关系。 要么回到原稿里把各帧对齐,要么把原点设到一个有意义的位置上—— 走路循环就设在脚底,而不是一个方框的中心。

精灵动画预览器是分辨这两种最快的办法,因为抖动在动起来的时候一眼就能看见, 摊成一排静态图反而看不出来。

5. 引擎可能又切了一遍

你这边切对了,然后下游又自己切了第二次。

Godot

Sprite2D 只有 HframesVframes 两个数—— 没有间距字段,所以带空隙的图要么重新导一份不带空隙的,要么走图集。TileSet 倒是有 marginseparation, 而且必须和图对得上。

Unity

Sprite Mode 设成 Multiple,然后在 Sprite Editor 的 Slice 面板里选 Grid By Cell Size,填对 Pixel SizeOffset Padding。把一张好图重新切坏的,正是它自己那套默认值, 而且它是按每张贴图分别记住的。

Phaser

load.spritesheet 接受 frameWidthframeHeightmarginspacing, 填这张图当初的那几个数。紧凑图集则改用 load.atlas 配 JSON。

常见故障

前面几帧没问题,最后几帧错得很离谱

单元格尺寸错了,误差在累加。这张图的帧之间有间距,而除法把它忽略了。

每一帧都偏了同样的一两个像素

图的外面有一圈外边距。去设它,别靠改小单元格来凑。

算出来的单元格尺寸带小数

那三个数里有一个是错的。单元格永远是整数个像素。

怎么调网格都不行——有的帧对,有的不对

这是紧凑排布的图集。导入导出工具那份 JSON,或者用自动识别。

每一帧都对,但播放时精灵在上下跳

裁剪把各帧弄成了不同大小,原点因此在动。关掉裁剪, 或者让清单里保留原始尺寸。

最后一行是空的或者只有半行

帧数不是列数的整数倍。这很正常——跳过空格子就行, 别为了填满它去改网格。

在这里切是好的,到 Unity 里就不对

Sprite Editor 用它自己的设置又切了一遍。 把它的 Pixel Size、Offset、Padding 和这张图对上。

现在就切一张