Skip to content

Phaser Texture Atlas Generator — Atlas JSON and Animations

Drop your frames and get what this.load.atlas (or multiatlas) and anims.fromJSON read: a tightly packed atlas, its JSON hash and an animations file with each frame's duration — drawn by Phaser 3.90 and 4.2 in the verification run.

Phaser loads a texture atlas as a PNG plus a JSON hash or array in TexturePacker's layout, with this.load.atlas(key, png, json); every frame is then addressed by its name, as in this.add.sprite(x, y, key, "run_0"). Nerulio packs your frames into that layout and adds an .anims.json with every tag and per-frame duration for this.anims.fromJSON. Rotation stays off, because Phaser 3.90 and 4.2 did not turn rotated frames back in our runs; trimmed frames are drawn correctly. Checked in Phaser 3.90 and 4.2.

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
  • Phaser 3 / 4Verified· Phaser 3.90 + 4.2
  • Starling / Sparrow XMLVerified· Phaser 4.2
  • Godot 4Verified· Godot 4.7.2
  • Unity 6Verified· Unity 6000.5.3f1
  • PixiJS 8Verified· PixiJS 8.21
  • Aseprite JSONVerified· Phaser load.aseprite · PixiJS 8.21
  • .asepriteVerified· Aseprite 1.3.18
  • Spine / libGDXVerified· spine-canvas 4.2
  • LÖVEVerified· LÖVE 11.5
  • DefoldBuilt· Defold bob.jar 1.13.1
  • GameMakerUNVERIFIED
  • CSS spritesVerified· Chromium
  • GIF / APNGDecoded· Pillow

How Phaser finds a frame in an atlas

The loader turns every entry under frames into a named frame of one texture key. frame is the rectangle cut from the PNG. When trimmed is true, Phaser passes sourceSize and spriteSourceSize to the frame's trim, so the cut pixels are drawn inside a box of the original size and the sprite behaves as if nothing was trimmed. A pivot in the entry becomes the sprite's origin when the frame is set.

Animations refer to those names. Build them in code with this.anims.generateFrameNames(key, {prefix, start, end, zeroPad}), or load them as data with this.anims.fromJSON. Each animation frame may carry its own duration in milliseconds: in Phaser 3.90's animation code the next frame is due after that duration, and after the animation's ms-per-frame when it is 0.

When the frames need several pages, this.load.multiatlas(key, json, path) reads one JSON with a textures list, one entry per page image. Frame names stay unique across pages, so your code does not care which page a frame is on.

Texture key
The name given to the loader ("run"); every frame lives under it.
Frame name
The key of an entry in frames ("run_0"); what add.sprite and animations refer to.
JSON hash / array
The same frame data as an object keyed by name, or as a list with a filename field. Phaser reads both.
multiatlas
One JSON with a textures list, one entry per page image.

Example: the run cycle as Phaser receives it

run.png   38 × 50 px, 6 frames of 40 × 29, trim on, padding 2, no rotation

"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 }
"pivot": {"x":0.5,"y":1}   → sprite origin (0.5, 1): the point between the feet

run.anims.json   { "key":"run", "frameRate":10, "repeat":-1, "yoyo":false,
                   "frames":[ {"key":"run","frame":"run_0","duration":100}, … ×6 ] }

this.add.sprite(100, 100, "run", "run_0")
  → a 40 × 29 sprite whose feet stand on (100, 100); the 18 × 15 piece is drawn at (10, 10) inside it
6 frames × 100 ms = one cycle every 0.6 s; repeat -1 = loop forever

Phaser's repeat counts the extra plays, so a tag set to play 3 times in Aseprite or in the Studio is written as repeat: 2, and a ping-pong tag becomes yoyo: true.

What it does

  • Atlas JSON hash, or a multiatlas when the frames need several pages.
  • .anims.json with one animation per tag and each frame's own duration, for this.anims.fromJSON.
  • Rotation stays off for Phaser: both 3.90 and 4.2 drew rotated TexturePacker frames wrongly (not turned back) in our runs.
  • Trim that keeps each frame's offset, alias of identical frames, padding and extrude.

How to do it

  1. Drop numbered frames, a GIF or an .aseprite file.
  2. Check the animations on the timeline.
  3. In Pack & Export choose the Phaser 3 / 4 preset.
  4. Load the atlas with this.load.atlas (or multiatlas) and the animations with this.anims.fromJSON.

