raylib compiles to WebAssembly through its official Emscripten backend (PLATFORM=Web). You get the same BeginDrawing / EndDrawing / IsKeyDown API in the browser that you use on desktop — just link against a Web-platform build of libraylib.a.
Setup
You need:
- The Emscripten SDK (activate with
source ./emsdk_env.sh) - A Web-platform build of raylib. The example's
build.shclones raylib 5.5 and buildslibraylib.awithemcmake cmake . -DPLATFORM=Web && emmake make raylib.
Point the build at your raylib source with RAYLIB_PATH=/path/to/raylib if you already have one.
SDK integration
Wavedash injects Wavedash into the page before your Emscripten module loads. Use EM_JS to call the SDK from C — same pattern as any Emscripten project:
Calling Wavedash.init() is required. Your game stays hidden behind the Wavedash loading screen until you do. Call it once your game is ready to play.
#include <raylib.h>
#include <emscripten.h>
EM_JS(void, wavedash_init, (), { Wavedash.init(); });
EM_JS(void, wavedash_progress, (double p), { Wavedash.updateLoadProgressZeroToOne(p); });
static void tick(void) {
BeginDrawing();
// ... draw frame ...
EndDrawing();
}
int main(void) {
InitWindow(900, 600, "My Game");
SetTargetFPS(60);
wavedash_progress(1.0);
wavedash_init();
emscripten_set_main_loop(tick, 0, 1);
return 0;
}
Call wavedash_progress(...) with intermediate values during async asset loading. wavedash_init() automatically signals load completion, so call it last.
Build script
#!/usr/bin/env sh
set -eu
RAYLIB_DIR="${RAYLIB_PATH:-$PWD/raylib}"
mkdir -p build
emcc src/main.c -O2 \
-I"$RAYLIB_DIR/src" \
"$RAYLIB_DIR/src/raylib/libraylib.a" \
-DPLATFORM_WEB \
-s USE_GLFW=3 \
-s ASYNCIFY \
-s ALLOW_MEMORY_GROWTH=1 \
-s MODULARIZE=0 \
-s ENVIRONMENT=web \
-s GL_ENABLE_GET_PROC_ADDRESS=1 \
-o build/game.js
cp web/index.html build/index.html
USE_GLFW=3 and ASYNCIFY are the core flags raylib needs on Emscripten. Don't add -s FULL_ES3=1 — raylib's CMake Web build defaults to OpenGL ES 2, and mixing ES3 client-array emulation with an ES2 library leaves the GL context uninitialized at draw time. The --shell-file approach also works if you prefer raylib's generated HTML over a custom shell.
HTML shell
raylib's Emscripten port reads Module.canvas during GL context creation, so Module has to be set before game.js starts executing. And window.Wavedash is injected as a Promise — resolve it before the EM_JS bridge tries to call .init() synchronously. The cleanest way to satisfy both is to do both in a module script and then inject game.js dynamically:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: #000; }
canvas { display: block; width: 100%; height: 100%; outline: none; }
</style>
</head>
<body>
<canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<script>
window.Module = { canvas: document.getElementById("canvas") };
const s = document.createElement("script");
s.src = "./game.js";
document.body.appendChild(s);
</script>
</body>
</html>
wavedash.toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./build"
Other SDK features
Add EM_JS wrappers for whatever else you need. UTF8ToString converts C strings to JavaScript:
EM_JS(void, wavedash_submit_score, (const char* id, int score), {
Wavedash.uploadLeaderboardScore(UTF8ToString(id), score, true);
});
See the SDK reference for the full API.