Skip to content

Collision Polygon Generator from Sprite Alpha

Generate a collision polygon from each frame's alpha, capped to a vertex count your engine likes, for a frame, a tag or every frame — then drag points to fine-tune.

A collision polygon traced from a sprite's alpha follows its silhouette more closely than a box. Nerulio traces each frame's outline along pixel edges at an alpha threshold (127 by default, so pixels at least half opaque count), simplifies it until it fits a vertex cap (12 by default, 3–64) without letting the outline cross itself, and keeps up to 8 polygons per frame in frame-canvas pixels. The polygons are exported in the Godot bundle's metadata and the generic JSON; Nerulio does not create CollisionPolygon2D nodes or Unity colliders.

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

Tracing, simplifying, and handing the points to an engine

The outline runs along the edges between opaque and transparent pixels, not through pixel centres, so every vertex sits on a whole pixel corner: a 16 × 16 opaque square becomes exactly (0, 0), (16, 0), (16, 16), (0, 16). Outer outlines come back clockwise on screen (y down), and specks smaller than 4 px² are dropped.

Simplification (Ramer–Douglas–Peucker) removes points that lie close to a straight line. The tolerance starts at 1 px and is raised until the polygon has no more vertices than the cap; a tolerance that would make the outline cross itself is rejected, because a self-crossing outline is not a usable physics shape. Raising the tolerance happens in steps, so a cap can be met with fewer vertices than it allows, and a low cap can round away gaps such as the space between two legs.

Engines take such polygons as point lists relative to the object. Godot's CollisionPolygon2D accepts concave polygons and, in its default solids mode, splits them into convex parts itself; it must be the child of an Area2D or a physics body. Unity's PolygonCollider2D takes points in local space, and Unity can also build its own outline from a sprite's Custom Physics Shape. In both, subtract the frame's pivot first; in Unity also flip y and divide by Pixels Per Unit.

Alpha threshold
The highest alpha still counted as transparent; 127 keeps pixels that are at least half opaque.
Vertex cap
The most points a polygon may have after simplification.
Tolerance
How far (in pixels) simplification may move the outline.

Example: one 32 × 48 character frame at three settings

A head, a body and two legs with a 4 px gap between them, plus a faint glow (alpha 60) around the head. Numbers from Nerulio's tracer:

traced outline (alpha > 127)            48 vertices, area 544 px²
cap 64   tolerance 1 px     16 vertices, area 540 px² (−0.7 %), max deviation 1.0 px, legs kept apart
cap 12   tolerance 3.8 px    5 vertices, area 428 px² (−21 %), max deviation 3.8 px, leg gap gone
threshold 40, cap 12        glow counted: area 628 px², 12 vertices at tolerance 2.0 px

Godot, pivot (16, 48)       polygon point (22, 48) → node space (22 − 16, 48 − 48) = (6, 0)

A cap of 12 did not give 12 points here: the next tolerance step already went down to 5. If the shape matters, raise the cap and compare the vertex count the Studio reports after Auto from alpha.

Where the polygons go

TargetPolygons in the exportWhat you build in the engine
Godot 4collision per frame in metadata/nerulio, each polygon as a flat list x0, y0, x1, y1 …A CollisionPolygon2D under an Area2D or a body, its polygon set from the list minus the pivot, per frame by your script
Generic JSONframes[key].collision: each polygon as [[x, y], …]Any engine that takes a point list
Unity 6None; the export notes point to the generic JSONPolygonCollider2D points from the generic JSON, or Unity's own Custom Physics Shape
Aseprite, Phaser, PixiJS, LÖVE, Spine, Starling, Defold, GameMaker, CSSNoneLoad the generic JSON next to the atlas

What it does

  • Auto from alpha: outline tracing on the pixel lattice with an alpha threshold.
  • A maximum vertex count per polygon (for example 8 for simple physics shapes).
  • Runs over this frame, the selection, the tag or every frame in one undo step.
  • Polygons are stored per frame in frame-canvas pixels and export with the frame data.

How to do it

  1. Drop and apply your sprite import.
  2. Set Max vertices and the alpha threshold in the Frame panel.
  3. Choose the scope and press Auto from alpha.
  4. Select points to move them, or clear and redraw; then export.

Check the result

  • After Auto from alpha the toast reports how many frames and vertices were made; step through the tag and look at the outline on each frame.
  • Toggle the threshold between 127 and a low value on a frame with glow or smoke and compare the outlines.
  • In Godot, add a CollisionPolygon2D under an Area2D with the converted points and turn on Debug › Visible Collision Shapes to see it over the sprite.

Troubleshooting

SymptomLikely causeHow to checkFix
The polygon swallows gaps such as the space between the legsThe vertex cap forced a tolerance larger than the gapThe vertex count after Auto is far below the cap, or the outline bridges the gapRaise the cap (for example to 24) and run Auto again
Glow or smoke is inside the collision shapeIts alpha is above the thresholdLower and raise the threshold and compareRaise the threshold so faint pixels count as transparent
The shape is offset in the enginePoints are frame-canvas pixels from the top-left, not relative to the pivotCompare one point with the worked exampleSubtract the pivot; in Unity also flip y and divide by Pixels Per Unit
Only some parts got a polygonUp to 8 separate shapes per frame are kept, and specks under 4 px² are droppedThe Auto message mentions dropped specks or extra shapesJoin the parts in the art, or draw the missing polygon by hand

Other ways to do it

OptionWhen it is the better choice
Unity's Custom Physics Shape in the Sprite EditorUnity only: Unity generates and edits the outline itself, and a Polygon Collider 2D uses it for new instances of the sprite.
A box or circle per frameMost platformer bodies; cheaper and steadier than a detailed polygon. Draw it with the hitbox editor, or use the convex hull, rectangle and circle options of the classic Sprite Lab.

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

  • Polygons are data in the export: no CollisionPolygon2D nodes or Unity colliders are created.
  • Collision polygons do not reach Unity, Aseprite or the atlas formats; use the generic JSON.
  • Holes inside a shape are not part of the Studio's polygons.
  • 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

The tracer and the numbers in the example come from Nerulio's own module (src/game/contour.js). The Godot bundle that carries the metadata was loaded by Godot 4.7.2; the collision polygons in it were not used as physics shapes in that run. Engine behaviour follows the Godot 4.7 and Unity 6 documentation.

Official documentation used

Classic tool

Convex hull, rectangle and circle fallbacks per frame are still in the classic Sprite Lab.

Open the classic tool →

Questions

Will the polygon cover faint pixels like glows?

Only pixels above the alpha threshold count. Raise it to ignore soft glows, lower it to include them.

Is the polygon simplified?

Yes, down to the vertex cap you set, and every point stays inside the frame canvas.

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