CAPITOLO 10
Tilemaps
Introduzione
Una tilemap è una struttura dati che consente di creare e manipolare mappe composte da piccole immagini di forma regolare chiamate tile. Queste mappe possono essere usate per creare livelli 2D, applicare la fisica, rilevare collisioni e controllare le posizioni degli sprite.

Terminologia:
- Tile — singola immagine quadrata che compone la mappa
- Tileset — l'insieme di tutte le tile disponibili, raccolte in un'unica immagine PNG
- Layer — livello della mappa; una mappa può avere più layer sovrapposti (visuale, collisioni, oggetti)

Tiled è lo strumento consigliato per creare le mappe. È open source e gratuito.
Tile-extruder — eliminare il bleeding
Quando le tile sono esattamente di 32×32 px, Phaser può generare piccoli spazi visibili tra le tile adiacenti (bleeding). La libreria tile-extruder risolve questo problema aggiungendo a ogni tile un bordo di un pixel che replica i pixel del perimetro.

Esempio: 4 tile da 32×32 → PNG da 128×32 px → PNG estrusa da 136×34 px (ogni tile aggiunge 2px).

Installazione:
npm install --global tile-extruder
Utilizzo:
tile-extruder -w 32 -h 32 -i tilemap-example.png -o tilemap-extruded.png
| Parametro | Significato |
|---|---|
-w | Larghezza della tile in px |
-h | Altezza della tile in px |
-i | File PNG di input |
-o | File PNG di output |

L'immagine estrusa tilemap-extruded.png è quella da importare in Tiled e in Phaser.
Creare la mappa con Tiled
Nuova mappa
Apri Tiled. Vedrai la schermata iniziale dell'applicazione.

Clicca su Nuova Mappa. Si apre questa finestra di dialogo:

Imposta le dimensioni delle tile a 32×32 per far coincidere con il tileset. Clicca OK e ti troverai in questa visualizzazione:

Vai su File → Salva Come:

Salva la mappa come level-0.json (formato: JSON map files *.json):

Phaser 3.9 legge il formato Tiled JSON classico (non il più recente .tmj, introdotto in versioni successive di Tiled). Se usi una versione recente di Tiled, assicurati di esportare in "JSON map files (*.json)" e non nel formato .tmj.
Importare il tileset
Clicca su Nuovo Tileset:

Sfoglia e seleziona tilemap-extruded.png → spunta Incorpora nella mappa → imposta:
- Margine: 1 px
- Spaziatura: 2 px

Questi valori corrispondono al bordo aggiunto da tile-extruder. Clicca OK.

Layer world (visuale)
Rinomina il layer di default in world. Seleziona una tile e usa lo strumento riempimento per coprire l'area:

Oppure usa lo strumento pennello per disegnare manualmente perimetri e strutture:

Layer collision (collisioni)
Clic destro nell'area dei layer → Nuovo livello Tile → nome: collision:

Ora hai due layer. Le icone occhio e lucchetto servono per nascondere/visualizzare e abilitare/disabilitare l'editing sui singoli livelli:

Disegna su questo layer le stesse zone che devono collidere con il personaggio.
Impostare la proprietà collision sulle tile
- Clicca l'icona Modifica Tileset:

- Seleziona la tile da usare per le collisioni e aggiungi una proprietà personalizzata:

- Imposta tipo
boole nomecollision:

- Inserisci il valore
truee salva conCTRL+S:

Questa proprietà verrà letta da Phaser per attivare la collisione fisica.
Importare la tilemap in Phaser
GameData.ts
tilemaps: [
{
key: "level-0",
path: "assets/map/level-0.json",
},
],
Per il tileset, aggiungilo come spritesheet con margine e spaziatura:
spritesheets: [
{
name: "tilemap-extruded",
path: "assets/map/tilemap-extruded.png",
width: 32,
height: 32,
spacing: 2,
margin: 1,
},
],
Il name del tileset nel codice deve corrispondere esattamente al nome che hai assegnato in Tiled quando hai importato il tileset.
Static vs Dynamic Layer
In Phaser 3.9 un layer di tilemap può essere di due tipi distinti, creati con due metodi separati:
| Tipo | Metodo | Caratteristiche |
|---|---|---|
| Static | createStaticLayer() | Più veloce da renderizzare, ma non supporta tint, alpha, o modifiche alle tile a runtime |
| Dynamic | createDynamicLayer() | Leggermente più lento, ma supporta effetti per-tile (setAlpha, setTint) e modifiche runtime (putTileAt, removeTileAt) |
Poiché nel nostro esempio vogliamo rendere il layer collisioni semi-trasparente durante il debug (setAlpha), useremo Dynamic Layer per entrambi i layer.
Variabili nella scena
private map: Phaser.Tilemaps.Tilemap;
private tileset: Phaser.Tilemaps.Tileset;
private layer: Phaser.Tilemaps.DynamicTilemapLayer; // layer visuale
private layer2: Phaser.Tilemaps.DynamicTilemapLayer; // layer collisioni
Inizializzazione nel create()
1. Crea la tilemap dalla chiave:
this.map = this.make.tilemap({ key: "level-0" });
2. Adatta i bounds della camera e del mondo fisico alle dimensioni della mappa:
this.cameras.main.setBounds(
0,
0,
this.map.widthInPixels,
this.map.heightInPixels
);
this.physics.world.setBounds(
0,
0,
this.map.widthInPixels,
this.map.heightInPixels
);
3. Aggiungi il tileset alla mappa:
this.tileset = this.map.addTilesetImage("tilemap-extruded");
4. Crea i layer con createDynamicLayer():
// Layer visuale (depth alta = in primo piano)
this.layer = this.map.createDynamicLayer("world", this.tileset, 0, 0)
.setDepth(9)
.setAlpha(1);
// Layer collisioni (può essere trasparente in produzione)
this.layer2 = this.map.createDynamicLayer("collision", this.tileset, 0, 0)
.setDepth(0)
.setAlpha(0); // imposta a 1 durante il debug
5. Attiva le collisioni sul layer:
this.layer2.setCollisionByProperty({ collide: true });
6. Crea il collider tra il player e il layer:
this.physics.add.collider(
this._player,
this.layer2,
(_player: any, _tile: any) => {
// verifica proprietà personalizzate della tile
if (_tile.properties.exit === true) {
console.log("livello completato");
}
if (_tile.properties.death === true) {
// decrementa vita o game over
}
},
undefined,
this
);
La callback del collider riceve il gameObject e la tile che hanno interagito. Puoi aggiungere a ogni tile qualsiasi proprietà personalizzata in Tiled — exit, death, damage, speed — e leggerle qui per gestire comportamenti specifici.
Durante lo sviluppo imposta setAlpha(1) sul layer collisioni per vedere visivamente dove sono le tile attive. In produzione metti setAlpha(0) per nasconderle.
Proprietà personalizzate avanzate — exit e death
Puoi aggiungere ulteriori proprietà personalizzate alle tile per gestire comportamenti speciali. Ad esempio, una tile di uscita dal livello.
Nel Modifica Tileset, seleziona la tile con la freccia in alto e aggiungi due proprietà personalizzate:

Nel layer world, inserisci la tile che indica visivamente l'uscita:

Nel layer collision, posiziona la stessa tile (o una trasparente) con le proprietà collide=true e exit=true:

Quando il player collide con questa tile, la callback del collider rileva la proprietà exit=true:

Layer oggetti — posizionare gameObject sulla mappa
Un Object Layer in Tiled è un tipo speciale di layer che memorizza segnaposto sulla mappa. Serve a definire dove appariranno i gameObject dinamici (nemici, bonus, spawn point, checkpoint).
Configurare il layer in Tiled
Clic destro nell'area layer → Nuovo → Livello Oggetto → nome: gameObjects:

Seleziona la tile da usare come segnaposto e aggiungi una proprietà personalizzata di tipo string chiamata behaviour:

Inserisci come valore un oggetto JSON e posiziona le tile sulla mappa:

{"type":"bonus","score":50}
Se il valore della proprietà appare opaco in Tiled, significa che non è attivo. Clicca sul campo e aggiungi uno spazio alla fine della stringa per attivarlo, poi salva.

Salva level-0.json.
Recuperare gli oggetti in Phaser
Crea un metodo dedicato nella scena, da richiamare nel create() dopo aver inizializzato la mappa:
setupObjects(): void {
const _objLayer = this.map.getObjectLayer("gameObjects");
if (_objLayer != null) {
const _objects = _objLayer.objects as any[];
_objects.forEach((tile: Phaser.Tilemaps.Tile) => {
// leggi la proprietà behaviour come stringa JSON
const _objectValue = JSON.parse(tile.properties[0].value).type;
switch (_objectValue) {
case "bonus":
// istanzia il gameObject nella posizione della tile
// this.addBonus(new BonusCoin({ scene: this, x: tile.x, y: tile.y, key: "bonus-coin" }));
break;
case "enemy":
// istanzia il nemico
break;
}
});
}
}
Nel create():
create() {
// ... inizializzazione mappa ...
this.setupObjects();
}
Schema completo del create()
create() {
// 1. Crea la tilemap
this.map = this.make.tilemap({ key: "level-0" });
// 2. Bounds camera e physics
this.cameras.main.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);
this.physics.world.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);
// 3. Tileset
this.tileset = this.map.addTilesetImage("tilemap-extruded");
// 4. Layer visuale
this.layer = this.map.createDynamicLayer("world", this.tileset, 0, 0).setDepth(9);
// 5. Layer collisioni
this.layer2 = this.map.createDynamicLayer("collision", this.tileset, 0, 0)
.setDepth(0).setAlpha(0);
this.layer2.setCollisionByProperty({ collide: true });
// 6. Player (deve usare physics.add.sprite per avere il body)
this._player = this.physics.add.sprite(100, 100, "player");
this._player.body.setCollideWorldBounds(true);
// 7. Camera segue il player
this.cameras.main.startFollow(this._player, true, 0.1, 0.1);
// 8. Collider player <-> mappa
this.physics.add.collider(this._player, this.layer2);
// 9. Object layer
this.setupObjects();
}
Documentazione ufficiale: Tilemaps namespace


Le tilemap sono il salto di qualità che trasforma un prototipo in un livello vero. Finché disegni il mondo con sprite posizionati a mano nel codice, stai lavorando contro te stesso. Con Tiled puoi costruire il livello visivamente e importarlo in tre righe.
Il workflow da memorizzare è: Tiled esporta JSON → Phaser carica JSON → layer separati per visuale e collisioni. Una volta che hai questo schema in testa, aggiungere nuovi livelli diventa banale.
Il layer collision trasparente è uno dei pattern più potenti: la mappa che vede il giocatore è una cosa, la mappa che "sente" la fisica è un'altra. Questa separazione ti dà controllo chirurgico su dove il personaggio può camminare.
Tip pro: le proprietà personalizzate delle tile (exit, death, damage) sono il modo professionale per gestire i livelli. Invece di hardcodare la logica nel codice, la metti nella mappa. Il designer può cambiare dove si trova la "porta di uscita" senza toccare il TypeScript. 🗺️