gif→sprite sheet

Basics

How to Make a Sprite Sheet (Aseprite, Photoshop, Browser)

Make a sprite sheet from separate frames: same-size canvases, row or grid, spacing, file order, Aseprite and Photoshop export steps, and engine size limits.

Updated 9 min read

To make a sprite sheet, put every frame on a canvas of the same pixel size, name the files so they sort in playback order, lay them out in a grid, and export one transparent PNG. If you draw in Aseprite, File → Export → Export Sprite Sheet does all of that in one dialog. If your frames are separate images from Photoshop, Krita, Procreate or an AI generator, drop them into our sprite sheet maker and download the sheet.

The export takes a minute. What costs people an evening is a sheet that looks fine as a picture and then jitters, bleeds or plays out of order in the engine.

Pick the route for what you have

You haveFastest route
An Aseprite fileExport the sheet from Aseprite (steps below)
A Piskel animationExport panel, PNG tab, set Columns and Rows, Download
Layers in Photoshop or Procreate, or a Krita timelineExport one PNG per frame, then pack them
A folder of PNGs (hand drawn, rendered, AI generated)Pack them in the sprite sheet maker
A GIF, animated WebP or a short videoConvert it with the GIF to sprite sheet tool, which pulls the frames out for you

Give every frame the same canvas

An engine cuts a grid sheet with one number pair: the cell size. Frame 7 is whatever sits in the seventh rectangle. So the character’s position inside the rectangle is the animation. If the feet are 3 px from the bottom in one frame and 5 px in the next, the character hops, and no import setting will fix it.

Make the canvas big enough for the widest pose (the sword swing, not the idle stance) and keep the feet on the same baseline in every frame. Don’t crop each frame to its visible pixels. Atlas packers like TexturePacker do that, and it only works because they write a data file with each frame’s offset. For a first game I’d skip trimmed atlases entirely: a plain grid wastes some transparent pixels and saves you a whole class of bugs.

AI generated frames usually fail here: they come back at slightly different sizes, with the character drifting. Our maker sizes the cell to the largest image and centres smaller ones in it, which will not line up a drifting character. Fix the alignment in an editor first.

Row or grid

A single row is the easiest sheet to read and to slice: the frame count is the column count. Use it for short loops. I stop at about eight frames, because a row grows in one direction only and runs into texture limits quickly. Sixty frames at 128 px wide is a 7680 px strip, and Godot’s documentation says mobile GPUs are typically limited to 4096×4096.

For anything longer, use a grid read left to right, then top to bottom. That reading order is what Unity’s grid slicer, Godot’s frame index, Phaser’s spritesheet loader and VRChat all assume. With several animations for one character, give each its own row (idle on row one, walk on row two).

sheet width  = margin × 2 + columns × cell width  + (columns − 1) × spacing
sheet height = margin × 2 + rows    × cell height + (rows − 1)    × spacing

12 frames, 64 × 64 px, 4 columns, no spacing:   256 × 192
same sheet, 2 px spacing and 2 px margin:       266 × 200

Write down three numbers when you export: cell size, columns, and frames per second. The PNG stores none of them. If the last row isn’t full, also note the real frame count, or the empty cells get imported as blank frames and the animation blinks at the end of each loop.

Do you need a power-of-two sheet?

Mostly no. Godot’s docs only recommend power-of-two sizes when a texture has to repeat, which a sprite sheet doesn’t, and GameMaker repacks your sprites onto its own power-of-two texture pages whatever you give it. Pad to 512 or 1024 when a platform demands a fixed size (VRChat emoji do), with empty space on the right and bottom so the cells don’t move. Never stretch the sheet to reach the size.

Spacing, margin and bleeding

Bleeding is a thin line of the neighbouring frame showing at the edge of a sprite. It appears when the engine samples the texture between pixels (smooth filtering, a camera that isn’t on whole pixels, a non-integer scale) and reaches one pixel past the cell.

If the cells already have transparent pixels around the character, you have a built-in buffer and zero spacing is fine. If art touches the cell edge, add 1 or 2 px of transparent spacing between cells and the same margin around the sheet. Tiles are the exception: a transparent gap shows as a seam between tiles, so for those you want extrusion, which repeats each cell’s edge pixels outward (Aseprite’s Extrude checkbox does this).

Spacing changes the numbers you type on import. In Unity 6’s Sprite Editor, Padding is the space between sprites and Offset shifts the grid from the corner, so a sheet made with 2 px spacing and a 2 px margin is sliced with Padding 2 and Offset 2. Other tools use the word padding differently, so check the exported image’s pixel dimensions against the formula.

Name the files so they sort correctly

Use one prefix per animation and zero-padded numbers: walk_000.png, walk_001.png, up to walk_011.png. Plain alphabetical sorting puts walk_10 before walk_2, and that is the most common reason a sheet plays in the wrong order. Our maker sorts by the number inside the name, so unpadded files still come out right. Other tools may not.

Pack separate images in the browser

  1. Open the sprite sheet maker and choose Upload images, or drop the files on the page. It takes PNG, JPG, WebP and GIF. The files stay on your device.
  2. Check the list of images in sheet order. Use the arrows to move a frame earlier or later, and the × to remove one.
  3. Under Layout, pick Grid (it chooses the squarest column count), Row, Column, or Custom to type the number of columns yourself.
  4. Set Spacing and Margin in pixels if your art touches the cell edges. Leave the frame size at 100% and the background empty for a transparent sheet.
  5. Watch the animation preview in the corner of the sheet. If it jitters here, it will jitter in the engine.
  6. Read the numbers under the options: the Unity line gives the Grid By Cell Size values and the Godot line gives Hframes and Vframes.
  7. Choose Download PNG. If your engine reads a data file, the same sheet is available as a JSON atlas, a Godot 4 SpriteFrames .tres, Sparrow XML or CSS.

It can’t redraw a pose or fix alignment, and it doesn’t produce rotated or trimmed atlases.

Export a sheet from Aseprite

Aseprite’s docs page writes the menu as File → Export Sprite Sheet. In the current source the command sits one level down, on Ctrl+E (Cmd+E on macOS).

  1. Choose File → Export → Export Sprite Sheet.
  2. In Layout, set Sheet Type. Horizontal Strip and Vertical Strip are a single row or column. By Rows wraps into a grid, and Packed fits frames wherever they go, which a grid slicer can’t read.
  3. With By Rows, set Constraints to Fixed # of Columns and type the column count.
  4. In Sprite, choose the layers and the frames (all of them, or one tag). Tick Split Tags to get one row per tag, which is the walk row and idle row layout from earlier.
  5. In Borders, Spacing is the gap between frames and Border Padding is the margin around the sheet. Leave Trim Cels off for a grid: it crops each frame separately, so the cells stop being equal.
  6. In Output, tick Output File and name the PNG. Tick JSON Data as well if your engine imports Aseprite’s JSON.
  7. Select Export.

Leave Merge Duplicates off too unless you import the JSON. Its tooltip says similar frames can share one rectangle, so a pose you held for three frames becomes one cell and a grid slice loses the timing.

The same export from the command line, for a build script:

aseprite -b walk.aseprite --sheet walk-sheet.png --data walk-sheet.json --sheet-type rows --sheet-columns 4 --shape-padding 2 --border-padding 2

Photoshop, Krita, Procreate and Piskel

I couldn’t find a sprite sheet export in Adobe’s or Krita’s documentation, so for those two the reliable route is frames out, then pack.

In Photoshop, put each frame on its own layer and use File → Export → Layers to Files. Set a File Name Prefix, pick a PNG file type, and select Run. Photoshop saves one file per layer, named with the prefix plus the layer name, so name the layers 000, 001, 002 before you export. Then check that the PNGs all have the same pixel dimensions. If they don’t, the export cropped each layer to its contents and the alignment is gone.

In Krita 5.3, use File → Render Animation, tick Export as Image Sequence and set a Base Name. Krita numbers the frames for you. Leave Only Unique Frames off if your animation has holds: with it on, Krita writes only the keyframes and the held frames disappear from the sheet.

In Procreate, Actions → Share → PNG Files exports every layer as Artwork-1, Artwork-2 and so on, starting with the bottom layer. Those names aren’t zero-padded, so pack them with a tool that sorts numerically.

Piskel exports a sheet directly: open the export panel, choose the PNG tab, set Columns and Rows, and select Download.

How big a sheet can be

If you want one number, keep both sides at 4096 px or less, and at 2048 if the game has to run on old phones.

Where it runsWhat the official docs sayHow it reads a grid
Unity 6Textures larger than the Max Size import setting are scaled down on import, so check that setting before blaming the artSprite Mode Multiple, then Sprite Editor → Slice → Grid By Cell Size with Pixel Size, Offset and Padding
Godot 4Mobile GPUs are typically limited to 4096×4096. Above 8192×8192 may fail on older desktopsSprite2D Hframes and Vframes, or SpriteFrames, Add frames from a Sprite Sheet
GameMakerTexture pages go up to 4096×4096, and some targets allow only 2048×2048. A larger image is halved until it fitsPacks your sprites onto its own pages, cropping empty space by default
Phaser 32048 px on mobile and 4096 px on desktop should be safeload.spritesheet with frame width and height. Frames are numbered from 0
Any WebGL pageMDN: effectively all systems support a MAX_TEXTURE_SIZE of 4096Your own code
VRChat animated emojiExactly 1024×1024, in a 2×2 (512 px frames), 4×4 (256 px) or 8×8 (128 px) grid, 64 frames at mostLeft to right, then top to bottom. A file name like VRRatEmoji_14frames_20fps.png presets the frame count and speed

When a sheet is too big, cut frames before you shrink them. Every second frame of a 24 fps clip still reads as the same motion at 12 fps, and scaling pixel art down to fit destroys it.

Play it back before you import it

Open the finished PNG in sprite sheet to GIF, set Find the frames by to Cell size, type the cell size you wrote down, and let it loop at your frame rate. Type the numbers yourself instead of using auto-detect, because that tests the same slicing the engine will do.

A sliver of another frame at one edge means the cell size or the gap is off by a pixel or two. Compare the image’s real width with the formula. If it grows from left to right across the sheet, you forgot the spacing.

A character that shakes while standing still was drawn at slightly different positions in each cell, or the frames were cropped separately. That is an art fix, so go back to the source canvas.

A loop that jumps partway through is a sorting problem (look for frame 10 sitting after frame 1), and a blink at the end is the empty cells of an unfilled last row being played.

Blur is rarely the sheet’s fault. Either the sheet went over a size limit and was scaled down, or the engine is smoothing it. In Unity, set the texture’s Filter Mode to Point (no filter) for pixel art, and export the sheet at 100% so the engine does the enlarging.