pycoGame
February 20 2022So I've finally accomplished one of my long-term goals: creating a custom game console of some sort with removable game media. I've wanted to do this since high school when I first started playing with Arduino. I found the TVout library and made a clone of Space Invaders. My first attempt at a game console was based on that. The idea was to use one ATmega328 to program binary from an SD card to a second ATmega328 that would actually run the game. I never finished that project. Now, fast-forward to when Raspberry Pi Pico came out, and I got inspired to resurrect this old goal.
The result of that is pycoGame, a handheld game console based on Raspberry Pi Pico and a MicroPython library for easy game development, inspired by pygame.
HardwareThe design is quite simple. As I already mentioned, the brain of the operation is the Raspberry Pi Pico, an ARM microcontroller running at 133 MHz with 264 kB of RAM and 2 MB of Flash. The display and removable storage are handled by a board with an ILI9341 display connected over a 4-wire SPI and an SD card slot. However, this caused some limitations for the device. It seems that there is a large overhead with individual SPI calls, and the RPi Pico doesn't have enough RAM to store the full framebuffer required by the screen (320x240 in RGB565 color format). As a result, only a relatively small part of the screen can be redrawn every frame while maintaining a good framerate. I tried to use a smaller framebuffer and do pixel-doubling while sending the data to the display, but I didn't have luck with that. Battery charging is handled by a board with a TP4056 chip. Additionally, there is a power switch, 7 buttons, a small buzzer for sound, and a 4-pin connector with power, ground, and I2C for connecting peripherals or enabling communication with other pycoGame devices for multiplayer games.
SoftwareThe main software component is the pycoGame MicroPython library. It is inspired by pygame and provides basic functionality for creating games.
- Clock class is for working with timing and framerates. It mainly provides functions for decoupling the speed of the game from the framerate and calculating the framerate.
- Input class simply reads button presses and handles debouncing.
- Sound class can be used to play very simple sound effects and music in the Ring Tone Transfer Language. Technically, it has two sound channels, so music and sound effects can be played at the same time, but it sounds quite bad.
- Display class has functions for drawing single pixels, lines, and rectangles. It can also load and draw sprites or directly draw images to the screen without loading the whole image into RAM. Additionally, it can draw text using one of the three fonts provided in the Font class (5x8, 8x14, or 13x23) or with a custom font defined with the XglcdFont class. Lastly, it provides access to the hardware scrolling feature of the display.
The next part is pglauncher, which serves as the main menu of the console. It is a simple menu that loads games from the SD card, shows the battery charge level, and can be used to turn on/off sound. Additionally, there are some utilities for converting images into the format used by the display, for pre-compiling game code, and for building MicroPython with the needed libraries.
GamesI've created a few games to demonstrate the capabilities of the pycoGame hardware and software library.
1. Micronoid (Breakout clone)Micronoid is a game where you control a paddle and aim to destroy all the bricks on the screen using a ball. The brick layouts can be easily defined in simple text files. There are multiple brick types with different score values, toughness (requiring more hits to be destroyed), and some that are indestructible. Additionally, there is a chance that a destroyed brick will drop a positive or negative power-up pill that you can catch or avoid. The collision detection for bricks is currently quite buggy, and if you hit them in the right way the ball can go through them. I may fix that one day (probably not).
FlappyTux is a game that demonstrates infinite scrolling. I utilized the framebuffer scrolling function of the display itself to avoid redrawing large parts of the screen on every frame. However, this required some trickery when drawing sprites that cross from one side of the framebuffer to the other. Thanks to these optimizations, the game runs at a stable 60 frames per second.
MicroMaze 3D is a game that required a lot of optimization to achieve a playable framerate. However, it was also the most enjoyable to develop. It is a 3D game where your goal is to find a gem on the other side of a labyrinth. I implemented a simplified and optimized ray-casting engine, following guides [1], [2], [3]. I made several modifications, such as using "pre-rendered" images instead of drawing individual lines on the screen. These images contain columns of walls shaded based on their size, as well as sections with floor and sky colors. This approach artificially lowers the horizontal and vertical resolutions and allows me to redraw only sections that have changed.
- [1] https://lodev.org/cgtutor/raycasting.html
- [2] https://www.youtube.com/watch?v=NbSee-XM7WA
- [3] https://www.youtube.com/watch?v=xW8skO7MFYw
The whole project is, of course, open source, and you can find it on my GitHub page: https://github.com/velezd/pycogame