Skip to content

Working for Web (HTML5)

Ben McAvoy edited this page Mar 19, 2024 · 64 revisions

raylib C code can be compiled to WebAssembly to run on Web. Compilation process is very similar to the one used for desktop platforms with gcc compiler but it requires a different toolchain: emscripten SDK.

emscripten provides a set of tools to compile C code to WebAssembly, the main tool provided is the emcc compiler. emcc is actually a direct replacement for gcc, so, anyone with experience compiling code directly in the command-line should not have much trouble to use emcc.

There are some additional compilation flags for emcc compilation and code linkage, so, a Makefile is provided in raylib/src/Makefile to simplify the compilation process, it only requires defining PLATFORM_WEB to use the correct compilation flags.

The complete process to compile for web is detailed below. The main steps to follow are:

  1. Install emscripten toolchain
  2. Compile raylib library
  3. Build examples for the web
  4. Setup raylib game for web
  5. Compile raylib game for web
  6. Test raylib game on web
  7. Upload raylib web game to itch.io

Note that it's VERY important to understand the different steps of the process. If you expect to find an already setup solution, ready to use out-of-the-box, it's very probable that it fails at some point. So, understanding the process is crucial to be able to configure web compilation with ANY build system.

1. Install emscripten toolchain

Download emscripten SDK from GitHub, download as a zip and decompress it in C:\emsdk folder. Those pages also provide detailed instructions.

emsdk requires Python and Git installed and accessible from system path to be called from emsdk prompt.

After decompression and installing required tools (and making sure python and git can be called from command line), go to emsdk installation folder and run emcmdprompt.bat (on Windows, on Linux proceed to next step).

Execute the following commands to install and activate latest emscripten tools:

emsdk update
emsdk install latest
emsdk activate latest

On Linux and macOS you will also need to set the proper environment so that raylib build system can find the Emscripten compiler:

source ./emsdk_env.sh

NOTE: Updated installation notes are always available here.

2. Compile raylib library

Before compiling your game, raylib library must be recompiled for HTML5, generating libraylib.a. Make sure all paths to emscripten and tools are correctly configured, emcc should be accessible from command-line.

2.1 Command-line compilation

To compile raylib library directly from the command line, those are the commands to run:

emcc -c rcore.c -Os -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2
emcc -c rshapes.c -Os -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2
emcc -c rtextures.c -Os -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2
emcc -c rtext.c -Os -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2
emcc -c rmodels.c -Os -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2
emcc -c utils.c -Os -Wall -DPLATFORM_WEB
emcc -c raudio.c -Os -Wall -DPLATFORM_WEB

emar rcs libraylib.a rcore.o rshapes.o rtextures.o rtext.o rmodels.o utils.o raudio.o

The -Os flag is used to tell the compiler to optimize code for size, the -Wall flag enables all compiler warning messages. Some additional compilation flags can be used (actually provided Makefile defines some more) but they are not required.

The compilation will generate some warnings but it should compile successfully.

2.2 Using Makefile

For Windows users :

Before compiling raylib, make sure all paths to emscripten installation (EMSDK_PATH) and emscripten required tools (Clang, Python, Node) are correctly configured on raylib/src/Makefile, you must verify these lines.

From command-line, the following line must be called:

mingw32-make PLATFORM=PLATFORM_WEB -B

NOTE: mingw32-make.exe is provided by MinGW toolchain, other compiler toolchains could provide similar implementations, usually called just make.exe. In any case, make must be accessible from command-line to execute it.

If you are using the provided raylib installer with Notepad++, it comes with a Notepad++ script ready to compile raylib library using makefile, the script configures required paths and calls required Makefile. To do this, start up Notepad++ for raylib, open the raylib.h file, press F6, choose raylib_makefile, verify that in the script the web platform is set (SET PLATFORM=PLATFORM_WEB) and click OK to run the script.

Note that current raylib/src/Makefile just adds/replace the compiled modules to any existing libraylib.a (instead of recreating it) so, if you had a previously compiled libraylib.a for desktop and you recompile raylib src for web, old modules are still inside libraylib.a. Solution: just delete libraylib.a and recompile it for PLATFORM_WEB or the new platform.

For Linux/macOS users :

