Créer un jeu de plateforme complet avec Phaser 4 : scènes, textures générées, tilemap, physique Arcade, animations, ennemis, particules, sons, UI, mobile et déploiement.
Un niveau de jeu de plateforme est une grille : chaque case est vide, du sol ou une plateforme. Phaser gère ces grilles avec les tilemaps : une seule texture de tuiles, un tableau d'indices, et Phaser se charge de l'affichage, des collisions et des optimisations. Ce chapitre décrit le niveau dans un fichier texte lisible, le transforme en tilemap et installe une caméra qui défile.
Plutôt qu'un éditeur, on dessine le niveau avec des caractères. Chaque ligne du tableau map est une rangée de tuiles de 32 pixels.
{
"nom": "Prairie",
"temps": 120,
"map": [
"............................................................",
"............................................................",
"............................................................",
"............................................................",
"............................................................",
"............................................................",
"...........................................................#",
".............................................CC............#",
"..............CC............................====...........#",
".............====.................CC.......................#",
".........CC........CC............====......................#",
"........====......====.................CC..................#",
"...........................CCC........====.................#",
"..........................#####............................#",
"..P.CCC.....S..........^^.#####...S.........S...^.CCC.S..F.#",
"##################...#################...###################",
"##################...#################...###################"
]
}| Caractère | Signification |
|---|---|
. | vide |
# | sol (herbe si la case au-dessus est vide, terre sinon) |
= | plateforme traversable par le dessous |
P | position de départ du joueur |
C | pièce |
S | slime |
^ | pics |
F | drapeau d'arrivée |
60 colonnes × 17 rangées donnent un niveau de 1920 × 544 pixels, deux fois plus large que l'écran. Modifier le niveau revient à éditer ce fichier et recharger la page : c'est le meilleur moyen de tester des idées de level design en quelques secondes.
import Phaser from 'phaser'
import { TILE } from './BootScene.js'
export class GameScene extends Phaser.Scene {
constructor() {
super('Game')
}
init(data) {
this.niveau = data.niveau ?? 'niveau-1'
this.fini = false
}
create() {
const niveau = this.cache.json.get(this.niveau)
this.creerDecor(niveau.map)
this.creerCarte(niveau.map)
this.cameras.main.fadeIn(300)
}
}this.cache.json.get(cle) récupère le JSON chargé par la scène Preload. Les données chargées sont mises en cache pour tout le jeu : pas besoin de les recharger à chaque partie.
creerCarte(lignes) {
// Convertit les caractères du niveau en indices de tuiles (-1 = vide)
const donnees = lignes.map((ligne, y) =>
[...ligne].map((c, x) => {
if (c === '#') return lignes[y - 1]?.[x] === '#' ? 1 : 0
if (c === '=') return 2
return -1
})
)
this.carte = this.make.tilemap({ data: donnees, tileWidth: TILE, tileHeight: TILE })
const tileset = this.carte.addTilesetImage('tuiles')
this.sol = this.carte.createLayer(0, tileset, 0, 0)
this.sol.setCollision([0, 1])
// Plateformes traversables : collision uniquement par le dessus
this.sol.forEachTile(tuile => {
if (tuile.index === 2) tuile.setCollision(false, false, true, false)
})
}Étape par étape :
tuiles créée au chapitre 2. -1 signifie « case vide ». Pour le sol, on regarde la case du dessus : si elle n'est pas du sol, on affiche la tuile avec herbe (0), sinon la terre (1). C'est un autotiling minimaliste : le niveau reste simple à écrire, l'affichage s'adapte.this.make.tilemap crée une tilemap à partir d'un tableau à deux dimensions.addTilesetImage('tuiles') associe la texture de tuiles. Phaser la découpe en cases de 32 pixels.createLayer crée le calque affiché. Une tilemap peut avoir plusieurs calques : décor de fond, sol, premier plan.setCollision([0, 1]) rend solides les tuiles d'indices 0 et 1.
tuile.setCollision(gauche, droite, haut, bas) choisit les faces qui bloquent. Avec seulement la face du haut active, le joueur traverse la plateforme en sautant par-dessous et atterrit dessus en retombant. C'est le comportement des plateformes dans la plupart des jeux du genre, en deux lignes.
Pour des niveaux plus riches, l'éditeur gratuit Tiled permet de dessiner à la souris, avec plusieurs calques et des objets. Phaser charge directement ses exports :
// preload
this.load.tilemapTiledJSON('niveau-2', 'levels/niveau-2.json')
this.load.image('tuiles-tiled', 'images/tuiles.png')
// create
const carte = this.make.tilemap({ key: 'niveau-2' })
const tileset = carte.addTilesetImage('nom-du-tileset-dans-tiled', 'tuiles-tiled')
const sol = carte.createLayer('Sol', tileset)
sol.setCollisionByProperty({ solide: true })Les objets placés dans Tiled (pièces, ennemis) se récupèrent avec carte.getObjectLayer('Objets').objects. La logique du jeu reste la même que celle de ce cours.
Des nuages qui défilent moins vite que le sol donnent une impression de profondeur. C'est l'effet de parallaxe, obtenu avec setScrollFactor :
creerDecor(lignes) {
const largeur = lignes[0].length * TILE
for (let i = 0; i < 12; i++) {
const facteur = Phaser.Math.FloatBetween(0.2, 0.5)
this.add
.image(Phaser.Math.Between(0, largeur * facteur + 960), Phaser.Math.Between(30, 220), 'nuage')
.setScrollFactor(facteur)
.setScale(facteur * 3)
.setAlpha(0.9)
}
}Un objet avec un scrollFactor de 1 suit la caméra normalement ; à 0, il reste fixe à l'écran (utile pour une interface) ; à 0,3, il se déplace à 30 % de la vitesse de la caméra. Les nuages lointains sont aussi plus petits : taille et vitesse cohérentes renforcent l'illusion.
creerDecor est appelée avant creerCarte : dans Phaser, les objets sont dessinés dans l'ordre de création. Pour forcer un ordre, setDepth(n) place un objet au-dessus de ceux de profondeur inférieure.
Phaser.Math regroupe les utilitaires mathématiques : Between (entier aléatoire), FloatBetween, Clamp, Distance.Between, Angle.Between…
Chaque scène possède une caméra principale, this.cameras.main. On la limite au niveau et on lui demande de suivre le joueur :
creerCamera() {
const camera = this.cameras.main
camera.setBounds(0, 0, this.carte.widthInPixels, this.carte.heightInPixels)
camera.startFollow(this.joueur, true, 0.12, 0.12)
camera.setDeadzone(120, 80)
}setBounds empêche la caméra de montrer ce qui est hors du niveaustartFollow(cible, arrondi, lerpX, lerpY) : le lerp à 0,12 fait rattraper la cible en douceur au lieu de la coller rigidement ; arrondi à true évite le flou des sprites en pixel artsetDeadzone(120, 80) définit une zone au centre de l'écran dans laquelle le joueur peut bouger sans que la caméra réagisse, ce qui rend le défilement moins nerveuxLe joueur n'existe pas encore : cette méthode sera appelée au chapitre suivant. En attendant, pour explorer le niveau, ajoutez temporairement ce code à la fin de create :
this.cameras.main.setBounds(0, 0, this.carte.widthInPixels, this.carte.heightInPixels)
this.fleches = this.input.keyboard.createCursorKeys()et cette méthode :
update() {
if (this.fleches.left.isDown) this.cameras.main.scrollX -= 8
if (this.fleches.right.isDown) this.cameras.main.scrollX += 8
}
Ouvrez http://localhost:5173/?debug : grâce à l'option ajoutée dans la configuration, la physique Arcade dessine les corps et les faces de collision. Pour les tuiles, on peut aussi visualiser les collisions avec :
const debug = this.add.graphics().setAlpha(0.6)
this.sol.renderDebug(debug, { tileColor: null, collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200) })Vous verrez les plateformes traversables marquées seulement sur leur face supérieure.

Un tableau de chaînes, une tilemap, un calque avec collisions et une caméra bornée : le monde du jeu existe. Il lui manque un habitant. Le chapitre suivant crée le joueur, avec la physique, le clavier et les animations.