James Stanley

Making a game with 24a2

Sun 28 June 2020

Tagged: software

24a2 is an "ultra-minimalist" game engine. It was posted on Hacker News this morning and I found it so interesting that I decided to take a day off from breaking keyboard switches and make a tiny game instead.

Playing with a new game engine was a perfect Sunday project for me at the moment, because I'm currently reading & enjoying Masters of Doom about id Software, John Carmack, and John Romero. I would also definitely recommend John Carmack's appearance on the Joe Rogan Experience.

I thoroughly enjoyed using 24a2, it brought back all the fun that got me into programming in the first place.

Paradoxically, the reason it is so easy to make a game with 24a2 is that it so severely constrains you. The only keyboard input available is arrow key presses. The only display output is a 24x24 grid of dots, and there are only 9 colours. There's no audio at all.

The game I made is called "24invaders" and it is a Space Invaders clone.

The 24a2 tutorial takes you through setting up a basic game where the player can move around the screen and score a point every time he steps on a green dot. But if you've ever written a game or any kind of physics simulation before, then you can safely skip the tutorial and refer directly to the reference manual. It is extremely short.

Typically, when making a game, you will want to decouple your logic from your rendering, so that if the graphics start to lag they don't slow down the logic. In 24a2 you don't waste your time even thinking about such problems. You only have 576 pixels to draw anyway so it probably won't be a problem.

The player

I started by making the game draw the player on the screen and allow the user to move it left and right with the arrow keys. 24a2 only tells you about key press events. This means you don't get to find out whether the key was just tapped or whether it is being held down. That means that if you want to move the player a long way, you have to tap the arrow key a lot of times. I considered hacking 24a2 to tell me the key state, but realised it is silly to use an engine that deliberately constrains you and then remove the constraints.

I wanted the game to stand out a bit compared to the 24a2 examples, so rather than use a single dot for the player, I made it look a bit like a spaceship with a gun on the front.

OK, it doesn't look very much like a spaceship with a gun, but what can you do? The tiny display means you don't have much scope to draw fancy graphics, and that is a good thing because "programmer art" is notoriously bad, but this way nobody minds.

Rather than make my drawing code manually plot each pixel, I defined the picture of the player as in ASCII, and then wrote a function to draw the "sprite".

let playerSprite = makeSprite([
    " # ",
    "###",
]);

