Demo Highlight: Asteroids

JoeStrout - Jan 31 - - Dev Community

This week I'd like to point out another demo that comes with Mini Micro: a loving tribute to the classic Atari arcade game, Asteroids.

Animated GIF of the Asteroids demo in play

Try it now!

The web player version of Mini Micro accepts a "cmd" query parameter. So, you can click this link to hop into Mini Micro and try out the demo right now:

https://miniscript.org/MiniMicro/index.html?cmd=run%20%22%2Fsys%2Fdemo%2Fasteroids%22

When the game is over, it will exit to the MiniScript prompt. Simply type run (and press return/enter) if you want to play again.

How it Works: Graphics

First, let's make it clear what you're looking at when you play! It looks like a vector display, but it's not. Most of the action is actually on a SpriteDisplay. The sprites are created from one of the built-in images, /sys/pics/Asteroids-2X.png:

/sys/pics/Asteroids-2X.png

(This image is also available on OpenGameArt.org.) It's a little hard to see on a white background, but each of the shapes here is a white outline with a bluish glow to it. That allows us to tint the sprites whatever color we like at runtime.

So the asteroids, the ship, the UFO, your shots, and even the little particles that appear when things explode, are all just sprites. The score in the corner, and the "GAME OVER" message at the end of the game, are just text on the default TextDisplay (which is layered on top of the sprite display). Same for the little stick figures that indicate how many lives you have left; having imported the "chars" module, we can reference that special character as chars.figureStanding. Fire up Mini Micro and try it yourself:

import "chars"; print chars.figureStanding

How it Works: Inputs

The game uses two directional inputs and one fire input. Directional inputs are obtained using key.axis("Horizontal") (for turning) and key.axis("Vertical") (for thrust). Using these axis inputs means the player can play with the arrow keys, or WASD, or even a gamepad or joystick. So, that's pretty great. We also check key.pressed("left shift") and key.pressed("right shift") as an alternate input for thrust, for players who want to turn with one hand and control thrust with the other.

For the fire input, we use key.pressed again, checking for any of three inputs: "space", "joystick button 0", and "joystick button 1". While gamepad/joystick buttons can be all over the place, in most cases buttons 0 and 1 will be face buttons, often labeled A/B or X/Y or something similar. Chances are pretty high that whatever gamepad the player is playing, they're going to mash one of these buttons when they want to fire. Of course it that doesn't work for you, you can go into the code and change or add to the buttons checked (around line 120). Use /sys/demo/inputCheck to see which button is which on your device.

How it Works: Sound

After graphics and input, the last major component of most games is sound. The asteroids demo uses two approaches to sound. Most of the sounds are synthesized sounds, defined right in the code. This includes the "bip" and "boop" heartbeat sounds that alternate in the background, getting faster as things get more tense; the engine noise that plays when you thrust; the "pew" sound when you fire; and the properly annoying UFO sound that appears whenever that thing is on screen.

The only exception is the "hit" sound, played when the player ship, the UFO, or a rock is hit and explodes. This is a digitized sound loaded from /sys/sounds/hit.wav.

Code Overview

Now that you've got the "big picture" of how the game works, I encourage you to dig around in the code! It's not very scary.

At the top of the code are some setup steps. Lines 15-28 load that Asteroids-2X.png sprite sheet, and carve it up into individual sprite images (stored on a map called images). Then lines 30-44 prepare the sounds.

Lines 46-72 define a subclass of Sprite called GameSprite. GameSprite has an update method that implements simple physics: it adds its velocity (times the time step) to its position, and does the wrap-around thing (if the object goes off the screen on one side, it reappears on the opposite side).

The player ship is defined as a specialized GameSprite in lines 76-168. This is where all the logic for handling input is found. There are also methods here to destroy and respawn the ship.

Next, on lines 172-184 we have a little GameSprite subclass called TimedSprite, which simply keeps track of how long it's been around and destroys itself after a set amount of time. Then, Bullet is made as a subclass of that (lines 186-196), which is why you can spray bullets everywhere and they don't just accumulate; they automatically disappear after 0.6 seconds.

Next up is the Enemy class, on lines 198-216; it's a GameSprite that also rotates at a set rate, and checks for hitting the player ship or a bullet. We have two subclasses of Enemy, as I bet you can guess: First there's Rock (lines 218-255), which is just one of the hunks of rock flying around. And then Ufo (lines 257-317), a bit more complicated subclass because it has its own special sound, it fires bullets, etc. But if you take those methods one at a time, they're not so bad.

The UfoBullet subclass of Bullet (lines 319-328) is special only because it checks for hitting the player ship.

That's it for classes; the rest of the program is various utility functions. For example, safeToRespawn (lines 331-336) determines whether any rocks are overlapping the respawn area, so we know when it's safe to respawn the player ship. The makeDebris function makes a bunch of tiny point sprites flying out from some position. The hearthbeat class (oops, I guess there was one more) manages that bip-boop background sound.

The last block of code that's really worth highlighting is the "autoplay" code on lines 396-412. This is used when the program is invoked in "autoplay" mode (i.e., when we have an environment shell and env.autorun is true).

autoplay = env.hasIndex("shell") and env.shell and env.autorun
if autoplay then
    // make some dummy inputs for auto-play mode!
    key = {}
    key.axis = function(which)
        if which == "Vertical" then
            return cos(time) + cos(time/2) > 0.8
        end if
        c = cos(time) + cos(time/2) + cos(time * 10)
        if abs(c) < 0.5 then return 0
        return sign(c)
    end function
    key.pressed = function(k)
        if k != "space" then return false
        return rnd < 0.1
    end function
end if
Enter fullscreen mode Exit fullscreen mode

When autoplay is true, we actually hide the standard built-in key module and replace it with our own! We define key.axis to return a value that depends on the time, and define key.pressed so that it randomly returns true for the spacebar, 10% of the time. This hackery makes the game play by itself — not very well, mind you, but well enough for an auto-play demo.

Having Fun & Learning Stuff

So there's our demo highlight for today. Remember, the demos are there to be fun and show off what Mini Micro can do, but also for you to learn from. So hack away! Here are some ideas. How many of these can you figure out?

  1. Change the color of the ship, UFO, and rocks.
  2. Change the sounds.
  3. Give yourself 5 lives instead of 3.
  4. Make it so you can also thrust backwards, not just forwards.
  5. Add a "hyperspace jump" button that instantly teleports your ship to another location on the screen.

And, as always, you can grab techniques and tricks from this code and apply it to your own, entirely different games.

If you found anything about this demo particularly interesting, surprising, or educational, let us know in the comments below!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .