Build for Web
Shipping a game to the web means compiling to WebAssembly (or pure JS) and serving it over HTTPS. The pipeline differs per engine: Unity uses Build Settings + WebGL, Godot has an Export Preset, Phaser is just a bundler step. Every path ends with the same checklist: COOP/COEP headers, asset caching, and a tiny loader.
WebGL/Wasm export, hosting, and performance basics
EXAMPLE
# ===== Unity (WebGL build) =====
# 1) File -> Build Settings -> WebGL -> Switch Platform
# 2) Player Settings:
# - Compression Format: Brotli (smaller; the server must serve .br files)
# - Code Optimization: Speed
# - Strip Engine Code: Enabled
# - Decompression Fallback: only if your server cannot send Content-Encoding
# 3) Build to /build. Output:
# Build/Build.framework.js.br
# Build/Build.wasm.br
# Build/Build.data.br
# 4) Serve with the right Content-Encoding:
# nginx
# location ~ \.br$ {
# add_header Content-Encoding br;
# types { application/wasm wasm.br; } # set mime per extension
# }
# ===== Godot 4 (HTML5 export) =====
# 1) Project -> Export -> Add 'HTML5'.
# - 'Variant': threads (if you can serve COOP/COEP, see below)
# - 'Export with Debug' OFF for production
# 2) Output is index.html + .wasm + .pck. Serve them via your static host.
# ===== Phaser / pure web =====
# It is just a JS bundler. vite build / webpack / esbuild.
# Outputs a single index.html + chunks + assets.
# ===== HTTPS + cross-origin isolation (REQUIRED for SharedArrayBuffer / threads) =====
# For threaded WASM (Unity threads variant, Godot threads, Emscripten pthreads):
# Cross-Origin-Opener-Policy: same-origin
# Cross-Origin-Embedder-Policy: require-corp
# nginx example
# add_header Cross-Origin-Opener-Policy 'same-origin' always;
# add_header Cross-Origin-Embedder-Policy 'require-corp' always;
# add_header Cross-Origin-Resource-Policy 'same-origin' always;
# ===== Caching policy — long-cache the hashed assets =====
# Hashed (e.g. /assets/abc123.wasm)
# add_header Cache-Control 'public, max-age=31536000, immutable';
# index.html (changes on every release)
# add_header Cache-Control 'no-cache';
# ===== Asset budget — be honest with yourself =====
# Total size on first load:
# - Casual / mobile users: < 8 MB compressed (Brotli)
# - Desktop: < 20 MB compressed
# Strip unused textures, decimate meshes, compress audio (Opus 96 kbps),
# remove unused fonts. Most web-game perf complaints are wait time, not framerate.
# ===== Loading UX =====
# - Splash screen with a real progress bar from the wasm streaming download
# - Lazy-load levels: package level 1 into the initial bundle; fetch the rest on idle
# - Detect mobile and offer a smaller texture pack
# - WebGL2 detection — fall back to a 'browser not supported' page when missing
# ===== Performance gotchas on the web =====
# - GC pauses: avoid per-frame allocations (object pools, typed-array reuse)
# - Audio context locked until user gesture — start it on first click/tap
# - Tab loses focus -> requestAnimationFrame stops. Pause game logic and audio.
# - iOS WebGL is finicky; test on real hardware, not Simulator.
# ===== Distribution =====
# Self-host on a CDN (Cloudflare Pages, GitHub Pages, S3+CloudFront, Netlify).
# OR list on itch.io / Newgrounds / poki / crazygames — they handle hosting + analytics.
Why it matters
COOP/COEP headers are the gate to threaded WASM. Without them, SharedArrayBuffer is unavailable, and your engine drops to single-threaded mode — much slower. If your hosting service does not let you set those headers, fall back to the non-threaded export and accept the perf hit; never ship a build that silently underperforms.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Unity → WebGL. Godot → Web (HTML5). Phaser is already web. // Mind WebGL memory caps and audio autoplay rules.Try it Yourself »
Discussion
Loading…