Files you get

FileWhat it is for
run.jsonAtlas JSON hash for one page: frames with frame, trimmed, spriteSourceSize, sourceSize, rotated and a normalised pivot; meta.image and meta.size.
run.multiatlas.jsonInstead of run.json when the frames need several pages: a textures list with run-0.png, run-1.png … and their frames.
run.anims.jsonOne animation per tag: frames with duration in ms, frameRate, repeat (−1 = loop) and yoyo for ping-pong. Not written when the project has no tags.
run.pngThe packed page, as an indexed PNG when it has 256 colours or fewer.
README-PHASER.mdThe preload and create code for this bundle.

Use it in Phaser 3 or 4

  1. Copy the PNG and JSON files together into your game's assets folder; a multiatlas finds its pages through the image names inside the JSON.
  2. In preload(): this.load.atlas("run", "assets/run.png", "assets/run.json") (or this.load.multiatlas("run", "assets/run.multiatlas.json", "assets/")) and this.load.json("run-anims", "assets/run.anims.json").
  3. In create(): this.anims.fromJSON(this.cache.json.get("run-anims")) registers the animations; then const hero = this.add.sprite(160, 120, "run", "run_0") and hero.play("run").
  4. For pixel art, create the game with pixelArt: true, which sets antialias to false and roundPixels to true.
  5. To build an animation yourself instead: this.anims.create({ key: "run", frames: this.anims.generateFrameNames("run", { prefix: "run_", start: 0, end: 5 }), frameRate: 10, repeat: -1 }).

Check the result

  • this.textures.get("run").getFrameNames() lists run_0 … run_5 (no file extensions).
  • Place this.add.image(100, 100, "run", "run_0") and "run_4" at the same point: the feet stay on one line, although the stored pieces start at different offsets.
  • this.anims.exists("run") is true, and one cycle of hero.play("run") takes 0.6 s.

Troubleshooting

SymptomLikely causeHow to checkFix
A frame name shows the wrong picture or nothingThat name is not in the atlas under this key: a typo, a file extension kept, or another texture keythis.textures.get(key).getFrameNames()Use the names exactly as in the JSON; Nerulio drops the extension (run_0, not run_0.png)
Only some frames are mirrored or turnedThe atlas was packed with rotation, for example by TexturePacker with rotation enabled; Phaser 3.90 and 4.2 did not turn such frames backThose entries have "rotated": trueRepack with rotation off; see Phaser atlas frames wrong
Pixel art is blurry or shimmers while movingLinear filtering and positions between pixelsThe game config has no pixelArt: trueSet pixelArt: true, or antialias: false and roundPixels: true
play("run") does nothingThe animations were never registered: fromJSON ran before the JSON was loaded, or read another cache keythis.anims.exists("run")Call this.anims.fromJSON(this.cache.json.get("run-anims")) in create(), after preload
The timing differs from the editorA frameRate passed to play() differs from the animation's, so Phaser uses its ms-per-frame instead of each frame's durationLook at the play configPlay without overriding frameRate; change durations in the tag instead

Other ways to do it

OptionWhen it is the better choice
Phaser's Aseprite loader: this.load.aseprite and this.anims.createFromAsepriteThe art is in Aseprite and you want its tags straight from the JSON; one page only, no rotation. See Aseprite to Phaser.
Starling / Sparrow XML with this.load.atlasXMLA pipeline that already uses XML (FNF-style assets); in Phaser 3.90 keep trim off. See Sparrow XML.
TexturePacker's Phaser exportYou already own TexturePacker: its Phaser format does not rotate by default and passed in Phaser 3.90 and 4.2 on all four sets we tested.

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

Nerulio's atlas and animation files were loaded by Phaser 3.90.0 and 4.2.1 (the real phaser.min.js in Chromium) on real CC0 frame sets and every frame was compared with its source. The loader and animation calls follow the Phaser documentation; the pivot-to-origin and frame-duration behaviour is read from the Phaser 3.90 source.

Official documentation used

Questions

Why is rotation disabled?

Phaser 3.90 and 4.2 both drew rotated TexturePacker frames wrongly (not turned back) in the test, so the Phaser preset never rotates.

Can I use Phaser's own Aseprite loader instead?

Yes: export Aseprite JSON for load.aseprite and createFromAseprite; it was verified in the same Phaser versions.

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