Skip to content

Web (Emscripten)

This guide covers building ESEngine games for deployment in web browsers using Emscripten.

Build Configuration

CMake Options

Terminal window
emcmake cmake -B build_web \
-DES_BUILD_WEB=ON \
-DCMAKE_BUILD_TYPE=Release
cmake --build build_web

Output Files

After building, you’ll have:

build_web/
├── MyGame.html # Standalone HTML page
├── MyGame.js # Emscripten glue code
├── MyGame.wasm # WebAssembly binary
└── MyGame.data # Asset bundle (if using PRELOAD)

Embedding in HTML

Standalone Page

The generated .html file works out of the box:

Terminal window
npx serve build_web
# Open http://localhost:3000/MyGame.html

Custom HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Game</title>
<style>
body { margin: 0; background: #000; }
#canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var Module = {
canvas: document.getElementById('canvas'),
onRuntimeInitialized: function() {
console.log('Game loaded!');
}
};
</script>
<script src="MyGame.js"></script>
</body>
</html>

Asset Loading

Bundle assets into .data file at build time:

CMakeLists.txt
target_link_options(MyGame PRIVATE
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/assets@/assets"
)

Access normally in code:

Texture texture = Texture::load("/assets/player.png");

Runtime Loading

Load assets via HTTP at runtime:

void onInit() override {
// Async load
Texture::loadAsync("https://example.com/player.png",
[this](Texture tex) {
playerTexture = tex;
},
[](const std::string& error) {
ES_LOG_ERROR("Failed to load: {}", error);
}
);
}

Module Configuration

Memory

target_link_options(MyGame PRIVATE
"SHELL:-s INITIAL_MEMORY=64MB"
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s MAXIMUM_MEMORY=256MB"
)

WebGL

target_link_options(MyGame PRIVATE
"SHELL:-s USE_WEBGL2=1" # WebGL 2.0
"SHELL:-s MIN_WEBGL_VERSION=2"
"SHELL:-s MAX_WEBGL_VERSION=2"
)

Optimizations

target_link_options(MyGame PRIVATE
"SHELL:-O3"
"SHELL:-flto"
"SHELL:-s ASSERTIONS=0"
"SHELL:--closure 1"
)

Browser APIs

Local Storage

#include <emscripten.h>
void saveGame(const std::string& data) {
EM_ASM({
localStorage.setItem('savedata', UTF8ToString($0));
}, data.c_str());
}
std::string loadGame() {
char* result = (char*)EM_ASM_PTR({
var data = localStorage.getItem('savedata') || '';
var len = lengthBytesUTF8(data) + 1;
var buf = _malloc(len);
stringToUTF8(data, buf, len);
return buf;
});
std::string data(result);
free(result);
return data;
}

Fullscreen

void toggleFullscreen() {
EM_ASM({
if (!document.fullscreenElement) {
Module.canvas.requestFullscreen();
} else {
document.exitFullscreen();
}
});
}

Deployment

GitHub Pages

  1. Build with correct base path

    Terminal window
    emcmake cmake -B build_web -DES_BUILD_WEB=ON
    cmake --build build_web
  2. Copy to docs/ or gh-pages branch

    Terminal window
    cp build_web/MyGame.* docs/
  3. Enable GitHub Pages in repository settings

itch.io

  1. Zip the build output files
  2. Upload to itch.io as HTML5 game
  3. Set viewport size in itch.io dashboard

Cloudflare Pages / Vercel

Just point to your build output directory. Both platforms serve static files directly.

Performance Tips

Troubleshooting

IssueSolution
Blank screenCheck browser console for errors
Memory errorIncrease INITIAL_MEMORY
Slow loadingEnable compression on server
Touch not workingEnsure canvas has tabindex="0"