Skip to main content

Overview

Cub3D can be compiled to WebAssembly (WASM) using Emscripten, allowing it to run in modern web browsers. The web version uses SDL2 compiled to JavaScript with full audio, graphics, and gamepad support.
See the live demo at omelhorsite.pt/en/games/cub3d

Prerequisites

Install Emscripten

Emscripten is required to compile C/C++ code to WebAssembly:
1

Download Emscripten SDK

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
2

Install Latest Version

./emsdk install latest
./emsdk activate latest
3

Set Environment Variables

source ./emsdk_env.sh
Add this to your ~/.bashrc or ~/.zshrc to make it permanent:
source "/path/to/emsdk/emsdk_env.sh"
4

Verify Installation

emcc --version
Should output something like:
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.x
For detailed installation instructions, see the official Emscripten documentation

Building for Web

Compile Command

Use the dedicated Emscripten Makefile:
make -f emcc.Makefile
This generates three main files:

cub3d.html

HTML shell and entry point

cub3d.js

JavaScript loader (~368KB)

cub3d.wasm

WebAssembly binary (~1.7MB)
Plus a data file:
  • cub3d.data - Preloaded assets (~12MB)

Emscripten Configuration

The emcc.Makefile is specifically configured for web builds:

Compiler Settings

CC = emcc
CFLAGS = -Wall -Wextra -Werror -O3 -D W_WIDTH=400 -D W_HEIGHT=300
The web build uses a smaller resolution (400x300) for better performance. You can adjust W_WIDTH and W_HEIGHT as needed.

Threading Configuration

CFLAGS += -pthread
LDLIBS += -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=5
Enables multi-threading with a pool of 5 worker threads.

SDL2 Integration

LDLIBS += -s USE_SDL=2
Uses Emscripten’s SDL2 port, which translates SDL calls to WebGL and WebAudio.

Safety and Debugging

LDLIBS += -s ASSERTIONS=1 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2
  • ASSERTIONS: Runtime checks for common errors
  • SAFE_HEAP: Memory access validation
  • STACK_OVERFLOW_CHECK: Detects stack overflows
These flags add runtime overhead. For production builds, consider disabling them:
LDLIBS += -s ASSERTIONS=0 -s SAFE_HEAP=0 -s STACK_OVERFLOW_CHECK=0

Asyncify

LDLIBS += -s ASYNCIFY=1
Enables synchronous-looking code to work with asynchronous browser APIs.

Asset Preloading

LDLIBS += --preload-file maps --preload-file assets/42lisboa
LDLIBS += --preload-file assets/fonts --preload-file assets/sounds
LDLIBS += --preload-file assets/textures --preload-file assets/wolf3d
LDLIBS += --preload-file assets/pt/textures/dommiguelilow.bmp
LDLIBS += --preload-file assets/pt/textures/portugal.bmp
Embeds game assets into the virtual filesystem (cub3d.data).
The --preload-file flag packages assets so they’re available via normal file I/O in the WASM module.

Memory Configuration

LDLIBS += -s INITIAL_MEMORY=268435456 -s STACK_SIZE=2MB
  • INITIAL_MEMORY: 256MB initial heap
  • STACK_SIZE: 2MB stack per thread
If you encounter “out of memory” errors, increase INITIAL_MEMORY:
-s INITIAL_MEMORY=536870912  # 512MB

ES6 Module Export

LDLIBS += -s ENVIRONMENT='web,worker'
LDLIBS += -s EXPORT_ES6=1 -s MODULARIZE=1
Exports as an ES6 module for modern JavaScript integration.

Custom HTML Shell

LDLIBS += --shell-file minimal.html
Uses minimal.html as the template for the final HTML page.

Custom HTML Shell

The minimal.html file provides the page structure. You can customize it:
minimal.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Cub3D</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      background: #000;
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
    }
    canvas {
      image-rendering: pixelated;
      image-rendering: crisp-edges;
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>
  {{{ SCRIPT }}}
</body>
</html>
The {{{ SCRIPT }}} placeholder is replaced by Emscripten with the module loader code.

Local Testing

Python HTTP Server

The project includes a simple server:
python3 server.py
This starts a local server (typically on http://localhost:8000) with proper MIME types and CORS headers.
Opening cub3d.html directly via file:// won’t work due to:
  • CORS restrictions on loading .wasm and .data files
  • SharedArrayBuffer requiring specific headers

Required Headers

For SharedArrayBuffer support (needed for threading), serve with:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
The included server.py handles this automatically.

Production Deployment

Build for Production

  1. Optimize the build:
    CFLAGS = -Wall -Wextra -Werror -O3 -D W_WIDTH=1024 -D W_HEIGHT=768
    LDLIBS += -s ASSERTIONS=0 -s SAFE_HEAP=0
    
  2. Build:
    make -f emcc.Makefile
    
  3. Deploy these files:
    • cub3d.html
    • cub3d.js
    • cub3d.wasm
    • cub3d.data

Server Configuration

server {
    listen 80;
    server_name your-domain.com;
    root /var/www/cub3d;
    index cub3d.html;

    # Required for SharedArrayBuffer
    add_header Cross-Origin-Opener-Policy same-origin;
    add_header Cross-Origin-Embedder-Policy require-corp;

    # Correct MIME types
    types {
        application/wasm wasm;
        application/javascript js;
        application/octet-stream data;
    }

    # Compression
    gzip on;
    gzip_types application/javascript application/wasm;
}

Performance Optimization

Reduce Asset Size

1

Compress Textures

Optimize BMP files or convert to WebP (requires code changes)
2

Limit Preloaded Assets

Only include essential maps and textures:
LDLIBS += --preload-file maps/hub.cub
LDLIBS += --preload-file assets/textures
3

Use Lazy Loading

For large asset packs, implement runtime loading instead of preloading

Compilation Optimizations

# Link-time optimization
CFLAGS += -flto

# Aggressive optimization (careful: may break some code)
CFLAGS += -Oz  # Optimize for size

# Closure compiler (advanced JS optimization)
LDLIBS += --closure 1
Test thoroughly after enabling aggressive optimizations, as they can introduce subtle bugs.

Troubleshooting

Check browser console for errors. Common causes:
  • Missing COOP/COEP headers (required for SharedArrayBuffer)
  • Failed to load .wasm or .data files (check network tab)
  • CORS errors (use proper HTTP server, not file://)
Increase heap size:
LDLIBS += -s INITIAL_MEMORY=536870912  # 512MB
LDLIBS += -s ALLOW_MEMORY_GROWTH=1     # Allow dynamic growth
Browsers require user interaction before playing audio. Add a “Click to Start” overlay.
  • Ensure gamepad is connected before page loads
  • Check browser gamepad support: gamepad-tester.com
  • Press any button to wake up the gamepad
  • Reduce resolution (W_WIDTH and W_HEIGHT)
  • Disable debug features (ASSERTIONS, SAFE_HEAP)
  • Use -O3 optimization
  • Reduce thread count (PTHREAD_POOL_SIZE)

Browser Compatibility

  • Chrome/Edge 90+
  • Firefox 90+
  • Safari 15.2+
  • Opera 76+

Makefile Targets

make -f emcc.Makefile
# or
make -f emcc.Makefile all

Next Steps

Building Native

Build for Linux/macOS desktop

Controls

Learn keyboard and gamepad controls

Build docs developers (and LLMs) love