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. window.Wavedash is installed synchronously by the host before any other script runs, so the EM_JS bridge can call .init() directly. Setting Module in a module script and then injecting game.js dynamically keeps both ordering constraints obvious in one place:
<!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.