Pong with Cheerp
This tutorial will guide you through the steps to create a simple game of pong in C++, and compile it to a combination of WebAssembly (game logic) and JavaScript (game graphics) with Cheerp.
Hello World
Let’s create a new C++ file pong.cpp
as follows:
webMain
represents the main entry point of the compiled application. Since WebAssembly cannot manipulate the DOM directly, we are instructing Cheerp to compile domOutput
in JavaScript with the [[cheerp::genericjs]]
attribute tag. To compile this file to WebAssembly, this simple command will suffice:
This command will generate two files: a JavaScript loader (pong.js
) and a WebAssembly binary (pong.wasm
). We will need both files to run the application on a browser. When using Cheerp in mixed-output mode, the JavaScript output will also include all the JavaScript output code, which will be normally used to interact with the DOM or any external JavaScript library.
All we need to run this Hello World is a simple HTML page:
We only need to reference the JavaScript loader, and the WebAssembly binary will be automatically loaded.
You now need to start a local HTTP server in the directory containing pong.html, pong.js and pong.wasm. Node’s http-server -p 8081
command work well, but any server will do.
Visit your page, for example “http://127.0.0.1:8081” with any browser, and you should see something like this:
Great! We can now move on to build an Hello World that writes on the Canvas.
Hello World (Canvas)
What we will do in this tutorial is keeping all the Canvas manipulation in the JavaScript output, and the rest of the game in WebAssembly. We will be doing our rendering using the Canvas 2D Web API, and we will also use requestAnimationFrame
for best performance. The Canvas 2D API is not directly accessible from WebAssembly, so we will ask the compiler to generate JavaScript code on the relevant sections.
Let’s have a look a this example:
We have created a new Graphics
class and tagged it using the [[cheerp::genericjs]]
attribute. Cheerp will generate all the code for this class in standard JavaScript, so that DOM manipulation can be done easily in C++ with no performance or syntax overhead. The code in webMain is compiled to WebAssembly and will use the WebAssembly imports to call the code compiled to JavaScript.
Compile this new code like we did before and refresh the browser tab, you should now see something like this:
Great stuff.
Drawing on the Canvas
Let’s get started on our game. We will now focus on drawing the paddle.
For this, we will create a Platform
object, which will be the only controllable entity in the game. The object will be moved to the left and to the right using the keyboard and will be rendered as white box on a black background.
Let’s create a new class for the Platform. We will not use the [[cheerp::genericjs]] attribute on this class so that all it’s code will be compiled in WebAssembly.
The class has some basic properties and a render function which then delegates the actual rendering to the Graphics
class since on the WebAssembly side we don’t have access to the DOM. Let’s add the drawRect function to the Graphics class:
We now need an instance of the Platform object, let’s put it in the global scope for convenience, and also add a top level function mainLoop
to handle the main loop of the application. This function will clear the background and then render the platform:
Lastly, we need to add a handler for drawing an animation in our Graphics
class. The browser will call the handler in sync with the display refresh rate, which generally is at 60 fps.
We then need to call the handler one first time in Graphics::initializeCanvas
:
Let’s recompile, and the result should look like this:
Looking good.
Animation and Keyboard events
We now need to be able to move the platform around. We will add a keydown
event handler for this purpose. Since it’s a DOM interaction, this code will be tagged as genericjs
, but it will update the values of the Platform
object which is compiled to WebAssembly.
Let’s add two new methods to Platform
:
as well as a keyDown event handler to the Graphics class which lives in genericjs
code.
Notice that we are using fmin
and fmax
instead of the usual std::min
/std::max
. In general, standard library (STL) functions are compiled to Wasm. We can use STL functions from the JS code but there are restrictions. One of these dictates that Wasm functions that have pointers or references to basic types are not available in the JS-annotated part of the code. Indeed, std::min
/std::max
get references to basic types while std::fmin
/std::fmax
get their inputs by copy. Another solution would have been to wrap std::min
/std::max
in non-JS-annotated functions that take int
s by copy. This limitation will hopefully disappear at some point in the future.
Let’s also register an EventListener
in Graphics::initializeCanvas
.
You should now be able to move the paddle around like this:
Final steps
We’ll now focus on the ball. We will create a Ball
class, including a basic physical model of position and velocity. We will keep this class in WebAssembly, so no need for a [[cheerp::genericjs]]
tag.
The Ball
class has methods to update its position, check for collisions and for rendering.
To visualize our ball on screen we need to implement Graphics::drawCircle
:
And, as with Platform
, instantiate a Ball
object in the global scope.
We will now expand the mainLoop
method to call them in sequence. Ball::collide
also checks if the ball has gone past the bottom of the screen, which is our losing condition, and mainLoop
is going to report that by drawing some text.
And that should be it! The game should look like this: