With this game, I decided to go with 256-color graphics for a couple of reasons. Mostly that the graphics I was using had very few colors, and that it would conserve memory. The first thing I needed to do was make sure that all of the graphics used the same palette. I used Photoshop for all images and used the following steps:
I set the image mode to use indexed color and then changed the Palette Setting to "System (Windows)".
It's important to change this from the default setting of Exact, which means that it only uses the exact colors in that particular image. Since CircuitPython only allows a single palette to be active at a time, if another image has different colors, the palettes will be different and likely won't display correctly together. For more details on how palettes work in CircuitPython, check out the Bitmap and Palette section of the CircuitPython Display Support Using displayio guide which does an excellent job explaining the graphics.
For the purposes of the game programming, you have to keep in mind that a palette is a list of the colors that are used and the bitmap is a grid of indexes that refer to this palette. The palette is sometimes referred to as the shader, which is an umbrella term that encompasses palettes as well as Color Converters used with higher resolution graphics. One of the challenges is to specify a color to use, rather than assigning the color directly, the color index needs to be used. To do that, I wrote a simple function that would scan through the palette and return the color index if found:
def get_color_index(self, color, shader=None): if shader is None: shader = self._shader for index, palette_color in enumerate(shader): if palette_color == color: return index return None
Another challenge was working with the labels. They return a 2-color palette with 0 being the background color and 1 being the foreground color. To get the text to show the correct color within the palette, the foreground and background indices need to be reassigned. A new 256-color bitmap is created because displayio does not provide a mechanism to change the number of available colors in a bitmap, which is likely due to memory reallocation. Then the bitmap is scanned pixel by pixel and the new bitmap has the index set to match the correct color index.
def reassign_indices(self, bitmap, foreground_color_index, background_color_index): # This will reassign the indices in the bitmap to match the palette new_bitmap = displayio.Bitmap(bitmap.width, bitmap.height, len(self.shader)) if background_color_index is not None: for x in range(bitmap.width): for y in range(bitmap.height): if bitmap[(x, y)] == 0: new_bitmap[(x, y)] = background_color_index if foreground_color_index is not None: for x in range(bitmap.width): for y in range(bitmap.height): if bitmap[(x, y)] == 1: new_bitmap[(x, y)] = foreground_color_index return new_bitmap
With those 2 challenges solved, the rest of the graphics work was fairly straightforward. The only other place where the color index was used was in specifying the key color for bitmaptools blit
function, which it avoids drawing. This allows 2 different tiles to be drawn on top of each other.
This spritesheet includes all of the tiles used as well as a duplicate set of keyed tiles (the ones with a light green background) for the creatures that need to be drawn on top of different backgrounds. The reason for having 2 different sets is for speed because most of the time, the creatures are drawn on top of empty floor, but occasionally (such as sliding on ice), the creature needs to be drawn on top of empty floor. This is to speed up the game, as most of the time, only one sprite needs to be drawn instead of 2.
Page last edited April 09, 2025
Text editor powered by tinymce.