Introduction: Space Invaders in Micropython on Micro:bit
In our previous articles we have explored game making on GameGo, a portable retro gaming console developed by TinkerGen education. The games that we made were reminiscent of old Nintendo games. In today's article, we're going to take a step back, to the golden age of arcade games. We will re-create Space Invaders game in Micropython on Micro:bit microcontroller - and as a twist, we will also use BitPlayer Micro:bit extension, which allows us to play game more conveniently.
Since this tutorial is about coding the game in Micropython, instead of traditional stage-by-stage sequence we used before for graphical programming tutorials, we're going to go over the code block by block - going over important functions, classes and the main loop. You can download the full code in this project's GitHub repository. Buckle up and let's begin!
Step 1: Main Loop
In this tutorial I'll be using word "method" quite often. A method in python is somewhat similar to a function, except it is associated with object/classes. So, for simplification, you can read it as "a function within the class". You can read more about methods here.
We enter the main loop with
while not game_over:
condition. Inside we get the number of invaders, chance of their appearing and number needed to get to the next level from levels dictionary. Next we check for left-right movement with Listen_Dir instance methods of class instance JoyStick. If one of the conditions evaluates as True, we increment/decrement x-value of our playable character. We constrain it to [-2,2] with two if conditions. Then we initialize an instance of DisplayBuffer class and check for "shield" or "fire bullet" button presses. We use method DisplayBuffer.set() to set objects for later rendering. For rendering the shield we use DisplayBuffer.set() directly, but for bullets and invaders we add them to their respective list and set() them one by one in for loop with following code for later rendering with DispBuffer.render():
for b in bullets:
b.render(dispBuf) for v in vaders: v.render(dispBuf)
All of the invaders, bullets and shield are rendered on display once every main loop iteration with
Before the end of the main loop we check if any of the bullets of invaders have reached the end of screen, and if they are, we delete them from their respective lists.
Step 2: Joystick
BitPlayer is easy to hold and use, with a 2-axis joystick like Gameboy or PSP controllers, it also includes another 6 programmable buttons labeled as L, R, A, B, C and D. For an immersive and interactive experience, the BitPlayer itself features a buzzer, a vibration motor and a Grove I2C port to connect additional peripherals like an OLED display.
We only use left-right rocker of the joystick for this game, for full example on use of all BitPlayer buttons you can have a look at joystick_example.py in this project's GitHub repository. On creating the instance of JoyStick class we check for X-axis default reading and store that value in self.Read_X. Then in function Listen_Dir, we check if deviation from that default value is higher than sensitivity variable (try tweaking it yourself, if you feel that JoyStick is too sensitive) and return True of False according to detected direction.
Let's have a look at concrete example of how this works:
Let's say our default X-axis reading is 0. Then, if we move Joystick right:
New_X = JoyStick_X.read_analog() #New_X=200 Right = New_X - self.Read_X #Right = 200 Left = self.Read_X - New_X #Left = -200
Then when we check for direction:
Precision = 150
if Right > Precision: #200 > 150 True Get_Rocker = DIR['R'] elif Left > Precision: #-200 > 150 False Get_Rocker = DIR['L'] else: Get_Rocker = DIR['NONE'] if Dir == Get_Rocker: return True else: return False
Step 3: Display Buffer
DisplayBuf class is responsible for controlling the LED screen. It is done using two methods, set() and render(). set() method changes the values corresponding to LED screen pixels. You might remember, that pixels on Micro:bit LED screen can be expressed as string or a list - "00000:00000:00000:00000:00000" is an empty screen. "00000:00000:00000:00000:00100" is a screen with dimly lit pixel in the center of the bottom row.
This notation might be easier to process :)
So, what we do during main loop is call set() method of DisplayBuf to set all our objects that need to be displayed on screen. Then we use render() method to actually show them all on the screen simultaneously.
Step 4: Invaders, Bullets and the Player
Bullets and Invaders belong to Mover class. Mover class instances have their x, y locations and speed, as well as brightness. Mover class has two instance methods, set() and move(). set() method simply calls DisplayBuf set() method with updated coordinates to save for later rendering on LED matrix. move() method updates instance coordinate according to instance speed - that comes useful later, when we need to change the speed of invaders as levels progress.
Class Bullet and class Invader are subclasses of Mover class. Here we use something called inheritance. The functionality of super() allows us to call methods of the superclass in subclass, without need to repeat the code.
Step 5: Make It Your Own
Congratulations! You have just re-created classic Space Invaders game on Micro:bit with some cool gaming hardware. Of course, you could improve the game code from here - for example, as of now, the game only has one level - you can add more challenging ones. Also, as you might recall, the original game has rocks floating in front of the player, which you can add as well.
If you do make an improved version of the game, share it in the comments below! For more information on BitPlayer and other hardware for makers and STEM educators, visit our website, https://tinkergen.com/ and subscribe to our newsletter.
TinkerGen has recently created a Kickstarter campaign for MARK(Make A Robot Kit), a robot kit for teaching coding, robotics, AI!
The original Micropython code from hexkcd/micro-vaders, was changed to work with TinkerGen BitPlayer.