Before compiling raylib, make sure all paths to emscripten installation (EMSDK_PATH) and emscripten required tools (Clang, Python, Node) are correctly configured on raylib/src/Makefile, you must verify these lines.

You have to modify 3 variables :(EMSDK_PATH) (PYTHON_PATH) and (PATH). Just verify inside the emsdk directory if you have correct paths for (EMSCRIPTEN_PATH), (CLANG_PATH) and (NODE_PATH). (EMSDK_PATH) corresponds to path where you downloaded emscripten.

You must set the (PATH) to :

$(shell printenv PATH):$(EMSDK_PATH):$(EMSCRIPTEN_PATH):$(CLANG_PATH):$(NODE_PATH):$(PYTHON_PATH)

As example, your MakeFile on Linux should look similar to:

    EMSDK_PATH         ?= /path/to/emsdk
    EMSCRIPTEN_PATH    ?= $(EMSDK_PATH)/upstream/emscripten
    CLANG_PATH          = $(EMSDK_PATH)/upstream/bin
    PYTHON_PATH         = /path/to/python
    NODE_PATH           = $(EMSDK_PATH)/node/12.9.1_64bit/bin
    PATH = $(shell printenv PATH):$(EMSDK_PATH):$(EMSCRIPTEN_PATH):$(CLANG_PATH):$(NODE_PATH):$(PYTHON_PATH)

After the path configuration, just execute the following command:

make PLATFORM=PLATFORM_WEB -B

If you get "emcc: command not found" or a similar error when running make but the paths are correct, add -e to the end of the make command.

Generated libraylib.a is placed in raylib\src\libraylib.a directory.

2.3 Using CMake

If you prefer to use CMake instead of the plain Makefile provided, you have a few options to choose from.

  • Generating the build system files

You can go with the emscripten suggested way: That is to use the following command that will add the compiler and toolchain options for you:

emcmake cmake -H . -B build 

(The "-H ." is deprecated since CMake 3.13 and if you are using higher version please replace the argument with "-S .")

Emscripten cmake will prefer the ninja build system generator if you have that installed (and you should).

IDE-friendly option: As the first option is only available from a command line where you have run "emsdk activate latest" when using an IDE (like Visual Studio, CLion, etc.) you should set the path to the emscripten toolchain file:

cmake -H . -B build -G Ninja -DPLATFORM=Web "-DCMAKE_TOOLCHAIN_FILE=<fullpath_to_emsdk>/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"

(The "-H ." is deprecated since CMake 3.13 and if you are using higher version please replace the argument with "-S .") (The ninja generator is optional and you can use your system default by removing "-G Ninja".)

One note here - if you're using vcpkg for package management and have installed raylib:wasm32-emscripten you should execute this instead:

cmake -H . -B build -G Ninja "-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=<fullpath_to_emsdk>/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" "-DCMAKE_TOOLCHAIN_FILE=<path_to_vcpkg>/scripts/buildsystems/vcpkg.cmake" "-DVCPKG_TARGET_TRIPLET=wasm32-emscripten"
  • Building with CMake

To build the project you would need to execute:

cmake --build build

...but keep in mind that you also have to add some additional setting in your CMakeLists.txt for emscripten. For the linker to execute successfully it will need the GLFW symbols which cannot be built by you. Luckily emscripten provides those symbols when you add the "-s USE_GLFW=3" to your linker. To do so you can add these lines somewhere in the root of your CMakeLists.txt

if (EMSCRIPTEN)
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s ASYNCIFY -s GL_ENABLE_GET_PROC_ADDRESS=1")
    set(CMAKE_EXECUTABLE_SUFFIX ".html") # This line is used to set your executable to build with the emscripten html template so that you can directly open it.
endif ()

3. Build examples for the web

At this point, if you (optionally) want to compile the provided examples for the web, there is another Makefile to configure. Make sure all paths to emscripten installation (EMSDK_PATH) and emscripten required tools (Clang, Python, Node) are correctly configured on raylib/examples/Makefile, you must verify these lines.

For Windows users :

Start up Notepad++ for raylib, open the raylib/examples/Makefile file, press F6, choose raylib_makefile, verify that in the script the web platform is set (SET PLATFORM=PLATFORM_WEB) and click OK to run the script. It will compile all the examples for the web.

