Introduction
macroquad
is a library for developing games. It has a remarkably simple API and a decent cross-platform support. There is an amazing tutorial on how to write the game with it. In addition to this, there is an excellent section on shipping your game in WebAssembly
. Is it possible to make a macroquad
application that is compiled into WebAssembly
, that works both on PC and mobile?
If you browse the examples, hosted on the macroquad website -- you will find that they work on WebAssembly
supporting mobile devices without any problems at a quite comfortable FPS. Some examples that do not require keyboard input can even be interracted with. However, these are just application examples. This blogpost is about Boring Arcanoid
-- a simple arcanoid clone written with macroquad
that can be played both on phones and PCs.
The application is a single standalone WebAssembly
module, that detects the platform it is run on at runtime and adjusts the conrols and ui accordingly. Essentially, that means that a game written this way can be effortlessly served through a super simple static HTTP server. Thus, making it a perfect for uploading such games to websites like itch.io
!
Miniquad plugin API
At the current moment a lot of browser API is not available in WebAssembly
. This is circumvented by providing what is needed with "import objects"1. In fact macroquad
and miniquad
do just that to import the WebGL API functions.
However, if we were to just use that import object to sneak in the OpenGL functions -- miniquad
and macroquad
applications would be quite hard to extend. Crates like quad_snd
would not be possible. For this reason miniquad
has a plugin API, which is described in detail on macroquad
website here. The gist of it is as follows
1 2 3 4 5 6 ;
The exported API should be put into the import object with reg_function
and all the plugin state (on the JS side) should initialised with init_func
. With this API, I was able to add some functions that would allow me to implement the cross-platform arcanoid.
The extra API
To fascilitate the mobile device support, the following functions would be needed: getting the device orientations and checking if the current device is a mobile device in the first place. The orientation checking2 is needed to prompt the user to rotate their mobile device (explicitly asking the device to enter a certain orientation is not baseline API at the moment3). Detecting if the device is a mobile device or not is not an existing API either. However, it is possible to figure this out by checking the user agent string4
1 2 3 4 5
6
7 8 9 10 11 12 13 14 15 16 17 18 19 20
With this in-place, the functions were imported into the Rust code for later calling as follows
1 2 3 4 5 6
7
8 9 10
11
12 13 14
To make the application also work on native platforms, I simply added the no-op implementations
1
2
3
Why the re-orientation
I have designed the arcanoid application in such a way, that it does not fit well into a portait style orientation. While designing the app -- there were two routes I could take when implementing mobile support:
- Rotate the rendered graphics automatically
- Just expect the device to be in landscape mode
When considering what route to take -- I decided that expecting the landscape mode is the best route for the following reasons:
- Touch position handling looks clean
- There is no extra code in the rendering system
- Redesigning for portrait mode equals extra problems
- It is common to play games with your phone in landscape mode
- Other route would undermine the idea of "it just works everywhere"
Handling user input
Now, having the API for platform specific stuff -- an abstraction is required. In case of a macroquad
game -- what needs to be done is to abstract away the input code. Platform specific wise it is as follows:
- On desktop -- we use
A
andD
to move the arcanoid paddle - On mobile -- we use on-screen buttons to move the paddle
For that I have written the Ui
struct in the ui.rs
module. It is responisble for reading the user input and (if needed) draw the controls on the screen. The input requests are encoded as a bunch of flags like that:
1
2 3 4 5
And when it comes to reading the input, the following method does the trick
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
And then inside the Ui::draw()
method I just did this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Requesting reorientation
As it has been been made clear before, it is not possible to make any mobile device change its orientation3. That is why we need to ask the user to do that instead. It is more or less simple to implement. We just check the device orientation every frame and if it is misoriented -- we enter a "please rotate" state, where game logic does not run.
1 if get_orientation != 0.0 && state != PleaseRotate 2 3 4
5
6 match state 7 8 9 10 11 12
Conclusion
WebAssembly
is fun and writing games is fun too. I am really happy with how relatively clean the code turned out. There are certainly several extra things to explore!
First of all, I should try uploading the game in itch.io
. It seems the website has some hacks of its own to handle the device orientation for mobile, so this might render the orientation code obsolete (which means the game would need less code altogether).
Secondly, the game code still leaves a lot to be desired: it is odd, full of quick-fixes and generally very brittle. I think I will occasionally revisit this arcanoid to refactor (probably with some lightweight ECS) and update it, adding some features and whatnot.
Thirdly, the current version place the same sounds over and over without any pitch shifting. Adding some subtle pitch shifts would certainly make it feel less monotone.
Finally, like macroquad
the game code also features some inefficiencies, which I would certainly would love to remove to re-use some parts of the code in the future. Here's a list for some of them:
- Frequent allocation inside
macroquad
- Repeated
on_mobile
calls (aka repeated FFI calls)