Skip to main content
Getly

How to Pack a Sprite Sheet (Free Tools, 2026)

Pack sprites into one atlas with free tools: TexturePacker, Free Texture Packer, Aseprite, Unity, Godot, Phaser 3 JSON, CSS sprites. Key settings.

13 min read
2,504 words
How to Pack a Sprite Sheet (Free Tools, 2026)

A sprite sheet is one image that holds many smaller images. A texture atlas is the same idea with a data file next to it that says where every frame sits: its name, its position, its size, and sometimes whether it was trimmed or rotated. In practice people use the two words for the same thing, and every engine and every packing tool calls it something slightly different.

This tutorial walks through packing a sprite sheet with free tools, the settings that decide whether it looks right in the game, how to load the result in Phaser 3, Unity, Godot and plain CSS, and the mistakes that show up most often. At the end there is a short section for people who sell sprite packs.

Why pack sprites at all

The reason is the same on every platform: fewer separate things for the hardware or the browser to deal with.

  • Fewer draw calls in a game. A renderer can usually batch sprites that share one texture into a single draw. If every frame of every character is its own image, the renderer has to switch textures constantly and batching breaks. Put them on one atlas and dozens of sprites can go out together.
  • Fewer requests on the web. Loading forty small icons as forty files means forty requests. One sprite sheet is one request and one decode. HTTP/2 made many small requests cheaper than they used to be, but a single image is still simpler to cache and preload.
  • Less wasted memory. Older GPUs and some mobile drivers round textures up to the next power of two. A 130 by 70 pixel image can end up occupying 256 by 128 in memory. Packing many images into one well sized atlas wastes far less.
  • Easier asset management. One file pair (image plus data) is easier to version, ship and load than a folder of hundreds of frames.

Packing is not always worth it. A single background image, or a sprite that is already huge, gains nothing from an atlas. The win comes from many small images drawn together.

Free tools that pack sprite sheets

You do not need to pay for anything to get a good atlas. The table below lists the tools covered here. "Free" means you can produce a usable atlas without buying a licence; some of these have paid tiers with extra features.

ToolWorks withTypical outputCost
TexturePackerPhaser, Pixi, Unity, Godot, Cocos, CSS and many morePNG + JSON, XML, engine specific filesFree basic mode, paid Pro features
Free Texture Packer (free-tex-packer)Phaser, Pixi, generic JSON, XML, CSSPNG + JSON hash or array, XMLFree, open source
AsepriteAny engine that reads JSON frame dataPNG + JSON hash or arrayPaid, source available to compile yourself
Unity Sprite AtlasUnity onlyInternal atlas built at build or play timeBuilt into Unity
Godot AtlasTextureGodot onlyResource that points at a region of a textureBuilt into Godot
Hand made CSS spriteBrowsersOne PNG or SVG + CSS background-positionFree

If you only remember one thing from the table: when your target is a JavaScript engine such as Phaser or Pixi, Free Texture Packer covers almost everything at no cost. When you are inside Unity or Godot, use the engine's own atlas system first and reach for an external packer only if you need something it does not do.

Step by step: packing with Free Texture Packer

Free Texture Packer is a desktop app (there is also a web version) that takes a folder of images and writes an atlas. It is a good default for web games because it has export presets for common JavaScript engines.

  1. Prepare your frames. Export each frame as its own PNG with transparency. Name them so they sort correctly: run_00.png, run_01.png and so on. Zero padded numbers matter; without them run_10 sorts before run_2.
  2. Add the folder. Drag the folder into the app. Subfolder names usually become part of the frame name, which is handy for grouping (hero/run_00.png).
  3. Pick the export format. For Phaser 3 choose the Phaser 3 preset or JSON hash. JSON array works too; the difference is explained below.
  4. Set padding and extrude. Start with 2 pixels of padding and 1 pixel of extrude. More on why in the settings section.
  5. Decide on trimming and rotation. Trimming removes transparent edges and saves space. Rotation lets the packer turn frames 90 degrees to fit more in. Leave rotation off unless your engine clearly supports rotated frames from this format.
  6. Set a maximum size. 2048 by 2048 is a safe ceiling for most targets including phones. If the frames do not fit, split them into several atlases rather than raising the limit blindly.
  7. Export. You get an image and a data file with the same base name. Keep them together.

TexturePacker follows the same flow: add sprites, choose a data format from its long list of frameworks, adjust padding and trimming, then publish. Some advanced options are marked as Pro, and the app will tell you when a chosen setting needs a licence. For a plain PNG plus JSON atlas the free mode is enough.

Exporting from Aseprite

If you draw pixel art in Aseprite, you can skip a separate packer for many projects. Use File, Export Sprite Sheet. In the dialog you choose the layout (by rows, by columns, packed), which layers and tags to include, border and shape padding, and whether to trim. On the output tab you can write a JSON data file next to the image, as either a hash or an array.

The JSON also includes your animation tags with their frame ranges and the per frame duration you set in the timeline. Some engines and plugins can read those directly, which saves you from typing frame numbers by hand. When you work in several files, a common setup is to export each character from Aseprite as loose frames or a strip and then pack everything together with a general packer so the whole game shares fewer textures.

Settings that actually matter

Most visual bugs with atlases come from four or five settings. Getting these right is the real skill.

Padding and extrude

When the GPU samples a texture with filtering on, it blends neighbouring pixels. At the edge of a frame, the neighbour may belong to the next frame in the atlas. The result is a thin line of the wrong colour at the border of a sprite, often only visible when the camera moves or the sprite is scaled. This is called bleeding.

Padding adds empty space between frames so there is nothing foreign to blend with. Extrude repeats the outer row of pixels of each frame outward, so if the sampler does reach past the edge it picks up the frame's own colour. For tiles that must sit flush against each other, extrude is the setting that removes visible seams. Two pixels of padding and one of extrude is a sensible starting point; raise them if you use mipmaps or scale sprites down a lot.

Trimming

Trimming cuts away transparent borders and records the original size and offset in the data file. The engine then places the trimmed frame back where it belongs. This saves a lot of space for characters with empty space around them. The catch: if your engine or loader ignores the trim data, animations will jitter because each frame is a different size. Phaser, Pixi and the common packers handle this correctly with JSON hash and array formats. If you see a sprite shaking in place, check trimming first.

Rotation

Letting the packer rotate frames by 90 degrees can fit more into the same sheet. The engine has to rotate them back when drawing, and not every loader does. If frames appear sideways, turn rotation off. The space saved is rarely worth the debugging.

Power of two and maximum size

Modern desktop GPUs handle non power of two textures fine, and WebGL 2 does too. Power of two sizes (512, 1024, 2048, 4096) are still the safe choice when you need mipmaps on older targets, when you use compressed texture formats that expect them, or when you are unsure about the hardware. For the maximum size, 2048 by 2048 is supported almost everywhere. 4096 by 4096 works on most current devices, but on low end phones it can be slow to upload or simply refused. If your game targets mobile, keep atlases at 2048 and use several of them.

Pixel art and filtering

Pixel art should be drawn with nearest neighbour sampling, not linear filtering, or it turns blurry. In Phaser set pixelArt: true in the game config. In Unity set the texture's filter mode to Point and turn off compression for small sheets. In Godot set the texture filter to Nearest on the node or in the project settings. With nearest sampling bleeding is less common, but padding still helps when sprites move at fractional positions.

Export formats: JSON hash, JSON array, XML

The image is almost always a PNG. The data file is where formats differ.

  • JSON hash stores frames as an object keyed by frame name: "frames": { "run_00.png": { ... } }. Easy to look up by name.
  • JSON array stores frames as a list, each with a filename field. It keeps a guaranteed order, which some tools prefer.
  • XML formats, such as the Starling / Sparrow style, list each sub texture with x, y, width and height attributes. Phaser can load this style with load.atlasXML.

Phaser 3 reads both JSON hash and JSON array through the same load.atlas call, so for Phaser the choice hardly matters. Pick one and keep it consistent across the project.

Loading an atlas in Phaser 3

Here is a minimal scene that loads an atlas, places a sprite from it and builds a run animation from numbered frames.

class GameScene extends Phaser.Scene {
  preload() {
    // image and data file exported by the packer
    this.load.atlas('hero', 'assets/hero.png', 'assets/hero.json');
  }