Opening generated .html files directly from disk may fail due to web browser security configuration. You should follow steps on Test raylib game on web.

4. Setup raylib game for web

To setup your own game to compile for web there are two possible options:

4.1 Avoid raylib while(!WindowShouldClose()) loop

Main reason to avoid the standard game while() loop is related to the way browsers work; the browser needs to control the executed process and just allow a single Update-Draw execution in a time-frame, so execution could be controlled and locked when required (i.e. when the tab is not active or browser is minimized). More details here

To avoid the loop, code must be slightly adapted. Basically it implies moving all your Update and Draw code to an external function, possibly called UpdateDrawFrame(), and consequently manage all required variables from a global context.

For a simple example on code refactoring for web, check core_basic_window_web.c example. For a more complex example, just check raylib-game-template, game template includes an already configured Makefile ready to compile it for web.

Avoiding while() loop will give better control of the program to the browser and it will run at full speed in the web.

4.2 Use standard raylib while(!WindowShouldClose()) loop

There could be some situations where the game while() loop could not be avoided and users need to deal with it. For those situations, emscripten implemented ASYNCIFY. ASYNCIFY basically detect synchronous code and allows it to run asynchronous. Enabling ASYNCIFY just requires an additional compilation flag passed to emcc when compiling game code.

raylib examples Makefile has been adapted to use ASYNCIFY by default, they work great but note that there is a small performance penalization.

5. Compile raylib game for web

5.1 Command-line compilation

To compile raylib game directly from the command line, those are the commands to run:

NOTE: in /raylib/src/ there is a file called minshell.html which is the recommended shell file for Raylib applications. So you may pass --shell-file $HOME/raylib/src/minshell.html to emcc if you had Raylib in your home directory.

  • Without ASYNCIFY
emcc -o game.html game.c -Os -Wall ./path-to/libraylib.a -I. -Ipath-to-raylib-h -L. -Lpath-to-libraylib-a -s USE_GLFW=3 --shell-file path-to/shell.html -DPLATFORM_WEB
  • With ASYNCIFY
emcc -o game.html game.c -Os -Wall ./path-to/libraylib.a -I. -Ipath-to-raylib-h -L. -Lpath-to-libraylib-a -s USE_GLFW=3 -s ASYNCIFY --shell-file path-to/shell.html -DPLATFORM_WEB

The compilation line is quite standard, similar to any other compiler and platform. Here a small explanation of the different parameters:

  -o game.html    // Output file, the .html extension determines the files that need to be generated: `.wasm`, `.js` (glue code) and `.html` (optional: `.data`). All files are already configured to just work.
  game.c          // The input files for compilation, in this case just one but it could be multiple code files: `game.c screen_logo.c screen_title.c screen_gameplay.c`
  -Os -Wall       // Some config parameters for the compiler, optimize code for small size and show all warnings generated
  ./path-to/libraylib.a   // This is the libraylib.a generated, it's recommended to provide it directly, with the path to it: i.e. `./raylib/src/libraylib.a`
  -Ipath          // Include path to look for additional #include .h files (if required)
  -Lpath          // Library path to look for additional library .a files (if required)
  -s USE_GLFW=3   // We tell the linker that the game/library uses GLFW3 library internally, it must be linked automatically (emscripten provides the implementation)
  -s ASYNCIFY     // Add this flag ONLY in case we are using ASYNCIFY code
  --shell-file path-to/shell.html  // All webs need a "shell" structure to load and run the game, by default emscripten has a `shell.html` but we can provide our own

There are some additional emscripten flags that can be useful if the game requires them. For example, in case of resources loading (images, textures, audio, fonts, models..), they need to be compiled with code (.data file generated). Web games use an internal Virtual-File-System to store data. Also note that the maximum memory size required by the application (considering everything the game will load) SHOULD be provided.

Here are some of those additional flags:

  --preload-file resources      // Specify a resources directory for data compilation (it will generate a .data file)
  -s TOTAL_MEMORY=67108864      // Specify a heap memory size in bytes (default = 16MB) (67108864 = 64MB)
  -s ALLOW_MEMORY_GROWTH=1      // Allow automatic heap memory resizing -> NOT RECOMMENDED!
  -s FORCE_FILESYSTEM=1         // Force filesystem creation to load/save files data (for example if you need to support save-game or drag&drop files)
  -s ASSERTIONS=1               // Enable runtime checks for common memory allocation errors (-O1 and above turn it off)
  --profiling                   // Include information for code profiling

