Web Workers für Spiellogik
Mit Web Workers läuft JavaScript in einem Hintergrund-Thread. Für Spiele heißt das: Physik, KI und prozedurale Generierung laufen, ohne dass Frames wegbrechen.
1) Wann Workers sinnvoll sind
Gute Kandidaten:
- Physiksimulation
- Pathfinding (A*, Navmesh)
- KI-Entscheidungen
- Prozedurale Generierung
- Asset-Verarbeitung (Bildbearbeitung, Kompression)
- Aufwendige Mathematik (FFT, Kollisionserkennung)
Weniger geeignet:
- Rendering (Workers kommen nicht direkt an DOM oder Canvas)
- Sehr kleine Aufgaben (der Thread-Overhead lohnt nicht)
2) Einen einfachen Worker erstellen
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) Inline-Workers (ohne eigene Datei)
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 als ES-Module
Du kannst Worker-Code als ES-Modul schreiben und darin import statt importScripts nutzen. Übergib dazu { type: 'module' } an den Konstruktor:
const worker = new Worker('physics-worker.js', { type: 'module' })Im Worker kannst du dann gemeinsame Helfer für Mathematik, Kollision oder KI genauso importieren wie im Haupt-Thread, die Spiellogik wird also nicht doppelt gepflegt. Modul-Workers werden in Chrome und Edge ab 80, Safari ab 15 und Firefox ab 114 unterstützt.
5) Beispiel Physik-Worker
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) Transferable Objects für Performance
Große Daten lassen sich ohne Kopieren übergeben:
// 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) Pathfinding-Worker
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) Worker-Pool für parallele Aufgaben
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 für Echtzeit-Synchronisation
Mit aktivierter Cross-Origin-Isolation kannst du Speicher teilen:
// 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 für Workers
Rendern im Worker. OffscreenCanvas ist inzwischen als Baseline breit verfügbar, in Chrome, Edge, Firefox und Safari (Safari unterstützt es ab 17.0 auf macOS und 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) Fehlerbehandlung
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 })
}Verwandt
- Ein Web-Spiel ausliefern, das schnell lädt
- Wasm-Threads (SharedArrayBuffer) aktivieren
- Canvas-2D-Spielschleife
- Physik-Bibliotheken für Spiele — Rapier oder Cannon-es in einem Worker betreiben
- Streaming von Assets — Assets abseits des Haupt-Threads dekodieren
- Tech-Stack für Web-Spiele 2026 — wo Workers in den Stack aus WebGL, WebGPU und Wasm passen
Externe Ressourcen
- MDN: Web Workers API — vollständige API-Referenz
- MDN: Using Web Workers — Schritt-für-Schritt-Anleitung
- MDN: Transferable objects — Datenübergabe ohne Kopie
- MDN: SharedArrayBuffer — geteilter Speicher zwischen Threads