Before we are ready to jump straight into coding, there are some things we can think about and try to plan out without writing a single line of code. This page covers the thought process I went through while making the Octopus game for CircuitPython. If you're a visual person, you could be making sketches or other illustrations during this process to get a feel for what "moving parts" your game will have. 

Select a game

Perhaps this goes without saying, but you need to decide on which game you want to port to CircuitPython. Or, if you're feeling extra creative, you could invent a new one! I looked through the original classic Game & Watch games and Octopus was the first one that caught my attention for more than a quick glance. I may still go back and port more of the Game & Watch games to CircuitPython, but the following were the main reasons I went with this one for the learn guide:

  • It has relatively basic graphics -- most things that need to be drawn can be either a bitmap image file that is hidden or shown, or a single tile within a sprite sheet of evenly sized sprites.
  • Sprite assets were available online. They'll require some modification and format conversion, but I don't think I'll need to make anything from scratch.
  • The gameplay is not super complex -- the user has two main inputs: Left and Right. The only NPC entity is the Octopus with its extending and retracting tentacles.

If it's your first time porting a game or you don't have much experience with CircuitPython displayio yet, consider some of these things when deciding on the game to port. If you have more experience already, you could aim for something more advanced.

Assess the Assets

Graphics

CircuitPython displayio graphics are primarily based around Bitmaps. Now is a good time to take stock of what assets are available for your game and what size and format those graphics are. This will give you an idea of what preparations will be required for your assets. You can also start making a first guess about how the graphics could be implemented. The primary ways to show graphics with displayio are:

  • ImageLoad library to load a .bmp file into a Bitmap object in memory, then show it with a TileGrid. Loading the Bitmap into memory gives you the option to manipulate it before or as you show it. If you anticipate needing to change the pixels within your graphics from code, then this is the best option. However Bitmaps do consume precious RAM, so if you don't need this level of control, other options may be better. This is great for small graphics that need to be changed somewhat frequently or arbitrarily. For animations or different visual states you can use a sprite sheet with the TileGrid to change between different sprites easily.
  • OnDiskBitmap to load a .bmp file directly to the display without storing it in memory. This means you cannot manipulate the pixels within the Bitmap. It can still be shown on the display and it will not consume as much RAM, so you can load larger images and not risk running out! These are great for backgrounds and other larger images that will remain static or only change infrequently.
  • Basic shapes with vectorio or display_shapes library. If you need to draw circles, rectangles or other polygons, there are various shape helper classes in these modules. These are good for making borders or backgrounds of panels or other graphics. These are solid-fill only, so if you want gradients or anything more complex than single-color fill, you'll need to use a Bitmap instead.

Don't feel stuck with a particular choice at this point, because there is still time to make adjustments. Just try to get a first guess at which of these makes sense for the different graphics that your game will utilize. If you have lots of different graphical components and aren't sure how to display them, consider making smaller self-contained test scripts to render a small subset of the graphics. You can try out different methods to see which ones are best suited for the graphics in your game.

For the Octopus game, I knew I needed to show and hide lots of individual tentacle segments and I wasn't sure if I'd want to change pixels within them -- but I wanted to keep the option open if possible. I wanted to verify before I got too far along whether I would have enough RAM on the PyGamer for them to be loaded into memory. In order to check, I cut out one segment and converted it to a .bmp file. I made a test script to load the same segment several times over with the help of a for loop. I set it to load more than I would actually end up needing for the game, which ensured that there was plenty of room left over. I loaded the background and a few other sprites to represent different graphics for the game all in the same script to ensure everything would fit together. Once I was sure we'd have enough RAM with this plan, I moved on to processing the rest of the game assets.

My plan for graphics is as follows:

  • Background scene and octopus main body will all exist in a single bitmap file that is the same size as the target display, and will be shown with OnDiskBitmap. There are two versions of the background scene that we can allow the user to switch between.
  • Player's diver character will be loaded with Imageload library and will use a sprite sheet that has all of the different visual states needed.
  • Octopus tentacle segments will each be loaded individually with the Imageload library.
  • Extra life indicator divers on the boat will be OnDiskBitmaps hidden and shown as needed to show the current extra lives. 
  • Treasure-retrieving diver on the boat will be loaded with Imageload library and use a spritesheet with the different frames of the animation to pull treasure up.
  • Caught-flailing diver will be loaded with Imageload and will use a sprite sheet that has each frame of the flailing animation. 

Sounds

CircuitPython has support for .wav and low-bitrate .mp3 files. There is also RawSample which allows you to make beeps, boops and other synth-like sounds. Similarly to the graphics, it's a good idea to try to make a first guess about how your game's sounds can be played. If you're unsure, you can make proof-of-concept scripts to try out the different types, and get a feel for APIs.

Planning Classes

To make your code easy to understand and develop, it is best to break things down into classes that encapsulate the behavior and graphics for your game elements. The exact way that it will break down is going to depend a lot on your specific game and what graphics and behaviors it has. Remember as well that we are still in the planning stage -- we're not committed to any specific design yet, and as you develop the game, it may still make sense to break out additional classes, or even to collapse and combine some of the ones you initially planned. This process is a bit of an art form, and requires practice. Don't get discouraged if your classes don't end up being perfect, because you can always tweak and mold things over time to add different behaviors as needed. Ideally you'll want to break it down in such a way that you'll have high-level variables with descriptively-named functions that make it easy to understand and follow the logic of the game while reading the code. 

For the Octopus game, I had the following ideas for classes:

  • OctopusGame - A high-level object that orchestrates and manages the entire game. It has functions for each hardware input event that the player can cause. It holds instances of the other classes. The Game class should have a tick() function that will get called from the main loop. In my game, tick() is called very quickly over and over. The game object keeps track of time and has a built-in delay to decide when the next action should occur within the game. 
  • DiverPlayer - A spritesheet-based bitmap image that manages its own location on the screen and currently-showing sprite. It has simple action functions that the Game object will call when the user inputs actions, and these will control the behavior of the player character in my game.
  • Octopus - Holds several bitmap images representing the octopus tentacle segments. This class manages the hiding and showing of the tentacle segments over time as the game is being played.

These classes can cover the majority of the more complex behaviors in the Octopus game, but there are a few simpler elements that deserve honorable mention.

Basic Elements

These don't have very complex behaviors and therefore don't need custom classes created for them -- the built-in displayio objects will do just fine. These will get created and managed by the Game class. Any elements in your game that need to hide/show, move around, or change sprites (but nothing more complex) could be good candidates for basic elements.

  • background - An OnDiskBitmap to show the background scene. Could support different backgrounds if we want.
  • caught_diver - A TileGrid that uses a spritesheet with a few sprites that represent the animation of the diver that is flailing its arms and legs when caught by the octopus.
  • boat_diver - A TileGrid that uses a spritesheet with 2 sprites showing the two states of pulling treasure out of the water and into the boat.
  • extra_life - There are two extra_life TileGrids that each show a static BMP image. These are hidden and shown as needed to indicate the players remaining. They're positioned in the boat near the top left. 
  • score_label - A display_text Label to show the player's current score.

This guide was first published on Aug 31, 2022. It was last updated on Mar 16, 2024.

This page (Planning Process) was last updated on Mar 08, 2024.

Text editor powered by tinymce.