Web Workers pour la logique de jeu
Les Web Workers permettent d’exécuter du JavaScript dans un thread en arrière-plan. Pour les jeux, cela signifie que la physique, l’IA et la génération procédurale peuvent s’exécuter sans provoquer de chutes de framerate.
1) Quand utiliser des Workers
Cas adaptés :
- Simulation physique
- Recherche de chemin (A*, navmesh)
- Prise de décision par l’IA
- Génération procédurale
- Traitement des ressources (manipulation d’images, compression)
- Calculs mathématiques complexes (FFT, détection de collisions)
Cas moins adaptés :
- Rendu (les Workers ne peuvent pas accéder directement au DOM ou au Canvas)
- Très petites tâches (le surcoût lié au thread n’en vaut pas la peine)
2) Créer un Worker simple
worker.js :
self.onmessage = (e) => {
const { type, data } = e.data
if (type === 'calculate') {
const result = heavyCalculation(data)
self.postMessage({ type: 'result', data: result })
}
}
function heavyCalculation(input) {
// Expensive work here
return input * 2
}main.js :
const worker = new Worker('worker.js')
worker.onmessage = (e) => {
const { type, data } = e.data
if (type === 'result') {
console.log('Got result:', data)
}
}
worker.postMessage({ type: 'calculate', data: 42 })3) Workers intégrés au code (sans fichier séparé)
function createInlineWorker(fn) {
const blob = new Blob([`(${fn.toString()})()`], { type: 'text/javascript' })
return new Worker(URL.createObjectURL(blob))
}
const worker = createInlineWorker(() => {
self.onmessage = (e) => {
const result = e.data * 2
self.postMessage(result)
}
})4) Workers sous forme de modules ES
Vous pouvez écrire le code du worker sous forme de module ES et y utiliser import au lieu de importScripts. Transmettez { type: 'module' } au constructeur :
const worker = new Worker('physics-worker.js', { type: 'module' })Dans le worker, vous pouvez ensuite utiliser import pour les utilitaires partagés de mathématiques, de collisions ou d’IA, comme dans le thread principal, afin de ne pas dupliquer la logique du jeu. Les workers de type module sont pris en charge par Chrome et Edge 80+, Safari 15+ et Firefox 114+.
5) Exemple de Worker pour la physique
physics-worker.js :
const bodies = []
const FIXED_DT = 1 / 60
self.onmessage = (e) => {
const { type, data } = e.data
switch (type) {
case 'init':
initWorld(data)
break
case 'step':
step()
break
case 'addBody':
bodies.push(data)
break
}
}
function initWorld(config) {
// Initialize physics world
}
function step() {
// Update physics
for (const body of bodies) {
body.vy += 9.8 * FIXED_DT // Gravity
body.x += body.vx * FIXED_DT
body.y += body.vy * FIXED_DT
}
// Send positions back
self.postMessage({
type: 'positions',
data: bodies.map(b => ({ id: b.id, x: b.x, y: b.y, rotation: b.rotation }))
})
}6) Objets transférables pour améliorer les performances
Les grandes quantités de données peuvent être transférées sans copie :
// Main thread
const positions = new Float32Array(1000)
worker.postMessage(positions, [positions.buffer])
// positions is now unusable here (transferred)
// Worker
self.onmessage = (e) => {
const positions = e.data
// Work with positions
self.postMessage(positions, [positions.buffer])
}7) Worker de recherche de chemin
pathfinding-worker.js :
let grid = null
self.onmessage = (e) => {
const { type, data } = e.data
if (type === 'setGrid') {
grid = data
}
if (type === 'findPath') {
const path = aStar(data.start, data.end, grid)
self.postMessage({ type: 'path', id: data.id, path })
}
}
function aStar(start, end, grid) {
// A* implementation
const openSet = [start]
const cameFrom = new Map()
const gScore = new Map()
gScore.set(key(start), 0)
while (openSet.length > 0) {
// ... A* logic
}
return reconstructPath(cameFrom, end)
}
function key(pos) {
return `${pos.x},${pos.y}`
}8) Pool de Workers pour les tâches parallèles
class WorkerPool {
constructor(workerUrl, size = navigator.hardwareConcurrency || 4) {
this.workers = []
this.queue = []
this.available = []
for (let i = 0; i < size; i++) {
const worker = new Worker(workerUrl)
worker.onmessage = (e) => this.handleResult(worker, e)
this.workers.push(worker)
this.available.push(worker)
}
}
run(data) {
return new Promise((resolve) => {
const task = { data, resolve }
if (this.available.length > 0) {
this.dispatch(this.available.pop(), task)
} else {
this.queue.push(task)
}
})
}
dispatch(worker, task) {
worker._currentTask = task
worker.postMessage(task.data)
}
handleResult(worker, e) {
const task = worker._currentTask
task.resolve(e.data)
if (this.queue.length > 0) {
this.dispatch(worker, this.queue.shift())
} else {
this.available.push(worker)
}
}
terminate() {
this.workers.forEach(w => w.terminate())
}
}9) SharedArrayBuffer pour la synchronisation en temps réel
Lorsque l’isolation entre origines est activée, vous pouvez partager la mémoire :
// Main thread
const shared = new SharedArrayBuffer(1024)
const positions = new Float32Array(shared)
worker.postMessage({ type: 'init', buffer: shared })
// Worker reads/writes directly to shared memory
// No postMessage overhead for position updates10) OffscreenCanvas pour les Workers
Effectuez le rendu dans un Worker. OffscreenCanvas est désormais largement disponible selon le référentiel Baseline dans Chrome, Edge, Firefox et Safari (Safari l’a pris en charge à partir de la version 17.0 sur macOS et iOS) :
// Main thread
const canvas = document.getElementById('game')
const offscreen = canvas.transferControlToOffscreen()
worker.postMessage({ canvas: offscreen }, [offscreen])
// Worker
self.onmessage = (e) => {
const canvas = e.data.canvas
const ctx = canvas.getContext('2d')
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw...
requestAnimationFrame(render)
}
render()
}11) Gestion des erreurs
worker.onerror = (e) => {
console.error('Worker error:', e.message, e.filename, e.lineno)
}
// In worker
self.onerror = (e) => {
self.postMessage({ type: 'error', message: e.message })
}À lire aussi
- Publier un jeu web qui se charge rapidement
- Activer les threads Wasm (SharedArrayBuffer)
- Boucle de jeu avec Canvas 2D
- Bibliothèques de physique pour les jeux — exécuter Rapier ou Cannon-es dans un Worker
- Chargement progressif des ressources — décoder les ressources en dehors du thread principal
- Stack technique des jeux web en 2026 — comment les Workers s’intègrent à la stack WebGL/WebGPU/Wasm
Ressources externes
- MDN : API Web Workers — référence complète de l’API
- MDN : utiliser les Web Workers — guide étape par étape
- MDN : objets transférables — transfert de données sans copie
- MDN : SharedArrayBuffer — mémoire partagée entre les threads