How to fix a sprite sheet that cuts in the wrong place
Frames come out shifted, clipped along one edge, or carrying a sliver of the sprite next door. Nudging the grid by a pixel and looking again is the slow way to do this. One comparison narrows it to a single cause in about ten seconds.
1. Compare the first frame with the last
Cut the sheet with whatever numbers you have and look at exactly two frames: the top-left one and the bottom-right one. Which of these you see decides everything that follows.
The first frame is right and the error grows across the sheet. The last frame is badly wrong, the middle ones mildly. That is a wrong cell size — a fraction of a pixel of error per cell, added up. Almost always it means the sheet has spacing between frames and the cell size was worked out by dividing.
Every frame is off by the same amount, including the first. Shift the whole grid and they all line up together. That is a margin: a border around the outside of the sheet that nothing accounted for.
The error follows no pattern. Some frames land perfectly, others are nonsense, and no single grid fixes them. The sheet is not a grid — it is a packed atlas, and there is no cell size to find.
Every frame is cut perfectly and the animation still looks wrong. This is the one that costs people an afternoon, because the cut is not the problem and re-cutting cannot fix it. Section 4.
2. Growing error: the cell size is wrong
The instinct is to divide the sheet width by the number of columns. That is right only when the frames are packed edge to edge with nothing between them, and sheets exported for tilesets and UI usually are not.
Four 32-pixel frames with a 4-pixel gap between them make a sheet 140 pixels wide — 128 of sprite and 12 of gap. Divide 140 by 4 and you get 35, which is wrong by three pixels per cell in a way that hides itself at first:
frame real cut at 35
0 0 - 31 0 - 34 3px of gap
1 36 - 67 35 - 69 1px early
2 72 - 103 70 - 104 2px early
3 108 - 139 105 - 139 3px early, into frame 2Frame 0 looks nearly fine. Frame 3 has three pixels of its neighbour down the left edge. That growing error is the diagnosis — nothing else produces it.
The arithmetic that does work:
cell width = (sheet width − margin×2 − spacing×(columns−1)) ÷ columnsIf that division does not come out as a whole number, one of the three numbers is wrong. A cell size is always a whole number of pixels, so a fractional answer is the cheapest possible check that you have guessed the spacing or the column count incorrectly. Try the next likely spacing value rather than rounding.
When you do not know any of the numbers, work backwards from the frame instead. Sprite sizes are conventional — 8, 16, 24, 32, 48, 64 — so divide the sheet width by each in turn and see which gives a whole column count. Sprite Sheet Cutter guesses this on load and draws the cut lines over the sheet, which makes a one-pixel error visible instead of theoretical.
3. Constant error: a margin, or no grid at all
A constant offset is a margin. Some exporters put a border around the whole sheet, usually one or two pixels, sometimes only on the top and left. It shifts every frame identically, which is why the first frame being wrong by exactly as much as the last is the fingerprint. Set the margin rather than shrinking the cells to compensate — compensating puts the error back as soon as you change anything else.
Margin and spacing are separate numbers and tools disagree about the words. Margin is the border outside the whole grid; spacing is the gap between neighbouring frames. Phaser calls them margin and spacing, Godot's TileSet calls them margin and separation, and some packers call spacing padding.
No pattern at all means it is not a grid. A packed atlas puts frames wherever they fit, at whatever size each one needs, and there is no cell size that describes it. Trying grid values on one is guaranteed to fail.
For those, stop guessing and read the layout off a file. Aseprite writes a JSON beside the sheet that states every frame's rectangle exactly; so do TexturePacker and most packers. Import it and there is no detection step at all — Sprite Sheet Cutter takes the PNG and the JSON together and cuts on the rectangles the file names, tags and all. Failing that, automatic detection finds islands of non-transparent pixels, which works well when the frames do not touch.
4. Cut correctly, still wrong when it plays
The frames are right. You have checked them one by one. Played back, the character still jitters — bobbing a pixel or two, or drifting sideways across the cycle.
Trimming is the usual cause. Trimming shrinks each frame to its own visible pixels, so a frame where the character raises an arm comes out taller than one where they do not. Different sizes mean the origin sits somewhere different in each, and the sprite moves even though the animation does not. The frames are not cut wrong; they have been made incomparable.
Either turn trimming off, so every frame keeps the full cell, or keep trimming and carry the original size with it. That second option is what a manifest's sourceSize and spriteSourceSize fields are for: they say how big the frame was before it was trimmed and where the kept pixels sat inside it, so the engine can put it back. A trimmed sheet whose manifest lost those numbers is the same failure with an extra step.
The other cause is the origin. Frames all the same size, cut perfectly, and the character still moves — because they are not drawn in the same place inside the cell, and the sprite is anchored to the centre. A character stood towards the left of one cell and the middle of the next will slide, and no amount of re-cutting changes that. Fix it in the art by lining the frames up in the source document, or set the origin somewhere meaningful — feet for a walk cycle, not the centre of a box.
Sprite Animation Previewer is the fastest way to tell these apart, because a jitter is obvious in motion and invisible in a contact sheet.
5. The engine may be cutting it again
You cut the sheet correctly, then something downstream cut it a second time.
Godot
A Sprite2D slices with Hframes and Vframes only — there is no spacing field, so a sheet with gaps has to be re-exported without them or used through an atlas. A TileSet does have margin and separation, and they have to match the sheet.
Unity
Sprite Mode Multiple, then the Sprite Editor's Slice panel: Grid By Cell Size with the right Pixel Size, Offset and Padding. Its own defaults are what re-slices a good sheet badly, and it remembers them per texture.
Phaser
load.spritesheet takes frameWidth, frameHeight, margin and spacing. Pass the same numbers the sheet was built with. For a packed atlas use load.atlas with the JSON instead.
Common problems
The first frames are fine and the last ones are badly wrong
A wrong cell size, accumulating. The sheet has spacing between frames that the division ignored.
Every frame is shifted by the same one or two pixels
A margin around the sheet. Set it rather than shrinking the cells.
The cell size comes out with a decimal
One of the three numbers is wrong. A cell is always a whole number of pixels.
No grid works — some frames land, others do not
It is a packed atlas. Import the exporter's JSON or use auto-detect.
Every frame is right but the sprite bobs as it plays
Trimming made the frames different sizes, so the origin moves. Turn it off, or keep the source size in the manifest.
The last row is short or half empty
The frame count is not a multiple of the column count. That is normal — skip the empty cells rather than changing the grid to swallow them.
It cut fine here and wrong in Unity
The Sprite Editor sliced it again with its own settings. Match its Pixel Size, Offset and Padding to the sheet.