$npx -y skills add Yakoub-ai/phaser4-gamedev --skill phaser-tilemapThis skill should be used when the user asks to "add tilemap", "create a level", "Tiled editor", "tile collision", "object layer", "create map", "tilemap not showing", "level design", "tile layer", "game map", "spawn point", "trigger zone", or "map collision".
| 1 | # Phaser 4 Tilemaps |
| 2 | |
| 3 | Phaser 4 has first-class support for Tiled map files (JSON format). The recommended workflow is: design in Tiled → export JSON → load in Phaser → create layers → set up collision. |
| 4 | |
| 5 | ## Tiled Editor Workflow |
| 6 | |
| 7 | Download Tiled for free at [mapeditor.org](https://www.mapeditor.org/). |
| 8 | |
| 9 | ### Creating a New Map |
| 10 | |
| 11 | 1. File → New → New Map |
| 12 | 2. Set **Orientation** to Orthogonal (most 2D games) or Isometric (top-down RPG/strategy) |
| 13 | 3. Set **Tile layer format** to CSV or Base64 — both work with Phaser |
| 14 | 4. Set **Tile size** to match your tileset (common: 16x16, 32x32) |
| 15 | 5. Set **Map size** in tiles (e.g. 40 wide × 23 tall for a 1280x736 map at 32px tiles) |
| 16 | |
| 17 | ### Adding a Tileset |
| 18 | |
| 19 | 1. In the Tilesets panel (bottom right), click the **+** button → New Tileset |
| 20 | 2. Set **Name** — this exact name is used in `map.addTilesetImage()` as the first argument |
| 21 | 3. Set **Type** to Based on Tileset Image |
| 22 | 4. Browse to your PNG, set tile width/height to match |
| 23 | 5. Margin and spacing: set these if your tileset has padding between tiles (often 0) |
| 24 | |
| 25 | ### Marking Collision Tiles |
| 26 | |
| 27 | 1. Select your tileset in the Tilesets panel |
| 28 | 2. Click the wrench icon (**Edit Tileset**) to open the tileset editor |
| 29 | 3. Select the tiles that should collide (click to select, Ctrl+click for multiple) |
| 30 | 4. In the Properties panel (left side), click the **+** button to add a property |
| 31 | 5. Set **Name** to `collides`, **Type** to `bool`, **Value** to `true` |
| 32 | 6. Close the tileset editor |
| 33 | |
| 34 | In Phaser, call `layer.setCollisionByProperty({ collides: true })` to activate these tiles. |
| 35 | |
| 36 | ### Layer Naming Conventions |
| 37 | |
| 38 | Use consistent layer names — Phaser references them by string: |
| 39 | |
| 40 | | Layer Name | Type | Purpose | |
| 41 | |---|---|---| |
| 42 | | `Background` | Tile Layer | Sky, distant scenery — no collision | |
| 43 | | `Ground` | Tile Layer | Main walkable surface — collision enabled | |
| 44 | | `Hazards` | Tile Layer | Spikes, lava — overlap (not collide) | |
| 45 | | `Foreground` | Tile Layer | Trees, arches that render in front of player | |
| 46 | | `Objects` | Object Layer | Spawn points, triggers, enemies | |
| 47 | |
| 48 | ### Object Layer Usage |
| 49 | |
| 50 | Object Layers in Tiled let you place named points, rectangles, and polygons that Phaser can query. |
| 51 | |
| 52 | **Named objects** (for unique things like player spawn): |
| 53 | 1. Add Object Layer named `Objects` |
| 54 | 2. Select the Rectangle tool, place an object on the map |
| 55 | 3. In Properties, set **Name** to `PlayerSpawn` |
| 56 | |
| 57 | **Typed objects** (for groups of the same kind, like enemies): |
| 58 | 1. Place objects and set **Type** (Tiled 1.8: use **Class**) to `Enemy` |
| 59 | 2. Add custom properties: click **+**, add `health` (int, 100), `patrol` (bool, true) |
| 60 | |
| 61 | ### Export Settings |
| 62 | |
| 63 | 1. File → Export As → JSON Map Files (`.json`) |
| 64 | 2. In export options, enable **Embed tilesets** — this avoids external `.tsx` dependencies |
| 65 | 3. Save to `public/assets/tilemaps/level1.json` |
| 66 | 4. Place the tileset PNG at `public/assets/images/terrain.png` |
| 67 | |
| 68 | --- |
| 69 | |
| 70 | ## Loading Assets |
| 71 | |
| 72 | ```typescript |
| 73 | preload(): void { |
| 74 | // Key must match first arg of map.addTilesetImage() |
| 75 | this.load.tilemapTiledJSON('level1', 'assets/tilemaps/level1.json'); |
| 76 | |
| 77 | // Key must match second arg of map.addTilesetImage() |
| 78 | this.load.image('terrain', 'assets/images/terrain.png'); |
| 79 | } |
| 80 | ``` |
| 81 | |
| 82 | --- |
| 83 | |
| 84 | ## Creating the Map and Layers |
| 85 | |
| 86 | ```typescript |
| 87 | private map!: Phaser.Tilemaps.Tilemap; |
| 88 | private groundLayer!: Phaser.Tilemaps.TilemapLayer; |
| 89 | |
| 90 | create(): void { |
| 91 | this.map = this.make.tilemap({ key: 'level1' }); |
| 92 | |
| 93 | // First arg: tileset name as set in Tiled (must match exactly, case-sensitive) |
| 94 | // Second arg: the this.load.image() key |
| 95 | const tileset = this.map.addTilesetImage('terrain', 'terrain'); |
| 96 | |
| 97 | // Decorative background — no collision |
| 98 | const bgLayer = this.map.createLayer('Background', tileset!, 0, 0); |
| 99 | |
| 100 | // Main ground layer — collision enabled below |
| 101 | this.groundLayer = this.map.createLayer('Ground', tileset!, 0, 0)!; |
| 102 | |
| 103 | // Foreground renders above the player |
| 104 | const fgLayer = this.map.createLayer('Foreground', tileset!, 0, 0); |
| 105 | fgLayer!.setDepth(10); // player depth should be 1–9 |
| 106 | } |
| 107 | ``` |
| 108 | |
| 109 | **Common reason tilemap does not show:** the tileset name in `addTilesetImage` does not exactly match the name set in Tiled. Open the JSON file and check the `"name"` field inside `"tilesets"`. |
| 110 | |
| 111 | --- |
| 112 | |
| 113 | ## Collision |
| 114 | |
| 115 | ### By Property (recommended) |
| 116 | |
| 117 | Uses the `collides: true` property set in Tiled's tileset editor: |
| 118 | |
| 119 | ```typescript |
| 120 | this.groundLayer.setCollisionByProperty({ collides: true }); |
| 121 | this.physics.add.collider(this.player, this.groundLayer); |
| 122 | ``` |
| 123 | |
| 124 | ### By Tile Index Range |
| 125 | |
| 126 | Collide tiles with GID (global ID) 1 through 10: |
| 127 | |
| 128 | ```typescript |
| 129 | this.groundLayer.setCollisionBetween(1, 10); |
| 130 | ``` |
| 131 | |
| 132 | ### By Exclusion |
| 133 | |
| 134 | Collide all tiles except empty (-1) and a specific index: |
| 135 | |
| 136 | ```typescript |
| 137 | this.groundLayer.setCollisionByExclusion([-1, |