Hitbox Editor — Hit and Hurt Boxes per Frame
Draw hit, hurt, interact or custom boxes on the frames of an attack, copy them along the tag and nudge them frame by frame. The same box keeps the same id on every frame, so an engine can track it.
A hitbox (the area that deals damage) and a hurtbox (the area that can be hit) are per-frame shapes that game code checks; they are not part of the image. Nerulio lets you draw rectangle, circle or polygon boxes typed hit, hurt, interact or any custom word on each frame, copy one box along a tag with the same id, and nudge it frame by frame. Every box is exported into the Godot bundle's metadata and the generic JSON, rectangles also as Aseprite slices; no engine nodes or colliders are generated, so you build Area2D shapes, Unity colliders or Phaser bodies from that data.

- 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
Boxes belong to frames, and engines build them from data
Action games switch boxes with the animation: an attack's hitbox exists only on its active frames, while the hurtbox follows the body on every frame. So boxes belong to frames, not to the sprite, and the same logical box needs one identity across frames. In Nerulio a box has an id, a type and a shape in frame-canvas pixels (origin top-left, y down); drawing or copying it with the tag scope keeps the id, so code can track the sword box from frame to frame.
Engines do not read boxes from images. Godot detects overlaps with Area2D nodes that carry CollisionShape2D or CollisionPolygon2D children; Unity uses Collider2D components such as BoxCollider2D, which has a size and an offset in local units; a Phaser Arcade body is sized with setSize, setOffset or setCircle and otherwise uses the frame size. In all three you update the shape per frame yourself, in code or an animation track. The export gives you the numbers; it does not create those nodes or components.
Box coordinates are relative to the frame canvas. To place a box on the object, subtract the pivot: in the Godot scene, where the offset is −pivot, a box at (x, y) on the frame sits at (x − pivot x, y − pivot y) in node space. Unity counts y upwards and in units, so divide by Pixels Per Unit (100 in the Unity export).
- Hitbox
- The area of an attack frame that deals damage.
- Hurtbox
- The area of the body that can be hit.
- Box id
- Stays the same on every frame the box was copied to, so code can follow one box.
- Frame-canvas pixels
- Coordinates from the top-left corner of the frame's full canvas, y down.
Example: one hitbox, placed in Godot and Unity
Frame canvas 48 × 64 with the pivot at (24, 64). On attack frames 4–6 a hit box at x 34, y 30, 16 × 12 px.
Nerulio (frame canvas, y down) x 34, y 30, w 16, h 12
relative to the pivot left 34 − 24 = 10, top 30 − 64 = −34
Godot node space (y down) rectangle 10, −34, 16 × 12, centre (18, −28)
Unity (y up, 100 px per unit) centre ((34 + 8 − 24) ÷ 100, (64 − (30 + 6)) ÷ 100) = (0.18, 0.28)
size (0.16, 0.12)
Godot metadata get_meta("nerulio")["frames"]["attack_004"]["boxes"]
[{"id": …, "type": "hit", "shape": "rect", "x": 34, "y": 30, "w": 16, "h": 12}]Circles are stored as cx, cy, r and polygons as a list of points, in the same pixels. For solid collision outlines rather than attack boxes, see the collision polygon generator.
Which export carries the boxes
| Target | Boxes in the export | What you build in the engine |
|---|---|---|
| Godot 4 | Every box (rectangle, circle, polygon) with id and type, per frame, in the SpriteFrames metadata/nerulio | Area2D with CollisionShape2D or CollisionPolygon2D children, moved per frame by your script (the metadata was read back by Godot 4.7.2) |
| Generic JSON | Every box per frame, with the pivot and the collision polygons | Any engine: read frames[key].boxes |
| .aseprite, Aseprite JSON | Rectangles only: one slice per box type and slot, keyed where the box changes; circles and polygons are skipped | Aseprite shows slices (read back by Aseprite 1.3.18); JSON readers get meta.slices |
| Unity 6 | None; the export notes say so | Colliders built by your own script from the generic JSON |
| Phaser, PixiJS, LÖVE, Spine, Starling, Defold, GameMaker, CSS | None | Load the generic JSON next to the atlas |
What it does
- Rectangle (B), circle (C) and polygon (Q) boxes snapped to whole pixels.
- Types hit, hurt, interact or any custom word, each with its own colour.
- Scope: one frame, the selection, the whole tag or every frame; Alt+B copies to the next frame.
- Boxes past the frame canvas are kept and marked; boxes export to Aseprite slices, the Godot bundle and generic JSON.
How to do it
- Drop and apply your sprite import.
- Pick the box type and scope, then drag on the frame.
- Step through the animation and adjust each frame (arrow keys nudge, Shift = 10 px).
- Export; the boxes travel with the frames.
Check the result
- Step through the attack with , and . and watch the box on each frame; boxes that reach past the frame canvas are kept and marked.
- After a Godot export,
print($Hero.sprite_frames.get_meta("nerulio")["frames"].keys())lists the frame keys, and each entry'sboxesholds your boxes. - After an .aseprite export, open the file in Aseprite: each rectangle box type is a slice whose keys change on the frames where the box moves.
Troubleshooting
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| Circle or polygon boxes are missing in Aseprite | Aseprite slices are rectangles, so the .aseprite export skips other shapes and names them | The export message lists the skipped boxes | Use rectangles for boxes that must reach Aseprite, or read circles and polygons from the generic JSON |
| Boxes appear in the wrong place in the engine | Box coordinates start at the frame canvas's top-left corner, not at the pivot | Compare one box with the worked example above | Subtract the pivot; in Unity also flip y and divide by Pixels Per Unit |
| A box shows up on frames where it should not | It was drawn or copied with the scope set to the tag or all frames | Step through the frames: the same id appears on each | Delete it on those frames, or redraw it with the scope set to this frame |
| The Unity import has no boxes | The Unity export writes sprite rects, pivots and clips only | The export notes: hitboxes have no Unity sprite field | Export the generic JSON as well and create colliders from it |
Other ways to do it
| Option | When it is the better choice |
|---|---|
| Draw the shapes in the engine (Godot CollisionShape2D, Unity BoxCollider2D, Phaser body setSize and setOffset) | One or two boxes that do not change from frame to frame. |
| Slices in Aseprite | Rectangle boxes drawn next to the art; Nerulio imports named slices as boxes (hit, hurt …) and writes them back. |
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.
| Target | Files | Check |
|---|---|---|
| Godot 4 | .tres SpriteFrames · .tscn AnimatedSprite2D · .png.import | Verified 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 · AnimationClip | Verified Unity 6000.5.3f1Batch mode: sprite rects, pivots, pixels, clip keys and durations checked. |
| Phaser 3 / 4 | atlas JSON (multiatlas) · .anims.json | Verified Phaser 3.90 + 4.2Frames and animations drawn by both versions. |
| PixiJS 8 | spritesheet JSON · animations · anchor · meta.scale | Verified PixiJS 8.21Rotated and multi-page atlases included. |
| Aseprite JSON | hash / array · frameTags · slices | Verified Phaser load.aseprite · PixiJS 8.21Tags keep their direction and repeat count. |
| .aseprite | layers · tags · durations · slices | Verified Aseprite 1.3.18Opened and rendered by the Aseprite command line. |
| Spine / libGDX | .atlas | Verified spine-canvas 4.2Every region drawn by the Spine runtime, rotated and trimmed; libGDX itself was not run. |
| Starling / Sparrow XML | XML per page | Verified 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ÖVE | Lua quads · nerulio_atlas.lua · main.lua | Verified LÖVE 11.5Every frame drawn and every animation stepped through the shipped helper. |
| Defold | .atlas · .tilesource | Built Defold bob.jar 1.13.1Built by Defold's command-line builder; the built texture's UVs were read back. |
| GameMaker | spr_<name>_stripN.png · gamemaker.json | UNVERIFIEDNot loaded in GameMaker (no headless GameMaker). The strips are cut the way its strip importer cuts them. |
| CSS sprites | .css · preview .html | Verified ChromiumEvery class drawn from the shipped stylesheet. |
| GIF / APNG | one file per animation | Decoded 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
- No engine nodes, colliders or physics bodies are generated; the export carries data only.
- Aseprite formats carry rectangle boxes only.
- Box coordinates are whole pixels on the frame canvas.
- 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
Boxes in the Godot bundle were read back by Godot 4.7.2, and rectangle slices by Aseprite 1.3.18, including boxes that reach past the frame canvas. The node and component names follow the Godot 4.7, Unity 6 and Phaser documentation.
Official documentation used
Questions
Can I draw one box for a whole attack?
Yes. With the scope set to the tag, one drag puts the same box (same id) on every frame of it; moving it moves all of them in one undo step.
Which formats carry the boxes?
Every box goes into the metadata of the Godot bundle and the generic JSON; .aseprite and Aseprite JSON carry rectangles only, as slices. Unity gets no boxes. Box export to the Godot bundle was loaded in Godot 4.7.2.
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).