Sprite Jitter After Trimming — Keep Offsets and Pivots
A character that stood still before packing and wobbles after it has lost its trim offsets: each frame was cropped to its visible pixels, and the engine now places or pivots every crop on its own. The fix is to keep each frame's original size and offset, or to move the pivot along with the crop.

- Godot 4Verified· Godot 4.7.2
- Unity 6Verified· Unity 6000.5.3f1
- Phaser 3 / 4Verified· Phaser 3.90 + 4.2
- PixiJS 8Verified· PixiJS 8.21
- Aseprite JSONVerified· Phaser load.aseprite · PixiJS 8.21
- .asepriteVerified· Aseprite 1.3.18
- Spine / libGDXVerified· spine-canvas 4.2
- Starling / Sparrow XMLVerified· Phaser 4.2
- LÖVEVerified· LÖVE 11.5
- DefoldBuilt· Defold bob.jar 1.13.1
- GameMakerUNVERIFIED
- CSS spritesVerified· Chromium
- GIF / APNGDecoded· Pillow
What it does
- Trim (keep size and position) stores every frame's original canvas size and where its cropped pixels sat on it.
- Each export writes that offset the way its engine reads it (table below), and the pivot is taken from the full frame canvas, never from the crop.
- Crop, keep position (pivot moves) makes frames really smaller and shifts the pivot by the same amount, so the art stays where it was.
- In the engine runs the trimmed frames were drawn by Godot 4.7.2, Phaser 3.90 and 4.2 and PixiJS 8.21 and compared with the original frames; Unity 6000.5.3f1 read back the pivots on one common anchor.
- If the art itself wobbles, because frames were drawn at slightly different spots, Align frames measures the drift in pixels and fixes it with whole-pixel moves.
How to do it
- Drop the frames, a sheet (then Apply), a GIF or an .aseprite file.
- Set the pivot (P) on one frame and apply it to the whole tag, for example bottom-centre under the feet.
- Play the loop with onion skin (F3) to make sure the art itself does not jump.
- In Pack & Export keep Trim (keep size and position), or pick Crop, keep position for smaller frames, and export.
How each target keeps a trimmed frame in place
| Target | How the offset is kept |
|---|---|
| Godot 4 | AtlasTexture margin = the offset plus the size that was cut away, so every frame keeps its full size |
| Unity 6 | Pivot normalised inside the trimmed sprite rect from the full-canvas pivot (it may lie outside 0–1) |
| Phaser, PixiJS, Aseprite JSON, Generic JSON | spriteSourceSize (the offset) and sourceSize (the full size), TexturePacker style |
| Spine / libGDX .atlas | offsets: x from the left, y from the bottom of the original frame, plus the original size |
| Starling / Sparrow XML | frameX / frameY (negative offsets) with frameWidth / frameHeight |
| Defold | Full-canvas frame images, so there is no offset to restore |
| CSS sprites | Never trimmed: the element's box is the stored rectangle |
| GameMaker strips | Every frame drawn on one cell with its pivot on the origin (UNVERIFIED in GameMaker) |
Engine exports and how each was checked
"Verified" means the exported files were loaded by the real engine or tool on real CC0 assets and the result was compared with the source. Anything that was not loaded is labelled as such.
| Target | Files | Check |
|---|---|---|
| Godot 4 | .tres SpriteFrames · .tscn AnimatedSprite2D · .png.import | Verified Godot 4.7.2Loaded by Godot; every animation frame drawn and compared with the source file. The scene uses Nearest filtering; rotation is never used. |
| Unity 6 | .unity.json · Editor/NerulioSpriteImporter.cs · AnimationClip | Verified Unity 6000.5.3f1Batch mode: sprite rects, pivots, pixels, clip keys and durations checked. |
| Phaser 3 / 4 | atlas JSON (multiatlas) · .anims.json | Verified Phaser 3.90 + 4.2Frames and animations drawn by both versions. |
| PixiJS 8 | spritesheet JSON · animations · anchor · meta.scale | Verified PixiJS 8.21Rotated and multi-page atlases included. |
| Aseprite JSON | hash / array · frameTags · slices | Verified Phaser load.aseprite · PixiJS 8.21Tags keep their direction and repeat count. |
| .aseprite | layers · tags · durations · slices | Verified Aseprite 1.3.18Opened and rendered by the Aseprite command line. |
| Spine / libGDX | .atlas | Verified spine-canvas 4.2Every region drawn by the Spine runtime, rotated and trimmed; libGDX itself was not run. |
| Starling / Sparrow XML | XML per page | Verified Phaser 4.2Phaser 3.90 draws trimmed XML frames wrongly (its own bug); the "Sparrow XML (Phaser 3)" preset turns trim off and passes in 3.90 and 4.2. |
| LÖVE | Lua quads · nerulio_atlas.lua · main.lua | Verified LÖVE 11.5Every frame drawn and every animation stepped through the shipped helper. |
| Defold | .atlas · .tilesource | Built Defold bob.jar 1.13.1Built by Defold's command-line builder; the built texture's UVs were read back. |
| GameMaker | spr_<name>_stripN.png · gamemaker.json | UNVERIFIEDNot loaded in GameMaker (no headless GameMaker). The strips are cut the way its strip importer cuts them. |
| CSS sprites | .css · preview .html | Verified ChromiumEvery class drawn from the shipped stylesheet. |
| GIF / APNG | one file per animation | Decoded PillowEvery frame and delay decoded again after export; APNG is exact. |
Evidence
Exports: 49 runs in the real engines on real CC0 assets, 47 pass — the 2 failures are Phaser 3.90's trimmed-XML bug, which the Phaser 3 preset avoids. Packer head-to-head on 5 real frame sets (CodeAndWeb free web packer, GAPTools, free-tex-packer): smallest sheet on all 5 with rotation allowed, 4 of 5 without; every frame restored pixel-exact.
Limits
- Polygon (mesh) packing is not offered: none of the verified engines draws a mesh atlas from exported data.
- GameMaker strips are UNVERIFIED in GameMaker itself.
- Scale variants (@0.5x–@4x) use nearest-neighbour only.
- Trimmed Starling XML is drawn wrongly by Phaser 3.90; use the "Sparrow XML (Phaser 3)" preset there.
Questions
Why did my frames wobble after another packer trimmed them?
Either the offset was never written, or it was written but ignored, or the pivot was set per cropped rectangle, so a "centre" pivot moved with each crop. Pivots measured on the full canvas avoid all three.
Should I just turn trim off?
That works and costs atlas space. The Studio does it only where it has to: CSS sprites never trim, because an element's box is the stored rectangle and a crop would lose its offset.
Why are some Unity pivots outside 0–1?
Unity pivots are relative to the sprite rect. For a trimmed frame the rect is the crop, so a pivot measured on the full canvas can land outside it, and that is exactly what keeps the frames aligned.
Godot uses only one offset per node. Does that break it?
AnimatedSprite2D has one offset per node, so the scene uses the first frame's pivot. The trim itself is kept per frame by AtlasTexture margins, and every pivot is kept in the resource metadata.
Is it free, and do I need an account?
Everything on these pages is free and works without an account. There is no watermark and no export that is held back.
Are my sprites uploaded?
No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.
Does it need Aseprite or an engine plugin installed?
No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).