用 Web Worker 处理游戏逻辑
Web Worker 允许你在后台线程中运行 JavaScript。对于游戏而言,这意味着物理模拟、AI 和程序化生成可以在不导致掉帧的情况下运行。
1)何时使用 Worker
适合的任务:
- 物理模拟
- 寻路(A*、导航网格)
- AI 决策
- 程序化生成
- 资源处理(图像处理、压缩)
- 复杂数学运算(FFT、碰撞检测)
不太适合:
- 渲染(Worker 无法直接访问 DOM/Canvas)
- 非常小的任务(不值得承担线程开销)
2)创建基础 Worker
worker.js:
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:
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)内联 Worker(无需单独文件)
js
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)ES 模块 Worker
你可以将 Worker 代码编写为 ES 模块,并在其中使用 import,而不是 importScripts。将 { type: 'module' } 传给构造函数:
js
const worker = new Worker('physics-worker.js', { type: 'module' })随后,你可以在 Worker 中像在主线程上一样 import 共享的数学、碰撞或 AI 辅助模块,从而避免重复编写游戏逻辑。模块 Worker 支持 Chrome 和 Edge 80+、Safari 15+ 以及 Firefox 114+。
5)物理模拟 Worker 示例
physics-worker.js:
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)使用可转移对象提升性能
无需复制即可转移大量数据:
js
// 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
pathfinding-worker.js:
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 池
js
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 实时同步
启用跨源隔离后,你可以共享内存:
js
// 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)在 Worker 中使用 OffscreenCanvas
在 Worker 中进行渲染。OffscreenCanvas 目前已成为 Baseline 广泛可用功能,可用于 Chrome、Edge、Firefox 和 Safari(Safari 从 macOS 和 iOS 17.0 开始支持):
js
// 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)错误处理
js
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 })
}相关内容
- 发布加载迅速的网页游戏
- 启用 Wasm 线程(SharedArrayBuffer)
- Canvas 2D 游戏循环
- 游戏物理库 — 在 Worker 中运行 Rapier 或 Cannon-es
- 流式资源加载 — 在主线程之外解码资源
- 2026 年网页游戏技术栈 — Worker 如何融入 WebGL/WebGPU/Wasm 技术栈
外部资源
- MDN:Web Workers API — 完整的 API 参考
- MDN:使用 Web Worker — 分步指南
- MDN:可转移对象 — 零拷贝数据传输
- MDN:SharedArrayBuffer — 线程之间的共享内存