5.2 Using Makefile

To configure all required compilation flags for web, an already setup Makefile is provided, you can check raysan5/raylib-game-template for reference.

Before compiling the game, review the Makefile to make sure emscripten sdk path (EMSDK_PATH) and other paths are correctly setup. Also review the following Makefile variables: PROJECT_NAME, RAYLIB_PATH, PROJECT_SOURCE_FILES.

Once Makefile has been reviewed, to compile raylib source code, just execute the following make call from command line:

make PLATFORM=PLATFORM_WEB -B

If you get "emcc: command not found" or a similar error when running make but the paths are correct, add -e to the end of the make command.

Note that required resources should be embedded into a .data file using the compiler parameter --preload-file filename.ext or --preload-file folder (already configured in the Makefile to use resources directory).

Compilation will generate several output files:

  project_name.html  > HTML5 game shell to execute the game
  project_name.js    > Glue code to load WebAssembly program
  project_name.wasm  > WebAssembly program
  project_name.data  > Required resources packaged

5.3 Using CMake

Use the following CMake options:

-DCMAKE_TOOLCHAIN_FILE=<YOUR PATH HERE>/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
-DPLATFORM=Web

Included this snippet at the top of CMakeLists.txt, with no changes whatsoever:

if (EMSCRIPTEN)
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s ASYNCIFY -s GL_ENABLE_GET_PROC_ADDRESS=1")
  set(CMAKE_EXECUTABLE_SUFFIX ".html") # This line is used to set your executable to build with the emscripten html template so taht you can directly open it.
endif ()

6. Test raylib game on web

To test the newly created .html file (and its .wasm, .js, .data and maybe .mem), you can create a localhost (you can do it using python) and open the web in the browser.

To create a localhost using python, from command-line go to the same folder where your .html file is located or keep in mind that the directory from which you set the localhost is the base directory for browser access. Execute the following command-line (it requires Python 3, provided by emscripten):

python -m http.server 8080

It will allow you to access the webpage from a browser directly from that directory with the web address:

localhost:8080/project_name.html

Alternatively, if you have the emscripten binaries in your path, you can run the following command

emrun project_name.html

7. Upload raylib web game to itch.io

To upload a raylib web game to itch.io you need to rename project_name.html to index.html and compress into project_name.zip the files generated on compilation:

  index.html
  project_name.wasm
  project_name.js
  project_name.data
  project_name.mem

Upload the project_name.zip to a new itch.io project and select the option for the file: This file will be played in the browser. You have some additional config options on the itch.io game section: Embed options

When saving the page and entering your itch.io game page, it should be playable!

FAQ | Common Issues

Q: When compiling my game I got this error with libraylib.a:

wasm-ld: error: unknown file type: rglfw.o

A: That's because it includes symbols from a previous compilation. Just delete all generated .o and libraylib.a and compile it again for web.

Q: Mouse Input not being detected?

A: Be sure that the Input Detection is in front of the Frame Draw. For some reason input isn't detected after the frame has been drawn.

Q: Failing to load resource files? (WARNING: FILEIO: [...] Failed to open file)

A: Make sure that you refer to the resource directory in the same way in the code and in the --preload-file compiler argument. If you passed an absolute path to the compiler (e.g. C:\my_game\resources) you'll have to use absolute paths in the code (e.g. "C:/my_game/resources/player.png"), and if you passed a relative path to the compiler (e.g. ..\resources) you'll have to use relative paths in the code (e.g. "resources/player.png").

Q: Failing to play sounds? (Uncaught ReferenceError: ccall is not defined at device.scriptNode.onaudioprocess)

A: Add this argument in the compiler line -s EXPORTED_RUNTIME_METHODS=ccall.

Q: Why don't I see the changes I compiled being applied?

A: Your web browser may be caching the game; you need to clear the cache. In most browsers the shortcut Shift+F5 or Ctrl+F5 will fully reload the page.

Please, feel free to add here your FAQ/Issues to help others!!!

Clone this wiki locally