How to wire isometric/dimetric tile art into the engines and frameworks that actually ship games. This file assumes the projection decision is already made (see projection-math.md) and the tileset already exists (see tile-spec.md, pixel-art-workflow.md, or blender-prerender.md). It owns the integration delta only — camera/tilemap/depth-sort configuration per engine — not asset creation.
Terminology reminder: the tile grids described here are almost always 2:1 dimetric (commonly called isometric in games) — see projection-math.md for the full disambiguation. Everywhere below that says "isometric" in a menu label or API name (Godot's
Tile Shape = Isometric, Unity's "Isometric Tilemap") is the engine vendor's own (loose) terminology, not a claim that the math is true 30° isometric.
TileMapLayerGodot 4.3+ uses the TileMapLayer node (the pre-4.3 single TileMap node with multiple
layers was deprecated in 4.3 and is scheduled for removal in a future 4.x release —
verify against the version pinned in your project; if you're still on 4.0–4.2, the same
tile-set configuration applies to the legacy TileMap node's per-layer settings).
TileSet resource on your TileMapLayer.Diamond Down and Diamond Right;
Diamond Down is the conventional choice — it matches the "top point, then widen
left/right, then bottom point" diamond most 2:1 dimetric art assumes. Diamond Right
rotates the same math 90° and is used for isometric layouts read top-to-bottom in
the opposite screen direction.)Tile Shape = Isometric is set — you do not hand-write the screenX/screenY
transform from coordinates-depth.md for placement; that
transform is still what you reach for in a custom minimap, a non-Godot picking
overlay, or when reasoning about layout before art exists.TileMapLayer (or on the parent Node2D/CanvasItem
that groups your tiles with dynamic sprites — characters, props that move between
cells). Godot then reorders draw calls every frame by each node's effective Y
position (global Y plus any Y Sort Origin offset you set per-node), not by tile
index.Sprite2D's offset/pivot so
(0,0) in local space lands at the bottom-center visual contact point, or set
Y Sort Origin explicitly to compensate.TileMapLayer sorts between tiles on that layer and other Y-sorted
CanvasItems in the same Y-sort group — it does not reach across separate, non-Y-sorted
layers. Ground, props, and overlay layers still need an explicit z-index/layer order
for the layer stacking (per tile-spec.md's layer field:
ground | props | overlay); Y-Sort resolves ordering within the dynamic layer.https://docs.godotengine.org/en/stable/classes/class_tilemaplayer.htmlhttps://docs.godotengine.org/en/stable/classes/class_canvasitem.html#class-canvasitem-property-y-sort-enableddepth-strider/practice-iso-tiles on itch.io, ships a
step-by-step Godot 4.3 setup guide (cited via SRC-A ch.3).Unity's Tilemap has isometric support (Tilemap → Isometric / Isometric Z as Y
grid types), but the camera and rendering settings around it are the part teams
consistently get wrong. Checklist, in the order you'll hit the problems:
| # | Setting | Value | Why |
|---|---|---|---|
| 1 | Camera → Projection | Orthographic |
No perspective foreshortening; matches the flat-scale iso convention. |
| 2 | Coordinate plane | Build the map on the XZ plane, Y = up | Unity's own constants (Vector3.up, physics, lighting) assume Y-up. A 2D-style XY map fights the engine at every turn once you add real 3D lighting/shadows/physics. Convert 2D (x, y) tile data to (x, 0, y) world positions. |
| 3 | Camera → Near Clipping Plane | large negative, e.g. −1000 to −100000 | Orthographic cameras default to a 0.1 near-plane tuned for perspective cameras. Panning an iso camera with a small near-plane clips geometry that's technically "behind" the camera's z but still meant to render (a common symptom: buildings vanish when the camera pans past their origin). |
| 4 | Edit → Project Settings → Quality → Shadow Projection |
Close Fit (not Stable Fit) |
Stable Fit computes shadow cascades from the near/far clip planes. Once #3 pushes the near plane to a large negative number, Stable Fit produces wildly oversized/broken cascades. Close Fit recomputes tightly around visible geometry each frame instead. |
| 5 | Shadow cascades | Disable (No Cascades), and raise Shadow Distance to a large value (e.g. 10000) |
Cascades are a perspective-camera optimization (higher detail near camera, coarser far away) that doesn't map cleanly onto an orthographic view; disabling them plus a large shadow distance avoids shadows popping/clipping at cascade boundaries as the camera pans. |
| 6 | PBR / lighting | Verify the camera's actual 3D world position, not just its 2D-looking framing | Unity's PBR pipeline (specular, reflections) computes off the camera's real spatial position. If you built the rig by eyeballing a "looks isometric" angle without confirming the true 3D transform, reflections and specular highlights will look subtly wrong even though the silhouette reads as iso. Confirm in the Scene view that camera position/rotation match your intended rig (see the dual dimetric/true-iso rig table in blender-prerender.md — the same two rotations apply to a live 3D Unity camera, not just a Blender render camera). |
| 7 | Draw order | SpriteRenderer.sortingOrder, driven by the same (x+y) ascending key from coordinates-depth.md |
Unity does not auto-sort by depth for 2D sprites; you compute sortingOrder (or sortingLayer + an order-in-layer) per the y-sort doctrine and assign it every frame an object moves. For moving platforms / multi-floor stacks, this is exactly the AABB-in-iso-space case in coordinates-depth.md, not a single-point sort. |
Kenney's free tilesets (see asset-sourcing.md) ship Unity-ready
samples alongside Tiled .tmx samples, useful as a known-good starting rig to diff
your own project's settings against.
Source: SRC-B "Setting Up an Orthographic Camera in Unity"; Envato Tuts+ "Isometric Depth Sorting for Moving Platforms" (SRC-A ch.2, sortingOrder treatment).
Two paths, pick based on how much of the iso math you want Phaser to own for you:
A community plugin family exists under the "Phaser 3 isometric/axonometric plugin"
name — koreezgames/phaser3-isometric-plugin is the commonly-cited example — providing
an IsoSprite game object, iso physics (Arcade-style AABB in iso space), and
cart↔iso projection helpers wired into the Phaser API (this.add.isoSprite(...),
this.iso.projector). Its lineage traces to the Phaser-2-era
lewster32/phaser-plugin-isometric, the original IsoSprite/isometric-physics plugin
that the Phaser 3 forks in this space adapted forward. Verify the plugin's current
npm/GitHub status before depending on it in a new project — this is exactly the kind
of small-community plugin that can go quiet; check last-publish date and open issues,
not just star count. As of this writing (July 2026), koreezgames/phaser3-isometric-plugin
itself was last pushed 2018-11-07 (per the GitHub API) — nearly eight years stale despite
sporadic metadata updates since — so treat the whole "Phaser 3 isometric plugin" family
as dormant/unmaintained rather than active, and expect to patch or fork rather than
pull latest. If you adopt one anyway, pin an exact commit, not a version range.
The lower-risk long-term choice for a production game: keep tiles as ordinary Phaser
Image/Sprite objects on a standard (non-isometric) Phaser scene, and do the
projection yourself using the exact transforms in
coordinates-depth.md:
// Tile (x, y[, z]) -> screen position, using the canonical 2:1 dimetric transform.
function tileToScreen(x, y, z = 0, tileW = 64, tileH = 32, elevStep = 16) {
return {
screenX: (x - y) * (tileW / 2),
screenY: (x + y) * (tileH / 2) - z * elevStep,
};
}
Set each sprite's depth (Phaser's explicit render-order property, distinct from
scene z-position) to the (x + y) y-sort key each frame an object moves, exactly per
coordinates-depth.md:
sprite.setDepth(tileX + tileY);
Manual picking (screen→tile, for mouse/touch input) uses the inverse transform from the same reference, including its within-diamond correction for the ambiguous 2×2-screen-tile overlap region.
Reference: Generalist Programmer, "Phaser Isometric Game Tutorial (2026)" — covers cart→iso transform, tile rendering, depth sorting, mouse picking, and A* movement on top of manual placement (cited via SRC-A ch.3).
PixiJS has no first-party isometric mode; the ecosystem answer is Traviso.js, an open-source isometric engine built on top of PixiJS (path-finding, tile-based scene management, object placement — a fuller "engine" layer than the Phaser plugin above). As with the Phaser plugin, check Traviso's current maintenance status before adopting it for new work; it is a smaller, single-maintainer-style project historically, not a Foundation-backed package.
For anything Traviso doesn't cover, or if you'd rather not add the dependency, PixiJS is
low-level enough that the manual approach in §3b (plain sprites + the
coordinates-depth.md transforms + sprite.zIndex with
container.sortableChildren = true for the y-sort key) applies directly — PixiJS's
zIndex/sortableChildren mechanism is the direct analogue of Phaser's setDepth.
Source: SRC-A ch.2/ch.3 (Traviso.js, 197 GitHub stars at time of source research; PixiJS
iso game repos under the GitHub isometric topic).
Tiled is the de facto interchange format when a tileset or level needs to move between tools/engines rather than live natively in one editor:
.tmx/.tsx, or the JSON map format),
then import into Godot (native Tiled-map importer plugins exist in the Asset Library),
Unity (Tiled import packages, or hand-roll a .tmj→Tilemap loader), or a custom web
renderer (the JSON format is a straightforward parse — object layers, tile layers, and
properties all serialize cleanly).sheet-pack.py atlases — anchor/pivot mappingscripts/sheet-pack.py packs a directory of tiles into one
spritesheet PNG plus a JSON atlas. The real schema (see that script's docstring/--help
for the authoritative version — this mirrors the TexturePacker/Phaser "hash" atlas
family, not a flat rect) is nested per frame:
{
"meta": { "...": "..." },
"frames": {
"<name>": {
"frame": {"x": 0, "y": 0, "w": 0, "h": 0},
"sourceSize": {"w": 0, "h": 0},
"spriteSourceSize": {"x": 0, "y": 0, "w": 0, "h": 0},
"sourceW": 0,
"sourceH": 0,
"trimmed": false,
"rotated": false
}
}
}
frame is the packed rectangle (post-trim, if --trim was used). sourceSize is the
original untrimmed image dimensions. spriteSourceSize is where the trimmed frame sits
inside that original canvas — its x/y is the trim offset from the untrimmed
top-left. sourceW/sourceH are flat duplicates of sourceSize.w/sourceSize.h,
provided for importers that don't want to descend into the nested object — they alone
cannot recover the trim offset, only spriteSourceSize can.
Getting sprites to sit correctly once imported into an engine is entirely an anchor/pivot mapping problem: the atlas records each frame's trimmed pixel rect, but the engine needs to know where in that rect the tile's logical anchor point is — which, per the tile-spec.md discipline, is the visual-feet point, not the frame center.
General mapping recipe, engine-agnostic:
--trim removes transparent margin, recover the anchor position in the
trimmed frame as:
anchor_in_frame = anchor_in_source − spriteSourceSize.{x,y}
(spriteSourceSize.x/.y is exactly the trim offset the packer cropped from the
untrimmed top-left; sourceW/sourceH tell you the original canvas size but not
which corner was trimmed from, so use spriteSourceSize for the offset itself, not
the flat sourceW/sourceH aliases).Sprite2D.offset (in local pixels, or normalize and use centered =
false with an explicit offset).Sprite.pivot (normalized 0–1 within the sprite rect) when slicing the
packed PNG in the Sprite Editor — set pivot mode to "Custom" and enter the
normalized coordinate.Sprite.setOrigin(x, y) (normalized 0–1).Sprite.anchor.set(x, y) (normalized 0–1).If every tile in a set shares the same canvas size and anchor convention (the tile-spec.md template enforces this), steps 1–3 reduce to one constant offset applied uniformly at import time rather than a per-tile lookup.
scripts/sheet-pack.py — produces the atlas format consumed in §6.threejs-ops skill — for a real-time 3D engine target instead of a 2D
tile engine; see threejs-orthographic.md for the iso-camera
delta this skill owns on top of that sibling skill's general three.js scaffolding.