Home¶
In this project we will be making an old school style video game for the Adafruit PyBadge. We will be using CircuitPython and the stage library to create a Space Invaders like game. The stage library makes it easy to make classic video games, with helper libraries for sound, sprites and collision detection. The game will also work on other variants of PyBadge hardware, like the PyGamer and the EdgeBadge. The full completed game code with all the assets can be found here.
The guide assumes that you have prior coding experience, hopefully in Python. It is designed to use just introductory concepts. No Object Oriented Programming (OOP) are used so that students in particular that have completed their first course in coding and know just variables, if statements, loops and functions will be able to follow along.
Parts
You will need the following items:
Adafruit PyBadge for MakeCode Arcade, CircuitPython or Arduino
PRODUCT ID: 4200
Pink and Purple Braided USB A to Micro B Cable - 2 meter long
PRODUCT ID: 4148
So you can move your CircuitPython code onto the PyBadge.
You might also want:
Lithium Ion Polymer Battery Ideal For Feathers - 3.7V 400mAh
PRODUCT ID: 3898
So that you can play the game without having it attached to a computer with a USB cable.
Mini Oval Speaker - 8 Ohm 1 Watt
PRODUCT ID: 3923
If you want lots of sound. Be warned, the built in speaker is actually pretty loud.
I did not create this case. I altered Adafruit’s design. One of the screw posts was hitting the built in speaker and the case was not closing properly. I also added a piece of plastic over the display ribbon cable, to keep it better protected. You will need 4 x 3M screws to hold the case together.
Install CircuitPython¶
Before doing anything else, you should delete everything already on your PyBadge and install the latest version of CircuitPython onto it. This ensures you have a clean build with all the latest updates and no leftover files floating around. Adafruit has an excellent quick start guide here to step you through the process of getting the latest build of CircuitPython onto your PyBadge. Adafruit also has a more detailed comprehensive version of all the steps with complete explanations here you can use, if this is your first time loading CircuitPython onto your PyBadge.
Just a reminder, if you are having any problems loading CircuitPython onto your PyBadge, ensure that you are using a USB cable that not only provides power, but also provides a data link. Many USB cables you buy are only for charging, not transfering data as well. Once the CircuitPython is all loaded, come on back to continue the tutorial.
Your IDE¶
One of the great things about CircuitPython hardware is that it just automatically shows up as a USB drive when you attach it to your computer. This means that you can access and save your code using any text editor. This is particularly helpful in schools, where computers are likely to be locked down so students can not load anything. Also students might be using Chromebooks, where only “authorized” Chrome extensions can be loaded.
If you are working on a Chromebook, the easiest way to start coding is to just use the built in Text app. As soon as you open or save a file with a *.py
extension, it will know it is Python code and automatically start syntax highlighting.
If you are using a non-Chromebook computer, your best beat for an IDE is Mu. You can get it for Windows, Mac, Raspberry Pi and Linux. It works seamlessly with CircuitPython and the serial console will give you much needed debugging information. You can download Mu here.
Since with CircuitPython devices you are just writing Python files to a USB drive, you are more than welcome to use any IDE that you are familiar using.
Hello, World!¶
Yes, you know that first program you should always run when starting a new coding adventure, just to ensure everything is running correctly! Once you have access to your IDE and you have CircuitPython loaded, you should make sure everything is working before you move on. To do this we will do the traditional “Hello, World!” program. By default CircuitPython looks for a file called code.py
in the root directory of the PyBadge to start up. You will place the following code in the code.py
file:
1 | print("Hello, World!")
|
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
Although this code does work just as is, it is always nice to ensure we are following proper coding conventions, including style and comments. Here is a better version of Hello, World! You will notice that I have a call to a main()
function. This is common in Python code but not normally seen in CircuitPython. I am including it because by breaking the code into different functions to match different scenes, eventually will be really helpful.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/usr/bin/env python3
# Created by : Jay Lee
# Created on : January 2020
# This program prints out Hello, World! onto the PyBadge
def main():
# this function prints out Hello, World! onto the PyBadge
print("Hello, World!")
if __name__ == "__main__":
main()
|
Congratulations, we are ready to start.
Image Banks¶
Before we can start coding a video game, we need to have the artwork and other assets. The stage library from CircuitPython we will be using is designed to import an “image bank”. These image banks are 16 sprites staked on top of each other, each with a resolution of 16x16 pixels. This means the resulting image bank is 16x256 pixels in size. Also the image bank must be saved as a 16-color BMP file, with a pallet of 16 colors. To get a sprite image to show up on the screen, we will load an image bank into memory, select the image from the bank we want to use and then tell CircuitPython where we would like it placed on the screen.
For sound, the stage library can play back *.wav
files in PCM 16-bit Mono Wave files at 22KHz sample rate. Adafruit has a great learning guide on how to save your sound files to the correct format here.
If you do not want to get into creating your own assets, other people have already made assets available to use. All the assets for this guide can be found in the GitHub repo here:
This is constants file to be used in this game.
Please download the assets and place them on the PyBadge, in the root directory. Your previoud “Hello, World!” program should restart and run again each time you load a new file onto the PyBadge, hopefully with no errors once more.
Assets from other people can be found here.
Game¶
This section describes things realted to gameplay. Go through the post below.
Background¶
Avoid or Shoot needs a background. This code is puts the first image in the background. The first image is 16 x 16 px image at the top. in this case the first image of image bank is white colour image. Of course when you save the file, save it as code.py
file:
1 2 3 | image_bank_1 = stage.Bank.from_bmp16("avoid_or_shoot.bmp")
background = stage.Grid(image_bank_1, 10, 8)
|
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
This code will not work. The code above has a lot to do. Here is a better version that shows the background. You can see that you called the main()
function. This is common in python code but usually not visible in CircuitPython. I am including it because by breaking the code into different functions to match different scenes, eventually will be really helpful.
1 2 3 4 5 6 7 | for y_location in range(8):
for x_location in range(16):
if y_location == 1:
tile_picked = random.randint(1, 3)
else:
tile_picked = random.randint(1, 1)
background.tile(x_location, y_location, tile_picked)
|
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
This codes replace the background of pybadge with another 16x16 image. pybadge contains 16 images on the x-axis and 8 image on the y-axis in the image bank. It fills that background with a for loop. When the second image is filled on the y-axis, it randomly fills the second to fourth images in the image bank.
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 27 28 29 30 31 32 33 34 | #!/usr/bin/env python3
# Created by : Jay Lee
# Created on : Jan 2020
# This program display background of pybadge
import ugame
import stage
import random
def main():
# this function display background of pybadge
image_bank_1 = stage.Bank.from_bmp16("avoid_or_shoot.bmp")
background = stage.Grid(image_bank_1, 10, 8)
for y_location in range(8):
for x_location in range(16):
if y_location == 1:
tile_picked = random.randint(1, 3)
else:
tile_picked = random.randint(1, 1)
background.tile(x_location, y_location, tile_picked)
game = stage.Stage(ugame.display, 60)
game.layers = [background]
game.render_block()
while True:
pass
if __name__ == "__main__":
main()
|
This is the basis of the background function.
Plane Selection¶
I created the selection scene that can choose the plane by user. Planes are placed in quadrant on the screen. And the select box has to move exactly on the plane’s position.
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 27 28 29 30 31 32 33 34 35 | while True:
keys = ugame.buttons.get_pressed()
if keys & ugame.K_UP != 0:
if select_box1.y != constants.SCREEN_Y / 4:
select_box1.move(int(select_box1.x),
int(constants.SCREEN_Y / 4))
else:
pass
pass
if keys & ugame.K_DOWN != 0:
if select_box1.y != (constants.SCREEN_Y * 3 / 4 -
constants.SPRITE_SIZE):
select_box1.move(int(select_box1.x),
int(constants.SCREEN_Y * 3 / 4 -
constants.SPRITE_SIZE))
else:
pass
pass
if keys & ugame.K_LEFT != 0:
if select_box1.x != constants.SCREEN_X / 4:
select_box1.move(int(constants.SCREEN_X / 4),
int(select_box1.y))
else:
pass
pass
if keys & ugame.K_RIGHT != 0:
if select_box1.x != (constants.SCREEN_X * 3 / 4 -
constants.SPRITE_SIZE):
select_box1.move(int(constants.SCREEN_X * 3 / 4 -
constants.SPRITE_SIZE),
int(select_box1.y))
else:
pass
pass
|
There is full codes of selection scene. => selection_scene.py <=
In the codes, I send airplane information to the game.
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
Now, you can choose plane on your PyBadge.
Show Airplane¶
sprites can be placed in front of the background. Create a list called sprites and put the ninth image in the image bank.
1 2 3 4 | sprites = []
plane = stage.Sprite(image_bank_1, 8, 72, 56)
sprites.append(plane)
|
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
This code will not work. There is a lot more to this code. Below is the full version of the code above. I imported stage and ugame. Also I add the list of sprites in layers and in the game rendering of while loop function.
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 27 28 29 30 31 32 33 34 35 36 37 38 | #!/usr/bin/env python3
# Created by : Jay Lee
# Created on : Jan 2020
# This program display sprite
import ugame
import stage
def main():
# this function display background of pybadge
image_bank_1 = stage.Bank.from_bmp16("avoid_or_shoot.bmp")
background = stage.Grid(image_bank_1, 10, 8)
sprites = []
plane = stage.Sprite(image_bank_1, 8, 72, 56)
sprites.append(plane)
# create a stage for the background to show up on
# and set the frame rate to 60fps
game = stage.Stage(ugame.display, 60)
# set the layers, items show up in order
game.layers = sprites + [background]
# render the background and inital location of sprite list
# most likely you will only render background once per scene
game.render_block()
# repeat forever, game loop
while True:
game.render_sprites(sprites)
game.tick()
if __name__ == "__main__":
main()
|
Now, you can get a sprite to show up in front of your background on your PyBadge.
Move Airplane¶
sprites can move on the screen. you make some if statements in the while loop. It makes the plane to move. In pybadge, the y value increases as it go down, so you need to subtract the y value from the Up button.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | While True:
keys = ugame.buttons.get_pressed()
if keys & ugame.K_RIGHT:
plane.move(plane.x + 1, plane.y)
pass
if keys & ugame.K_LEFT:
plane.move(plane.x - 1, plane.y)
pass
if keys & ugame.K_UP:
plane.move(plane.x, plane.y - 1)
pass
if keys & ugame.K_DOWN:
plane.move(plane.x, plane.y + 1)
pass
game.render_sprites(sprites)
game.tick()
|
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
Now, you can move a plane on your PyBadge.
Enemy¶
The game requires the appearance of enemies and the loading missiles.
1 2 3 4 5 6 7 8 9 10 11 12 | def show_flying():
enemy_picked = random.randint(0, 1)
if enemy_picked == 0:
if bird.y < 0:
bird.move(200, random.randint(0 + constants.SPRITE_SIZE,
constants.SCREEN_Y -
constants.SPRITE_SIZE))
else:
if enemy.y < 0:
enemy.move(200, random.randint(0 + constants.SPRITE_SIZE,
constants.SCREEN_Y -
constants.SPRITE_SIZE))
|
Create a function to show enemy. This function represents randomly set y values for the enemy. Also create a function that represents a missile like this.
1 2 3 4 5 6 7 8 9 10 | if bird.y > 0:
bird.move(bird.x - 2, bird.y)
if bird.x < constants.OFF_SCREEN_X:
bird.move(constants.OFF_SCREEN_X, constants.OFF_SCREEN_Y)
show_flying()
elif enemy.y > 0:
enemy.move(enemy.x - 2, enemy.y)
if enemy.x < constants.OFF_SCREEN_X:
enemy.move(constants.OFF_SCREEN_X, constants.OFF_SCREEN_Y)
show_flying()
|
Make this code is while loop. Then you can see the enemy moving. Also make the code that moves a missile like that.
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
Shoot the Missile¶
Create multiple missiles and put them in the list because missiles can appear on single screen.
1 2 3 4 5 6 | missiles = []
for missile_number in range(constants.TOTAL_NUMBER_OF_MISSILES):
missile = stage.Sprite(image_bank_3, 12, constants.OFF_SCREEN_X,
constants.OFF_SCREEN_Y)
missiles.append(missile)
|
So I used for loop to create multiple missiles.
1 2 3 4 5 6 7 8 9 10 | if keys & ugame.K_X != 0:
if a_button == constants.button_state["button_up"]:
a_button = constants.button_state["button_just_pressed"]
elif a_button == constants.button_state["button_just_pressed"]:
a_button = constants.button_state["button_still_pressed"]
else:
if a_button == constants.button_state["button_still_pressed"]:
a_button = constants.button_state["button_released"]
else:
a_button = constants.button_state["button_up"]
|
The above distinguishes when pressed, when still pressed, and when not pressed.
1 2 3 4 5 | if a_button == constants.button_state["button_just_pressed"]:
if number_of_missiles > 0:
for missile_number in range(len(missiles)):
if missiles[missile_number].y < 0:
missiles[missile_number].move(plane.x, plane.y)
|
When the A button is pressed, the missile move to plane location.
1 2 3 4 5 6 7 8 | for missile_number in range(len(missiles)):
if missiles[missile_number].y > 0:
missiles[missile_number].move(missiles[missile_number].x +
constants.MISSLE_SPEED,
missiles[missile_number].y)
if missiles[missile_number].x > constants.SCREEN_X:
missiles[missile_number].move(constants.OFF_SCREEN_X,
constants.OFF_SCREEN_Y)
|
Then, missile move from plane to right side. And if the missile go off the screen, it moves to the location where I created it.
As soon as you save the file onto the PyBadge, the screen should flash and you should see something like:
Now, you can shoot the missiles on your PyBadge.