2025-09-11
TL;DR: I made a little game with macroquad
, a Rust game
library. I call the game Pinwheel, and it’s a clone of a game my wife
found on the at-table point-of-sale device at an Applebee’s. You can
play my version in your browser here or view the code
here. There’s only one
level, and it doesn’t even tell you when you’ve won or lost (it just
stops), but it technically works and was a good motivator for
learning.
The game is pretty simple. It’s a game of skill where the goal is to tap the screen at the right time to fire colored pins onto a spinning circle. The circle is divided into various colored sectors, and the colors of the pins must match the colors of the sectors where they land, or the game is over. To increase difficulty as gameplay progresses within a level, the game also ends if any two pins collide, regardless of color. Sometimes the spinner will start with black pins, which act as initial obstacles. If the player can get rid of all the pins in a level, they proceed to the next one. Otherwise, the level restarts.
The essential parts of the game are:
The level design can become somewhat interesting by setting the number of pins near the maximum number that can fit in the given sectors, increasing the spinner speed, decreasing the pin flight speed, and probably other stuff I haven’t thought of. It seems to me like the upper bound for replayability is somewhat low, but that isn’t a problem in the original scenario (i.e., a customer waiting 10–30 minutes for their food to arrive).
macroquad
One of my goals with this project is to build up a basic game
development workflow. I started out thinking I’d use Godot or Bevy, but
I was unhappy with the idea of relying on such relatively heavy systems
and feeling too far from the underlying game state and graphics.
macroquad
is a much better fit by this metric because it
calls itself a game library, not an engine. This seems
to mean that it doesn’t provide any “higher-level” conveniences for game
development and is mostly a cross-platform way to draw pixels to the
screen. There are definitely plenty of graphics-related conveniences,
like being able to draw text, but macroquad
doesn’t
prescribe or even provide a specific game engine framework.
This is harder if your goal is, first of all, to ship a game that
isn’t too complex. Pinwheel is well within the capabilities of basically
any game engine, and if that was my goal I’d be taking on unnecessary
work by using macroquad
. However, in my case I was
deliberately looking for the least amount of help possible as a way to
learn more and have some fun building out my own somewhat wonky game
engine-ish system.
It’s annoyed me for a while now that drawing to a computer screen is
so difficult on current systems, particularly in a cross-platform way.
macroquad
is built on miniquad
,
which appears to have been built to solve this problem. I’ll definitely
use it again on future projects that need to just open a window and
paint on it.
By the way, another big advantage to macroquad
is that
it compiles trivially to WebAssembly, which is how I was able to publish
Pinwheel to the web so easily. The other game engines I mentioned can do
that too, but I was pleased to see it available in the much smaller and
simpler library as well.
When the time arrived to start building rendering logic for Pinwheel,
right away I found that I didn’t know how to draw the spinner.
macroquad
makes it easy to draw circles, but there isn’t a
built-in way to draw a circular sector, so I had to build it.
This turned out to be easy once I understood the basics of how to
interact with the graphics API. (The function is here
if you want to see the code.)
First, you have to get an InternalGlContext
, which is
just a function call. Since there’s some
churn about the thread safety of this context struct, I call it once
at the top level of the program and pass it down as a mutable reference
where I need it. Then we get into the real algorithm. It turns out that
you can render a circular sector by splitting it into triangles, kind of
like a hand fan.
All the triangles share the center of the circle as one point, and then
they fan out towards the circumference, approximating it with line
segments. If you divide the sector into enough segments, it looks
convincing on the screen.
To actually do this with macroquad
, you have to build up
two vectors. One is for vertices, the actual points that will form the
corners of the triangles, and the other is for indices. You only list
each vertex once, and the indices tell the graphics system how to look
up the vertices to form triangles. The index vector is just a
Vec<u16>
, but I guess its length always has to be a
multiple of three. Anyway, in this case you build up these vectors by
iterating from some starting and ending angle and do trigonometry to
figure out where the points should go. Then you push that all into the
graphics machinery and out comes a lovely filled sector.
I was going to write a lot more about my duct-taped game engine that I made up as I went along, but I ran out of time this week. When this project comes around again next month I’ll try to get some detail in here on that. Hopefully I’ll be able to improve it by then, too, along with adding more than one level, maybe sound, scoring, collision improvements, etc.