Toolkitly

Sprite Sheet Generator

Combine individual PNG frames into a single sprite sheet — as a grid, an animation strip or a packed atlas — with a JSON manifest to match. Free, no sign-up, and every pixel stays in your browser.

Nothing is uploaded — the frames are decoded and packed inside your browser.

How to make a sprite sheet

Four steps from a folder of frames to a sheet your engine can index.

  1. 1Add every frame at onceDrop them in or pick them from the file dialog. They are sorted numerically by name, so frame_2 lands before frame_10 even without zero padding.
  2. 2Fix the order if it mattersDrag any thumbnail to move it. The index under each frame is the position it will occupy in the sheet, which is the number your animation player will use.
  3. 3Choose the layoutGrid for animations — every cell the same size, indexed by number. Single row for a classic animation strip. Packed when the frames vary in size and nothing indexes by position.
  4. 4Export the sheet and the manifestDownload the PNG, and the JSON beside it. Pick TexturePacker (JSON Array) and a Phaser or PixiJS loader reads it directly; pick the plain list if you are writing your own. The manifest is mandatory for a packed sheet and useful documentation for a grid.

Cell size, and why the largest frame wins

In a grid sheet every cell is the same size, and that size is the largest frame in the set. Anything smaller sits inside its cell with transparent space around it. That is not waste for its own sake: it is what makes the sheet indexable. An animation player computes a frame's position as column × cellWidth — arithmetic that only works if the cells are uniform.

Where the smaller frame sits inside its cell is the part worth thinking about. Centre is the safe default. Bottom centre is what you want for a character: if the frames of a walk cycle vary in height, centring them makes the sprite bob up and down as it plays, while aligning to the bottom keeps the feet on one line.

If the frames were exported with a lot of transparent margin, turn on trim. Each frame is measured down to its visible pixels before being placed, which shrinks the cell size and often the whole sheet considerably — at the cost of changing where each frame's origin sits.

Padding, bleeding and the stray line along your sprite

A sprite that shows a one-pixel line of the neighbouring frame along one edge is suffering from texture bleeding. It appears when the GPU samples the sheet at a fractional coordinate — a scaled sprite, a rotated one, a camera that is not on a whole pixel — and the sample lands between two frames.

The fix is padding: one or two transparent pixels between cells, so a sample that strays picks up transparency instead of the wrong sprite. It costs a little sheet area and solves the problem outright. Add margin as well if the sheet itself will be scaled, so the outermost frames get the same protection.

Grid, strip or packed

Grid

Uniform cells indexed by number. What Godot's AnimatedSprite2D, Unity's sliced sprite mode and Phaser's load.spritesheet() all expect.

Single row

The same thing one frame tall. Easy to eyeball, easy to slice, and the format most tutorials assume.

Packed

Frames of different sizes fitted together to waste as little space as possible. There is no grid to index, so the JSON manifest is not optional.

When you need this

Exporting from a frame-by-frame tool

Animation software gives you numbered PNGs. Engines want one texture. This is the step in between.

Rebuilding a sheet after edits

Pull a sheet apart with the Sprite Sheet Cutter, fix one frame, pack it back together with the same settings.

Cutting draw calls

One texture with fifty sprites in it batches into far fewer draw calls than fifty textures. This is the main reason atlases exist.

Shipping a UI atlas

Icons, buttons and panel pieces of every size, packed into one file with a manifest your loader reads at startup.

Loading the sheet in your engine

Every engine wants the cell size — which the tool reports and the manifest records.

Godot

Assign the PNG to a SpriteFrames resource and use Select Frames with the reported columns and rows. Set the import filter to Nearest for pixel art.

Unity

Sprite Mode Multiple, then Sprite Editor → Slice → Grid By Cell Size with the cell size shown above the preview. Set Filter Mode to Point (no filter).

Phaser

A grid sheet loads with this.load.spritesheet(key, url, { frameWidth, frameHeight }). A packed sheet loads with this.load.atlas() — export the manifest as TexturePacker (JSON Array) and pass it as the third argument.

Manifest formats

The sheet is half the export. The other half says where each frame sits, and engines disagree about how that should look.

TexturePacker (JSON Array)

The default, and the closest thing to an interchange format. Phaser 3 reads it through load.atlas(), as do most loaders written against TexturePacker's output. Frames are a list, each with a filename.

TexturePacker (JSON Hash)

The same data as an object keyed by filename. What PixiJS's spritesheet parser expects, and Phaser accepts it too. Pick this one if your loader indexes frames by name rather than iterating them.

Plain list

A flat { filename, x, y, w, h } array with no nesting. No engine reads it as-is; it is there because it is the only one you can open and understand without a reference, which matters when you are writing the loader yourself.

Both TexturePacker layouts carry trim information. With trim on, each frame's sourceSize and spriteSourceSize record the size it was drawn at and where the visible pixels sat inside it, so a loader re-inflates the sprite to its original bounds and nothing shifts. That is what makes trimming safe: the sheet gets smaller and the game still positions sprites the way the artist drew them.

Nothing here rotates frames to pack them tighter, so rotated is always false — worth knowing if you are comparing output against a paid packer.

Frequently asked questions

How do I make a sprite sheet from individual images?

Select or drop every frame at once. They are sorted by file name, laid out in a grid whose cell size comes from the largest frame, and drawn into a single PNG. Adjust the column count, then download the sheet and its frames.json manifest.

My frames came out in the wrong order. Why?

Almost always because the file names are not zero padded. Plain alphabetical order puts frame_10 before frame_2, which scrambles the animation. This tool sorts numerically instead — frame_2 lands before frame_10 whether or not it is padded — and you can drag any thumbnail to fix the order by hand.

Should I use a grid or packed layout?

Grid for animations. An engine's animation player indexes into the sheet by frame number, which only works when every cell is the same size, and the wasted space is the price of that. Packed for a UI atlas or a mixed bag of sprites, where nothing indexes by position and the JSON manifest carries the coordinates.

Can I export a TexturePacker JSON atlas?

Yes. The manifest can be exported as TexturePacker JSON Array or JSON Hash as well as a plain list. Array is the default and loads directly in Phaser 3 through this.load.atlas(key, png, json); Hash is the layout PixiJS expects. Both record trim data, so frames trimmed to their visible pixels are re-inflated to their original bounds by the loader. Frames are never rotated, so rotated is always false.

What does padding between frames do?

It stops neighbouring frames bleeding into each other. When the GPU samples a texture at a non-integer position — anything scaled, rotated or on a fractional camera offset — it can pick up a pixel from the frame next door and paint a stray line along the sprite's edge. One or two pixels of padding is enough to prevent it.

Do I need a power-of-two sheet size?

Rarely, these days. Desktop and modern mobile GPUs handle arbitrary texture sizes fine. It still matters for older mobile targets, for some compressed texture formats, and when you want mipmaps. The option pads the sheet out to the next power of two on both axes without moving any frame.

What does “bottom centre” alignment do?

It puts each frame at the bottom of its cell rather than the middle. For a walk cycle whose frames vary in height, that keeps the character's feet on the same line, so the sprite does not bob up and down as the animation plays. Centre alignment is right for effects and projectiles.

Are my images uploaded to a server?

No. Every frame is decoded and drawn with the Canvas API inside your browser. Nothing leaves your machine, and the tool keeps working offline once the page has loaded.

Related guides

Related tools