Skip to content

Sprite Pivot Editor — Origin Points per Frame

Click once to place a frame's pivot on the pixel grid, or set it for a whole tag at once. The pivot travels with the frame into each engine's own origin field.

A pivot (also called origin or anchor) is the point of a frame that sits on the game object's position: rotation turns around it, and frames of different sizes line up on it. In Nerulio you click it onto the pixel grid (P) for one frame, the selection, a tag or every frame; it is stored as 0–1 of the frame canvas and written into each engine's own field on export. Unity, Phaser and PixiJS can follow a pivot per frame; a Godot node has one offset, so the table below says what each target really gets.

Drop a sprite sheet, numbered frames, a GIF / APNG or an .aseprite file
Runs in your browser. Files are not uploaded.
Studio Sprite workspace: one frame with its pivot, a hit box and a hurt box, and the timeline with animation tags
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

One pivot value, many engine conventions

Engines count pivots differently. Unity measures a sprite's pivot from the bottom-left of that sprite's rect, as 0–1 (Normalized) or in pixels. Phaser and PixiJS use 0–1 of the frame with y pointing down. Godot's AnimatedSprite2D has no pivot at all, only a drawing offset in pixels for the whole node. Nerulio keeps one value, 0–1 of the frame canvas with y down (bottom-centre 0.5, 1.0 unless you set it), and converts it per target, so a pivot set once survives trimming, packing and a change of engine.

Per-frame pivots matter when frames differ in size or are trimmed. The Godot scene therefore uses the first frame's pivot as the node offset, keeps every frame's pivot in the resource metadata, and warns when one animation has differing pivots. The robust setup for any engine is frames on one canvas and one pivot per tag at the feet.

The pivot tool snaps to whole pixels, and pivots travel with the art when frames are aligned, mirrored or fixed for jitter.

Pivot / origin / anchor
The point of a frame placed on the object's position; the name depends on the engine.
Normalized pivot
0–1 across the frame or sprite rect; Unity counts y from the bottom, Phaser and PixiJS from the top.
Node offset (Godot)
Pixels by which an AnimatedSprite2D draws its texture away from the node origin; one value for every frame.

Example: one pivot, written for five engines

A 48 × 64 frame canvas with the pivot at bottom-centre. The packer trims a standing frame to 20 × 30 at (14, 34) and a jumping frame to 20 × 30 at (14, 20).

Nerulio           pivot (0.5, 1.0) of 48 × 64   → pixel (24, 64)
Godot .tscn       centered = false, offset = (−24, −64)
Phaser / PixiJS   pivot / anchor (0.5, 1.0) of the 48 × 64 source frame
LÖVE              px = 24, py = 64
Unity, stand      ((24 − 14) ÷ 20, (30 − (64 − 34)) ÷ 30) = (0.5, 0.0)
Unity, jump       ((24 − 14) ÷ 20, (30 − (64 − 20)) ÷ 30) = (0.5, −0.467)

The jump frame's Unity pivot lies below its own rect; that is exactly what keeps the feet on the same spot. Why trimming moves pivots is explained on sprite jitter after trimming.

What each target receives

TargetWhat Nerulio writesWhat the engine does with it
Godot 4Scene: centered = false, offset = −pivot of the first frame of the first animation; every frame's pivot in metadata/nerulioAnimatedSprite2D draws every frame with that one offset; per-frame pivots need your own script
Unity 6A custom pivot per sprite, 0–1 inside the sprite rect with y up (outside 0–1 for some trimmed frames)Each sprite keeps its own pivot, so a clip changes pivot with the frame (read back in Unity 6000.5.3f1)
Phaser 3 / 4pivot {x, y} per frame in the atlas JSONPhaser 3.90 marks it as the frame's custom pivot and calls setOrigin with it on every frame change, animation frames included
PixiJS 8anchor {x, y} per frameIt becomes the texture's default anchor: a Sprite takes it when created; an AnimatedSprite follows it per frame only with updateAnchor = true
Aseprite JSON, .asepriteA pivot slice keyed on the frames where the pivot changesAseprite shows a slice with a pivot point
LÖVE 11px, py in the Lua frame tableThe shipped nerulio_atlas.lua draws each frame with its pivot on (x, y)
GameMakerOne strip per tag with every pivot on one point; that point is the origin in gamemaker.json and the READMEType it as the sprite's Origin in the Sprite Editor (UNVERIFIED in GameMaker)
Defold, Spine / libGDX, CSSNo pivotSet it in the engine

Starling / Sparrow XML also gets pivotX and pivotY in pixels; whether a reader uses them depends on that reader.

What it does

  • Pivot tool (P): a click snaps the pivot to the pixel grid of the current frame.
  • Scope: this frame, the selected frames, the tag or every frame.
  • Pivots stay attached to the art through alignment and mirroring.
  • Exports: Godot scene offset, Unity sprite pivot, PixiJS anchor, Aseprite slice pivot, GameMaker origin.

How to do it

  1. Drop and apply your sprite import.
  2. Choose a scope in the Frame panel and press P.
  3. Click where the feet (or the centre) should be; nudge with the arrow keys.
  4. Export for your engine; the pivot is written in its format.

Check the result

  • Turn on onion skin (F3) and step through the tag: the pivot cross should stay on the same spot of the body.
  • Godot: read the export notes (they warn about differing pivots) and print($Hero.sprite_frames.get_meta("nerulio")) for every frame's pivot.
  • Unity: open the texture in the Sprite Editor; each sprite's Pivot is Custom with the normalized value from the example.
  • Phaser: after sprite.play('run'), sprite.originX and sprite.originY equal the current frame's pivot.

Troubleshooting

SymptomLikely causeHow to checkFix
The Godot character jumps between animationsThe node has one offset, taken from the first frame of the first animation, and other animations use other pivotsThe Godot export notes; compare pivots in metadata/nerulioGive all tags of the character one pivot, or set offset from the metadata in a script when the animation changes
A PixiJS AnimatedSprite ignores per-frame pivotsupdateAnchor is false by default, so the anchor of the first texture staysLog sprite.updateAnchorSet updateAnchor = true, or keep one pivot for the tag
Unity pivots below 0 or above 1The frame was trimmed and the full-canvas pivot lies outside the smaller rectSprite Editor: the Custom Pivot valueNothing to fix: it keeps the art aligned. Pack with trim off if a tool demands 0–1

Other ways to do it

OptionWhen it is the better choice
Set the pivot in the engine (Unity Sprite Editor Pivot, GameMaker Origin, Godot node offset)One sprite with one pivot that you rarely re-export.
Pivot slices in AsepriteYou keep pivots in the .aseprite source; Nerulio reads a pivot slice on import and writes it back to .aseprite and Aseprite JSON.

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

GIF decoder: 408 of 408 frames identical to Pillow on 29 real GIFs; APNG 132 of 132 on 16 files. .aseprite: 231 corpus files imported, re-written and reopened in Aseprite 1.3.18 with the same tags, durations and pixels (231/231). 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.

Limits

  • Godot gets one pivot per node, not per frame, unless you script it.
  • Defold, Spine / libGDX and CSS exports carry no pivot.
  • GameMaker origins are UNVERIFIED in GameMaker itself.
  • A grid or island guess is a preview with a confidence; nothing is cut until you press Apply, and every choice has one-click alternatives.
  • .aseprite files whose layers do not compose exactly (e.g. group opacity) are imported flattened, and the Import panel says why.
  • Rotated frames in an imported atlas stay rotated; their export is UNVERIFIED.
  • Images up to 32,768 px per side (268 million pixels), subject to your browser's memory.

Versions and sources

Unity 6000.5.3f1 read back every sprite's pivot and it put the art where it was relative to one common anchor. Godot 4.7.2 loaded the scene with its offset. What Phaser and PixiJS do with the pivot comes from the Phaser 3.90 source and the PixiJS 8 documentation, not from a Nerulio run.

Official documentation used

Questions

Is the pivot stored in pixels or 0–1?

It is stored as 0–1 of the frame canvas and the tool snaps it to whole pixels; each engine format is converted from that value.

Does Unity get one pivot per frame?

Yes. Unity 6 sprite rects are created with their pivots by the included importer script (checked in Unity 6000.5.3f1).

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