(The "makeSprite()" call is because I was planning to use eval to compile the sprite into a function that would draw it, but that's a pointless premature optimisation, especially at 24x24, so I never bothered to actually implement that. So "makeSprite()" just returns its input and "drawSprite()" does everything.)

The player's bullet

Something that always annoyed me about Space Invaders is that once you've fired the gun, you can't fire again until your bullet either hits something or leaves the screen. This means that if you miss, you have a long wait before you can take another shot. I've always somewhat suspected that this was really only done so that the game does not need to keep track of more than 1 player bullet.

But it's a part of the game, so I did it that way too. It means you should try harder to time your shots.

The bullet is just implemented as an (x,y) coordinate. When there is no bullet in play, the y coordinate is set to -1.

Each frame, if there is a bullet in play, it's y coordinate is decreased by 1. 24a2 games run at 24 fps by default (this can be changed, but I left it alone), so it would take 1 second for the bullet to cross the entire screen.

Shields

The shields in Space Invaders stop the aliens' bullets from reaching you, but they also stop your bullets from reaching the aliens. The shields are depleted every time they get hit until eventually bullets can pass straight through.

I implemented the shields by defining a fixed y coordinate and keeping track of the set of x coordinates that still have shields on. When a bullet hits a shield, that x coordinate is removed from the set and the bullet is destroyed, so that bullet didn't go through but the next one will.

Aliens

The aliens move in a big pack, sliding left or right until they reach an edge of the screen, then dropping down a row and sliding in the opposite direction. The aliens also speed up with each level (although, in my implementation, top speed is reached at level 8 and every subsequent level is the same).

To make the game look a bit more interesting, I drew 3 different types of alien, and also gave each one a 2-frame animation. It's difficult to evoke the image of the Space Invaders aliens in a 3x3 drawing, but I think it kind of works.

Initially I just made the pack of aliens move through a fixed path, but I discovered that this does not faithfully recreate the Space Invaders logic, because if you have shot all of the aliens on one side then the other aliens need to move further before they reach the edge of the screen. This is important because it is a key part of the strategy: you should shoot the aliens at the sides first to buy yourself time. So instead of a fixed path, I just tested the position of each individual alien and reversed the direction of the pack if any one alien reached the edge.

The player loses if any of the aliens get far enough down the screen to capture him. I think technically you should only lose when one of the aliens hits you, but I just tested the y coordinates of the aliens to see if any of them are far enough down.

The aliens' bullets

The aliens occasionally shoot back at the player. I'm not sure if there should be some logic to when the aliens start shooting, and whether or not that logic should be computed for each alien individually, or for the aliens as a group. I just made it randomly fire a bullet from a randomly-chosen alien on each frame with 20% probability. This means that the rate that the aliens fire at you does not decrease as the number of aliens decreases. I did try making each alien fire as an individual, but it didn't play very well because you etiher get way too many bullets when all 12 aliens are alive, or not enough bullets when you're down to the last handful.

I also didn't make the aliens pay any attention to whether or not the shot was going to go anywhere near the player, but it seems fine.

If the player gets hit by an alien bullet, he dies. In the original game you have several lives but I didn't bother with that. One hit and you're dead.

Particle effects

We have a working game now, but I wanted to provide a bit more action in the graphics so I added some basic particle effects. This is used in the game to make the aliens and the shields explode when they get hit by a bullet, and it's also used for the confetti effect on the level transition and game over screens.

(Sorry, there's no screenshot because a static image can't really do it justice, but trying to video record my screen and turn it into a GIF proved too time-consuming).

Particle effects are very easy to make and look pretty cool, even at 24x24. Each particle just has a (x,y) coordinate, (vx, vy) velocity, and a colour. At each frame, the particle's (x,y) coordinate is updated by adding on its velocity, and its velocity is updating by adding gravity to vy:

for (let i = 0; i < particles.length; i++) {
        particles[i].x += particles[i].vx;
        particles[i].y += particles[i].vy;
        particles[i].vy += 0.1;
}

The coordinates and velocities are stored as floating point values, with the coordinates rounded to integer when they need to be drawn.

If a particle ever goes off the screen it is deleted immediately, but you could consider keeping track of particles that go "above" the screen but are still within its width, because you know gravity will eventually pull it back down.

Both the alien/shield explosions and the confetti are created the same way: given an (x,y) coordinate at which to start the effect: just create 3 new particles, starting at (x,y), with random velocity. Alien/shield explosions take on the colour of the thing that is exploding, and confetti takes a randomly-chosen colour.

Game over screen

To support different types of screen, I created a variable called "gamestate" that references the function to run for each frame. The main update function then just calls gamestate, followed by drawing the particles.

Once the game is over, we transition into the "game over" state and from there we can never transition out. If you want to play again, you need to refresh the page.

I created the writing in the GIMP with the pencil tool on a 24x24 canvas zoomed in pretty far, and then manually wrote it out as a sprite:

let gameoverSprite = makeSprite([
    " ###    #   #   # ####",
    "#      # #  ## ## #   ",
    "#     #   # # # # #   ",
    "#     #   # #   # ### ",
    "#  ## ##### #   # #   ",
    "#   # #   # #   # #   ",
    " ###  #   # #   # ####",
    "                      ",
    " ###  #   # #### #### ",
    "#   # #   # #    #   #",
    "#   # #   # #    #   #",
    "#   # #   # ###  #### ",
    "#   # #   # #    # #  ",
    "#   #  # #  #    #  # ",
    " ###    #   #### #   #",
]);

It was just good luck that the 2 words are the same width, and that width fits neatly in a 24x24 canvas. The "E" is 1 character narrower than the other letters, but both words contain exactly 1 "E" so it works out.

There are any number of ways to make the scrolling rainbow effect, and mine is bad. (But, in short, I made a function that scans what has been drawn the "entire" screen and replaces every red pixel with a colour selected from a function that creates the rainbow effect, and then to get the rainbow effects you just plot red everywhere you want a rainbow, and then call that function).

Just to make it a bit more interesting, there is some random confetti added using the particle effects.

Level transition screens

The level transition screen tells you the number of the level you're about to move to. I wrote the "level" text the same way as the "game over" text, but slightly smaller because the required letters allow it. (For example, it's hard to make a capital "G" that is only 3px wide).

I also added a couple of pixels of scrolling rainbow effect at the sides, and made the sprite position shift around horizontally and scroll vertically, kind of like how the aliens move.

The level transition screen has a fixed timer of 60 frames, after which it reverts back to the main game state so that the new level can begin playing.

Fonts

I wanted big numbers on the level transition screen, and big numbers are hard to draw by plotting pixels by hand, so I copied the numbers from this 11x7 font. I manually wrote them out as sprites, and then when I need to draw the level number, I just draw the sprites for the appropriate digits. It technically supports up to level 99 (i.e., up to 2 digits), but I don't know if anyone will ever get as far as level 10.

You could use the same scheme to do arbitrary letters. This is the same way bitmap fonts work anywhere.

And that's pretty much all there is to know about how I made 24invaders.

If you like my blog, please consider subscribing to the RSS feed or the mailing list:

James Stanley - james@incoherency.co.uk | ricochet:it2j3z6t6ksumpzd | jesblogfnk2boep4.onion | [rss]