Skip to content

Sprite Sheet Maker — Pack Frames into an Atlas

Drop frame images, a GIF or an .aseprite file and get a tightly packed sprite sheet with the data file your engine reads — packed live in a worker as you change settings.

A sprite sheet puts many frames on one image so a game loads one texture instead of dozens; a packed atlas also writes a data file that names every frame and gives its rectangle. Drop numbered PNGs, a GIF or an .aseprite file, pick your engine's preset, and Nerulio packs the frames in your browser (MaxRects, trim on, 2 px padding by default) and exports the PNG with that engine's data file. For a plain grid of equal cells, the classic maker is linked further down.

Drop frame images, a GIF or an .aseprite file to pack
Runs in your browser. Files are not uploaded.
Studio Pack & Export: the packed atlas page with every frame outlined, the used area and the export targets
The real Studio with a CC0 asset (OpenGameArt).
Exports to
  • 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

Grid sheet or packed atlas

A grid sheet gives every frame the same cell, so an engine finds frame n by arithmetic: column = n mod columns, row = n ÷ columns, rounded down. It needs no data file, but every cell is as large as the largest frame, transparent border included.

A packed atlas trims each frame to its visible pixels and fits the pieces wherever they fit. Positions are no longer predictable, so the packer writes a data file: for each frame its name, where the stored pixels are on the page (frame), where they sat on the original canvas (spriteSourceSize), the original canvas size (sourceSize) and whether the piece lies turned by 90° (rotated). The engine uses those numbers to draw the frame at full size, so a trimmed frame does not move.

Two settings protect the edges. Shape padding leaves transparent pixels between frames, and extrude repeats each frame's outermost pixels around it, so a texture filter that samples just past an edge reads the same colour instead of a neighbour. Rotation saves a little area, but only some engines turn a rotated frame back; Nerulio's presets allow it only for PixiJS and Spine, where the engine runs drew rotated frames correctly.

Page
One atlas image. Frames that do not fit the maximum size go on further pages (multipack, up to 64).
Trim
Cutting the transparent border off a frame and recording the offset, so it is drawn at its old place.
Shape padding
Transparent pixels between two frames: 2 px by default.
Extrude
Copies of a frame's edge pixels placed around it: 0 by default, 1 in the LÖVE preset.
Alias
Frames with identical visible pixels share one stored region.

Example: six 40 × 29 running frames

The six frames of a CC0 ninja run cycle (40 × 29 px each, mostly transparent), packed by Nerulio with the Phaser preset (trim on, padding 2, no rotation) and compared with other layouts:

layout                           page        area        vs strip
horizontal strip, 6 × 40 px      240 × 29    6,960 px²   100 %
packed, trim off, padding 2       82 × 91    7,462 px²   107 %
packed, trim on, padding 2        38 × 50    1,900 px²    27 %
packed, trim on, rotation         33 × 57    1,881 px²    27 %

visible pixels (6 bounding boxes)             1,591 px²
share of the trimmed page used     1,591 / 1,900 = 0.84

"run_0": { "frame": {"x":0,"y":34,"w":18,"h":15},
           "spriteSourceSize": {"x":10,"y":10,"w":18,"h":15},
           "sourceSize": {"w":40,"h":29}, "rotated": false, "trimmed": true }

Only 18 × 15 pixels of run_0 are stored; the engine draws them 10 px right and 10 px down inside a 40 × 29 box, exactly where they were. Trim does the saving here: without it the packed page is slightly larger than a plain strip, because padding is added between frames.

What it does

  • MaxRects (five rules), Skyline and Guillotine; "try all" keeps the smallest page.
  • Trim that keeps offsets, alias for identical frames, extrude, border and shape padding, POT / square / fixed sizes, up to 64 pages.
  • Scale variants @0.5x–@4x with nearest-neighbour, optional premultiplied alpha.
  • Colour-exact PNGs: frames are decoded in JavaScript, not through a canvas, so colours and alpha do not drift (interlaced PNGs fall back to the browser decoder, and the stage says so).

How to do it

  1. Drop your frames (a folder of numbered PNGs works) or a GIF / .aseprite file.
  2. Pick your engine's preset; the atlas repacks live.
  3. Check the used %, pages and the frame list; change trim, padding or size if needed.
  4. Press Export for your engine.

What each engine receives, and how it loads the sheet

Keep every PNG next to its data file: the data names its image (meta.image in JSON, imagePath in XML, the page line in a Spine .atlas).

FileWhat it is for
run.png + run.json (Phaser 3 / 4)this.load.atlas("run", "run.png", "run.json"), then this.add.sprite(x, y, "run", "run_0"); details on the Phaser atlas page.
run.png + run.json (PixiJS 8)const sheet = await Assets.load("run.json"), then sheet.textures["run_0"] or sheet.animations["run"]; see PixiJS spritesheet JSON.
run.tres + run.tscn (Godot 4)A ready SpriteFrames resource and scene instead of a bare atlas; see sprite sheets in Godot.
run.unity.json + importer script (Unity 6)An editor script slices the texture and builds clips; see sprite sheets in Unity.
run.lua, run.atlas, run.xml, run.cssLÖVE quads, a Defold or Spine atlas, Sparrow XML or CSS classes: LÖVE, Defold, Spine, CSS sprites.

