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 Frogger 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:








USB Cable

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:

Lipo Battery

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.






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.






USB Cable

3D Printed Case

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

PyBadge UF2

Clearing the PyBadge and loading the CircuitPython UF2 file

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.

Chromebook Text Editor

Chromebook Text app

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.

Mu Editor

Mu IDE

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:

Hello, World!

Hello, World! program on PyBadge

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 : Mr. Coxall
# 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.

Image Bank for Clown Town

Image Bank for Clown Town

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:

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 is where we started to make the actual game itself we first started by creating our background and placing our sprites, where we than procceded to make our sprite move accross the screen, as well as setting a border for it to not to escape the screen. We than procceded to spawn objects and make them fall down from the top of the screen. We later than used for loops and if statements to make objects respawn randomly at the top of the screen, and finally we added a scoring system that increases by one every time you complete a wave.

  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
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
  def game_scene():
    # this function is the game scene
    score = 0

    text = []

    score_text = stage.Text(width=29, height=14, font=None, palette=constants.SCORE_PALETTE, buffer=None)
    score_text.clear()
    score_text.cursor(0, 0)
    score_text.move(1, 1)
    score_text.text("Score: {0}".format(score))
    text.append(score_text)

    def show_tomato():
        # make an tomato show up on screen on the x-axis
        for tomato_number in range(len(tomatos)):
            if tomatos[tomato_number].x < 0:
                tomatos[tomato_number].move(random.randint
                                          (0 + constants.SPRITE_SIZE,
                                           constants.SCREEN_X -
                                           constants.SPRITE_SIZE),
                                          constants.OFF_TOP_SCREEN)
                break

    def show_pie():
        for pie_number in range(len(pies)):
            if pies[pie_number].x < 0:
                pies[pie_number].move(random.randint
                                          (0 + constants.SPRITE_SIZE,
                                           constants.SCREEN_X -
                                           constants.SPRITE_SIZE),
                                          constants.OFF_TOP_SCREEN)
                break

    def show_balloon():
        for balloon_number in range(len(balloons)):
            if balloons[balloon_number].x < 0:
                balloons[balloon_number].move(random.randint
                                          (0 + constants.SPRITE_SIZE,
                                           constants.SCREEN_X -
                                           constants.SPRITE_SIZE),
                                          constants.OFF_TOP_SCREEN)
                break

    # an image bank for CircuitPython
    image_bank_2 = stage.Bank.from_bmp16("sprites.bmp")

    splat_sound = open("splat.wav", "rb")
    sound = ugame.audio
    sound.stop()
    sound.mute(False)

    tomatos = []
    pies = []
    balloons = []

    # drops tomatos
    for tomato_number in range(constants.TOTAL_NUMBER_OF_TOMATOS):
        a_single_tomato = stage.Sprite(image_bank_2, 3,
                                      constants.OFF_SCREEN_X,
                                      constants.OFF_SCREEN_Y)
        tomatos.append(a_single_tomato)

    show_tomato()

    # drops pie
    for pie_number in range(constants.TOTAL_NUMBER_OF_PIES):
        a_single_pie = stage.Sprite(image_bank_2, 4,
                                      constants.OFF_SCREEN_X,
                                      constants.OFF_SCREEN_Y)
        pies.append(a_single_pie)

    show_pie()


    # drops balloon
    for balloon_number in range(constants.TOTAL_NUMBER_OF_BALLOONS):
        a_single_balloon = stage.Sprite(image_bank_2, 5,
                                      constants.OFF_SCREEN_X,
                                      constants.OFF_SCREEN_Y)
        balloons.append(a_single_balloon)

    show_balloon()

    # sets the background to image 0 in the bank
    background = stage.Grid(image_bank_2, constants.SCREEN_GRID_X, constants.SCREEN_GRID_Y)

    clown = stage.Sprite(image_bank_2, 2, 74, 56)
    sprites.insert(0, clown)  # insert at the top of sprite list

    # create a stage for the background to show up
    # setting the frame rate to 60fps
    game = stage.Stage(ugame.display, 60)
    # setting the layers to show them in order
    game.layers = text + sprites + pies + tomatos + balloons + [background]
    # rendering the background and the locations of the sprites
    game.render_block()

    # repeat forever game loop
    while True:
        # get user input
        keys = ugame.buttons.get_pressed()

        if keys & ugame.K_RIGHT != 0:
            if clown.x > constants.SCREEN_X - constants.SPRITE_SIZE:
                clown.move(constants.SCREEN_X - constants.SPRITE_SIZE, clown.y)
            else:
                clown.move(clown.x + constants.CLOWN_SPEED, clown.y)
        if keys & ugame.K_LEFT != 0:
            if clown.x < 0:
                clown.move(0, clown.y)
            else:
                clown.move(clown.x - constants.CLOWN_SPEED, clown.y)
        if keys & ugame.K_UP != 0:
            if clown.y < 0:
                clown.move(clown.x, 0)
            else:
                clown.move(clown.x, clown.y - constants.CLOWN_SPEED)
        if keys & ugame.K_DOWN != 0:
            if clown.y > constants.SCREEN_Y - constants.SPRITE_SIZE:
                clown.move(clown.x, constants.SCREEN_Y - constants.SPRITE_SIZE)
            else:
                clown.move(clown.x, clown.y + constants.CLOWN_SPEED)

        # resets tomatos and adds score
        for tomato_number in range(len(tomatos)):
            if tomatos[tomato_number].x > 0:
                tomatos[tomato_number].move(tomatos[tomato_number].x,
                                          tomatos[tomato_number].y +
                                          constants.TOMATO_SPEED)
                if tomatos[tomato_number].y > constants.SCREEN_Y:
                    tomatos[tomato_number].move(constants.OFF_SCREEN_X,
                                              constants.OFF_SCREEN_Y)
                    score += 1
                    score_text.clear()
                    score_text.cursor(0, 0)
                    score_text.move(1, 1)
                    score_text.text("Score: {0}".format(score))
                    game.render_block()
                    show_tomato()

        # resets pies
        for pie_number in range(len(pies)):
            if pies[pie_number].x > 0:
                pies[pie_number].move(pies[pie_number].x,
                                          pies[pie_number].y +
                                          constants.PIE_SPEED)
                if pies[pie_number].y > constants.SCREEN_Y:
                    pies[pie_number].move(constants.OFF_SCREEN_X,
                                              constants.OFF_SCREEN_Y)
                    show_pie()

        # resets balloons
        for balloon_number in range(len(balloons)):
            if balloons[balloon_number].x > 0:
                balloons[balloon_number].move(balloons[balloon_number].x,
                                          balloons[balloon_number].y +
                                          constants.BALLOON_SPEED)
                if balloons[balloon_number].y > constants.SCREEN_Y:
                    balloons[balloon_number].move(constants.OFF_SCREEN_X,
                                              constants.OFF_SCREEN_Y)
                    show_balloon()

        # collision with tomato
        for tomato_number in range(len(tomatos)):
            if tomatos[tomato_number].x > 0:
                if stage.collide(tomatos[tomato_number].x + 1,
                                 tomatos[tomato_number].y,
                                 tomatos[tomato_number].x + 15,
                                 tomatos[tomato_number].y + 15,
                                 clown.x, clown.y, clown.x + 15, clown.y + 15):
                    sound.stop()
                    sound.play(splat_sound)
                    time.sleep(2.0)
                    sound.stop()
                    sprites.remove(clown)
                    game_over_scene(score)

        # collision with pie
        for pie_number in range(len(pies)):
            if pies[pie_number].x > 0:
                if stage.collide(pies[pie_number].x + 1,
                                 pies[pie_number].y,
                                 pies[pie_number].x + 15,
                                 pies[pie_number].y + 15,
                                 clown.x, clown.y, clown.x + 15, clown.y + 15):
                    sound.stop()
                    sound.play(splat_sound)
                    time.sleep(2.0)
                    sound.stop()
                    sprites.remove(clown)
                    game_over_scene(score)

        # collision with balloon
        for balloon_number in range(len(balloons)):
            if balloons[balloon_number].x > 0:
                if stage.collide(balloons[balloon_number].x + 1,
                                 balloons[balloon_number].y,
                                 balloons[balloon_number].x + 15,
                                 balloons[balloon_number].y + 15,
                                 clown.x, clown.y, clown.x + 15, clown.y + 15):
                    sound.stop()
                    sound.play(splat_sound)
                    time.sleep(2.0)
                    sound.stop()
                    sprites.remove(clown)
                    game_over_scene(score)

        # update game logic

        # redraw sprite list
        game.render_sprites(sprites + pies + tomatos + balloons)
        game.tick()  # wait until refresh rate finishes

Background

For our background we used a simple statement to fill our background with the first box of our sprites. Our background is a simple circus themed red and black stripes running down the screen. We used it for our splash, game, and game over screen.

1
  background = stage.Grid(image_bank_2, constants.SCREEN_GRID_X, constants.SCREEN_GRID_Y)

Clown

For our main character we created a simple 16x16 clown sprite which we than just placed at the center of the screen when the user presses start from the menu screen. We spawned our sprite using a simple statement and placing its coordinates.

1
2
3
4
5
  # an image bank for CircuitPython
  image_bank_2 = stage.Bank.from_bmp16("sprites.bmp")

  clown = stage.Sprite(image_bank_2, 2, 74, 56)
  sprites.insert(0, clown)  # insert at the top of sprite list