Poner el nivel en el código funciona hasta el segundo nivel. Después necesitas un editor, y el editor es Tiled — gratis, open source, y Phaser lo carga nativo.
La idea
Un tilemap es una cuadrícula de índices. En vez de guardar 3000 sprites, guardas 3000 números que apuntan a un tileset. Es compacto, rapidísimo de renderizar, y editable visualmente.
Estructura mínima
En Tiled, monta tres capas:
suelo— decorativa, sin colisiónparedes— con colisiónobjetos— capa de objetos, no de tiles: spawns, puertas, enemigos
Separar la colisión en su propia capa es lo que te deja tener suelo bonito sin que cada baldosa decorativa bloquee al jugador.
Exporta como JSON, no como TMX. Phaser lee JSON.
Cargarlo
preload() {
this.load.image('tiles', 'tileset.png')
this.load.tilemapTiledJSON('nivel1', 'nivel1.json')
}
create() {
const map = this.make.tilemap({ key: 'nivel1' })
// "tileset-nombre-en-Tiled" tiene que coincidir EXACTO con el de Tiled
const tileset = map.addTilesetImage('tileset-nombre-en-Tiled', 'tiles')
map.createLayer('suelo', tileset, 0, 0)
const paredes = map.createLayer('paredes', tileset, 0, 0)
paredes.setCollisionByProperty({ colisiona: true })
this.physics.add.collider(this.player, paredes)
}
El error número uno: el nombre del tileset en addTilesetImage debe coincidir literalmente con el que le pusiste en Tiled, no con el nombre del archivo. Si no coincide, no da error — simplemente no se dibuja nada, y te vuelves loco.
Colisión por propiedad
setCollisionByProperty({ colisiona: true }) requiere que en Tiled marques los tiles: selecciona el tileset, elige los tiles sólidos, y añádeles una propiedad booleana colisiona = true.
La alternativa es setCollisionByExclusion([-1]) (todo colisiona menos el vacío), más rápida de montar y mucho menos flexible. Empieza por propiedades; te lo agradecerás cuando quieras plataformas atravesables desde abajo.
La capa de objetos
Aquí es donde el tilemap deja de ser decorado y pasa a ser diseño de nivel:
const spawns = map.getObjectLayer('objetos').objects
spawns.forEach(obj => {
if (obj.name === 'jugador') {
this.player = this.physics.add.sprite(obj.x, obj.y, 'heroe')
}
if (obj.name === 'enemigo') {
this.enemigos.create(obj.x, obj.y, 'slime')
}
})
Ahora puedes colocar enemigos, checkpoints y puertas en el editor y el código no cambia. Ese es el objetivo: que diseñar un nivel no requiera programar.
Rendimiento
Los tilemaps grandes se dibujan enteros aunque no se vean. Si notas caídas:
paredes.setCullPadding(2) // sólo dibuja lo cercano a la cámara
Y evita mapas de 500×500 en una sola capa; divide por zonas y cárgalas por separado.
Sesión 5 del curso presencial de Phaser.