Building for the Web
Emscripten is a C++ to WebAssembly compiler. Because there are ports for both the SDL and OpenGL libraries our games use, this means you can compile your C++ games to run in a web browser! This is what this site uses for the in-browser playable versions of the game.
In order to get your games working with Emscripten, there are some minor Emscripten-specific code changes required, which this guide outlines.
I’ve only extensively tested this process on a Mac, but it should also work fine on Windows, too. Let me know if there are issues!
Installing the Emscripten SDK
First, you need to install the Emscripten SDK (emsdk) following these instructions. On Mac, you should issue the commands from terminal whereas on Windows, use a normal Windows Command prompt (and not Git Bash).
Make sure you keep track of where you installed emsdk, as you will need this path later. If you’re on Mac/Linux, I’d suggest just starting from your ~
directory (your user’s home directory), so that everything can be relative to that path.
C++ Code Changes
You will need to make some minor changes to the C++ code of every game you want to build for Emscripten. The 3D games will require a couple more changes than the 2D ones.
Fixing the Game Loop
The main change for Emscripten is that you need to change the way the main game loop works. Most browsers do not like infinite loops in JavaScript/WebAssembly. Because of this, our normal infinite loop will trigger the infinite loop detection which will almost immediately kill the game.
Instead of an infinite loop, Emscripten requires you to register a callback function to execute once every ~60 seconds. So, you need to add a function to Game
which runs a single iteration of the game loop that we can then call in the callback.
First, after the includes of Game.h add the following:
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#else
#define emscripten_cancel_main_loop()
#endif
This says to #include <emscripten.h>
if we are building for Emscripten, otherwise it defines a macro called emscripten_cancel_main_loop
that does nothing.
Next, in the public section of Game
, add the following (for simplicity, just put it in Game.h
):
void EmRunIteration()
{
if (!mIsRunning)
{
// We aren't running anymore, so cancel the main loop callback
emscripten_cancel_main_loop();
}
ProcessInput();
UpdateGame();
GenerateOutput();
}
This declares a member function called EmRunIteration
that runs one iteration of the normal game loop. Note the extra check for if we aren’t running anymore, we tell Emscripten to stop calling our main loop function.
Be sure to double-check that your RunLoop
game loop function isn’t doing anything else than calling ProcessInput
, UpdateGame
and/or GenerateOutput
. If it is, you’ll also need to add that to your EmRunIteration
.
Finally, you also need to change your Main.cpp
function to register the callback, which will look something like this:
#include "Game.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#ifdef __EMSCRIPTEN__
void EmMainLoop(void *arg)
{
Game* game = reinterpret_cast<Game*>(arg);
game->EmRunIteration();
}
#endif
int main(int argc, char** argv)
{
Game game;
bool success = game.Initialize();
if (success)
{
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(EmMainLoop, &game, 0, true);
#else
game.RunLoop();
#endif
}
game.Shutdown();
return 0;
}
This code says that if we’re building for Emscripten, we want to create a new function called EmMainLoop
. This function takes in a pointer argument (which it casts to a Game*
) and then calls the EmRunIteration
function on it.
Finally, the code in main
says that if we’re building for Emscripten, instead of calling the normal RunLoop
, we want to register our callback function. The syntax is a little funky because Emscripten is a C API, not C++.
Disabling ESC to Quit
For the Emscripten version of your games, you may also want to disable the functionality where the ESC button exits the game. This is particularly the case for labs 9 through 12, which capture the mouse in the browser window. To get mouse control back, the user has to press ESC, which would mean the game would end. You can compile out this code on Emscripten using #ifndef __EMSCRIPTEN__
. It will look something like this:
#ifndef __EMSCRIPTEN__
if (state[SDL_SCANCODE_ESCAPE])
{
mIsRunning = false;
}
#endif
Adding an Emscripten HTML Template
Although technically optional, the CMake files tell Emscripten to use a template HTML, which is the Libraries/Emscripten/shell_minimal.html
file to your repo.
Adding CMake Support
Since we use CMake as our build system, we also have support for Emscripten in the CMakeLists.txt
files included in your repo.
Configuring CMake
The instructions on how to run CMake are different depending on whether you’re on Mac or Windows.
Mac
First cd
to the directory which contains your ITP380 repo.
Run CMake with:
emcmake cmake -DCMAKE_BUILD_TYPE=Release -B embuild
Windows
Be careful when you edit your PATH
environment variable that you do not accidentally overwrite the existing entries. You just want to add to the PATH
.
First, you should edit your PATH
environment variable (see here if you don’t know how) to include the location of the CMake and Ninja. If you’re using Visual Studio 2022, the locations you want to add to PATH
are:
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja
(If you have a different install of Visual Studio, you’ll have to find the similar directories).
You will also need to modify the CMakeLists.txt file in the root director of your ITP 380 repo. Find the line that says this:
set(CMAKE_CXX_FLAGS "-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS='[\"png\"]' -sUSE_SDL_MIXER=2 -sUSE_SDL_TTF=2")
And replace it with:
set(CMAKE_CXX_FLAGS "-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS=\"[\"png\"]\" -sUSE_SDL_MIXER=2 -sUSE_SDL_TTF=2")
Then, find the location in Windows Explorer where emsdk
is and double click on emcmdprompt.bat
to open up the “Emscripten Command Prompt.”
After you open this, cd
to the directory that contains your ITP380 repo. Then run:
emcmake cmake -DCMAKE_BUILD_TYPE=Release -B embuild
Building/Testing for Emscripten
Once CMake is configured, you need to change directories to embuild
:
cd embuild
M1 Mac
There is currently a bug with M1 where you need to tell Emscripten to use your built-in python3
instead of the one included in the SDK.
First, run:
which python3
And note which directory you get back. From here, you can build the specific lab you want to build with the following command:
EMSDK_PYTHON=/usr/bin/python3 cmake --build . --target LabXX
Where LabXX
is the lab folder you want to build. So for example, Lab01
would build your Pong game. Replace /usr/bin/python3
with whatever path you got back from which python3
, if it is different.
Intel Mac or Windows
From here, you can build the specific lab you want to build with the following command:
cmake --build . --target LabXX
Where LabXX
is the lab folder you want to build. So for example, Lab01
would build your Pong game.
Testing Emscripten Locally
Once the game is built, you can test it locally. However, because most browsers don’t allow loading of WebAssembly files from file://
, you can’t directly load the HTML file directly into your browser. Instead, you need to use the emrun
script which will spin up a web server that you can locally connect to for testing. To launch it for a specific game, use:
emrun --port 8080 LabXX
Where again, LabXX
is the game you want to test. This should launch your browser, but if it doesn’t, you can go to http://localhost:8080 directly. From the main page’s file listing, click on the LabXX.html
file. If it all worked properly, you should be able to play your game in browser. Note that you often will have to click once onto the game for sound effects and/or mouse capture to work.
Some students have said that for them, the Portal game runs slowly in Safari (which emrun may use by default), but runs at an appropriate framerate in Chrome.
Uploading to the Web
If you want to upload your game to your portfolio website, you just need to go into your embuild/LabXX
directory for the game in question. In here, you’ll see the following four files:
LabXX.html
LabXX.js
LabXX.wasm
LabXX.data
You should copy all four of these files and upload it to your site. Note that because Lab01 (Pong) doesn’t have any asset files, it will not have the .data
file.
And that’s it! Let me know if you have any issues.