  create() {
    this.anims.create({
      key: 'run',
      frames: this.anims.generateFrameNames('hero', {
        prefix: 'run_',
        start: 0,
        end: 7,
        zeroPad: 2,
        suffix: '.png'
      }),
      frameRate: 12,
      repeat: -1
    });

    const player = this.add.sprite(200, 300, 'hero', 'run_00.png');
    player.play('run');
  }
}

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  pixelArt: true,
  scene: GameScene
};

new Phaser.Game(config);

The frame names in generateFrameNames must match the names in the JSON exactly, including the extension if the packer kept it. Open the JSON once and look; most "missing frame" warnings come from a prefix or suffix that does not match.

Unity Sprite Atlas and Godot AtlasTexture

In Unity, create a Sprite Atlas from the Create menu under 2D, then drag folders or individual sprites into its list of objects to pack. The atlas inspector has padding, an option to allow rotation and an option for tight packing. Tight packing follows the sprite outline instead of its rectangle and saves space, but it can clip sprites with unusual shapes, so check the result. Your sprites keep their own references in scenes and prefabs; Unity swaps in the atlas when it builds. Make sure sprite packing is enabled in the project's editor settings, otherwise you will not see the effect in play mode.

In Godot, an AtlasTexture is a resource that points at a rectangular region of another texture. You can create one in the inspector, assign the big sheet as its atlas and set the region. For animated characters, the SpriteFrames editor used by AnimatedSprite2D can slice a sheet into a grid and add the frames directly, which is often quicker than creating many AtlasTexture resources by hand. For tile maps, the TileSet editor works from the sheet itself and has its own separation and margin settings.

CSS sprites for the web

For icons on a website, a sprite is one image with every icon on it and CSS that shows the right part. Many packers can export a stylesheet; you can also write it by hand.

.icon {
  width: 32px;
  height: 32px;
  background-image: url('/img/icons.png');
  background-repeat: no-repeat;
}
.icon-cart   { background-position: 0 0; }
.icon-search { background-position: -32px 0; }
.icon-user   { background-position: -64px 0; }

For high density screens, export the sheet at twice the size and set background-size to the original dimensions so each icon stays sharp. For simple single colour icons, an SVG sprite with <symbol> elements and <use> is often a better choice than a PNG, because it scales without extra files.

Common mistakes

  • Thin lines at sprite edges. Bleeding from filtering. Add padding and extrude, or switch pixel art to nearest sampling.
  • Animation jitters in place. Trimmed frames without the loader reading trim offsets, or frames exported with different canvas sizes. Keep the same canvas per animation or make sure trim data is used.
  • Frames appear rotated. The packer rotated them and the loader does not rotate back. Turn rotation off.
  • Atlas works on desktop, blank on a phone. The sheet exceeds the device's maximum texture size. Split it into 2048 sheets.
  • "Frame missing" warnings. Names do not match because of a prefix, suffix, subfolder or missing zero padding.
  • Repacking changes everything. Positions in the sheet shift when you add a frame. Always load frames by name, never by hard coded coordinates, and the next repack will not break anything.
  • One giant atlas for the whole game. Grouping by level or by screen keeps memory lower, since you only load what the current scene needs.

If you sell sprite packs

Buyers use different engines and different packers, so the most useful pack works for all of them. A few habits make a pack easier to buy and easier to use:

  • Ship both forms. Include the loose frames as separate PNG files and a packed atlas with its JSON. People who repack with their own settings want the loose frames; people who want to drop it in and go want the atlas.
  • State the facts on the listing. Frame size in pixels, number of frames per animation, padding and extrude used in the atlas, whether frames are trimmed, the data format (JSON hash, JSON array, XML) and the frame rate you designed for.
  • Keep naming predictable. Zero padded, lowercase, one prefix per animation. Buyers will write code against those names.
  • Include a preview. An animated GIF or a short video of each animation answers the main question before purchase.
  • Be clear about the licence. Say whether the pack can be used in commercial games.

If you want to see how other creators present their packs, or put your own up for sale, the 2D sprites category on Getly is one place where sprite packs are sold.

Packing a sprite sheet takes a few minutes once the settings are clear: consistent frame names, some padding and extrude, careful use of trimming, no rotation unless you know the loader handles it, and an atlas size the target hardware accepts. Get those right and the atlas disappears into the background, which is exactly what it should do.

Ready to start selling?

Independent marketplace for digital creators. Keep 80–90% of every sale. Accept cards and stablecoins.