Check the result

  • Read the used share in Pack & Export: 0.84 means 84 % of the page is visible frame area. A low figure usually means trim is off or one frame is much larger than the rest.
  • Hover the rows of the Packed frames list: each shows canvas → stored size, its page and flags such as trimmed, rotated or "same as" (an alias).
  • In the engine, draw one trimmed frame at the same point as its untrimmed neighbour; if it is shifted, the loader ignores spriteSourceSize (see troubleshooting).

Troubleshooting

SymptomLikely causeHow to checkFix
Frames jump or wobble while the animation playsTrimmed frames drawn without their offset: the loader or your code uses frame but ignores spriteSourceSize and sourceSizeDraw two frames with different trims at one point; they should line upUse a loader that reads trim data, or pack with trim off (larger page); more in jitter after trimming
Thin lines or colour fringes at frame edges when the sprite is scaled or moves by fractions of a pixelThe texture filter reads pixels just outside the frame: a neighbour, or transparent paddingZoom in on the edge: the line has a neighbour's colour, or is see-throughKeep shape padding at 2 or more, add extrude 1, use nearest filtering for pixel art; see atlas padding
Some frames appear sideways, mirrored or clippedRotation was allowed for an engine or loader that does not turn frames backThose entries have "rotated": truePack with rotation off; Nerulio's Godot, Unity, Phaser, LÖVE, XML and CSS exports refuse a rotated pack
An error says sprites do not fit the pageOne frame plus its padding and extrude is larger than the maximum page sizeThe message names the frame and its sizeRaise the maximum size (up to 16384 px), allow rotation where the engine reads it, or scale that frame down

Other ways to do it

OptionWhen it is the better choice
A plain grid sheet (the classic maker below)Every frame already has the same size and your engine slices by cell, for example GameMaker strips or Godot's Add Frames from Sprite Sheet. There is no data file to keep in sync.
Aseprite's own sheet export (--sheet-pack, --trim, --merge-duplicates on its command line)The art lives in Aseprite and its JSON with tags and durations is all you need. It writes no engine bundles such as Godot SpriteFrames.
TexturePacker ProYou need polygon packing, GPU texture formats or a command line in a build pipeline; measured against Nerulio on the TexturePacker alternative page.
The engine's own packer (Defold atlas, Spine's texture packer)The engine packs separate images at build or export time anyway; then you only need the frame images, for example from a sheet split into PNG frames.

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.

TargetFilesCheck
Godot 4.tres SpriteFrames · .tscn AnimatedSprite2D · .png.importVerified 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 · AnimationClipVerified Unity 6000.5.3f1Batch mode: sprite rects, pivots, pixels, clip keys and durations checked.
Phaser 3 / 4atlas JSON (multiatlas) · .anims.jsonVerified Phaser 3.90 + 4.2Frames and animations drawn by both versions.
PixiJS 8spritesheet JSON · animations · anchor · meta.scaleVerified PixiJS 8.21Rotated and multi-page atlases included.
Aseprite JSONhash / array · frameTags · slicesVerified Phaser load.aseprite · PixiJS 8.21Tags keep their direction and repeat count.
.asepritelayers · tags · durations · slicesVerified Aseprite 1.3.18Opened and rendered by the Aseprite command line.
Spine / libGDX.atlasVerified spine-canvas 4.2Every region drawn by the Spine runtime, rotated and trimmed; libGDX itself was not run.
Starling / Sparrow XMLXML per pageVerified 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ÖVELua quads · nerulio_atlas.lua · main.luaVerified LÖVE 11.5Every frame drawn and every animation stepped through the shipped helper.
Defold.atlas · .tilesourceBuilt Defold bob.jar 1.13.1Built by Defold's command-line builder; the built texture's UVs were read back.
GameMakerspr_<name>_stripN.png · gamemaker.jsonUNVERIFIEDNot loaded in GameMaker (no headless GameMaker). The strips are cut the way its strip importer cuts them.
CSS sprites.css · preview .htmlVerified ChromiumEvery class drawn from the shipped stylesheet.
GIF / APNGone file per animationDecoded 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.

Versions and sources

The sizes above are the output of Nerulio's packer on the six ninja frames of the repository's test corpus, the same numbers as the 2026-09-23 head-to-head. Exports were loaded in Godot 4.7.2, Unity 6000.5.3f1, Phaser 3.90 and 4.2, PixiJS 8.21, spine-canvas 4.2 and LÖVE 11.5; Defold bob.jar 1.13.1 built the Defold files. The other tools' option names follow their documentation.

Official documentation used

Classic tool

A plain uniform grid sheet (fixed cells, rows × columns) is still made by the classic sprite sheet maker.

Open the classic tool →

Questions

Can it make a plain grid sheet (rows × columns)?

The Studio packs tightly. For a uniform grid with fixed cells, the classic sprite sheet maker linked below still does that.

Are the frames pixel-exact?

Yes. On five real frame sets every frame restored from the atlas equals its source file pixel for pixel.

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).