Pixel-y Panel Project: Spritesheets for Animation
At the company I work for, we recently launched a pixel-y status board called "Display" that immediately caught my eye:
It runs a custom app on the Tidbyt platform and was a successful Kickstarter in 2021. While it has a lot going for it (dimmable display, mobile app configuration, SDK and 3rd party apps, beautiful wood case), at $180 USD, it's still more than I want to spend.
A tweet I saw about it featured an animated character on the display which got me interested immediately. I dove right into a spritesheet animation tutorial on Adafruit. The basic idea is to use a single bitmap image that has all the frames on it, then use the displayio.TileGrid class to conveniently cycle through the frames. The finished demo:
Here's some notes on how I did it:
- A
TileGrid
represents a grid of images taken from a source bitmap. The bitmap is partitioned into equally sized "tiles", and any of these sub-regions can be shown as an image in theTileGrid
. - To choose a tile from the bitmap to be shown on a
TileGrid
, and to reference the bitmap's tiles are indexed in row-major order. For example, suppose we have a bitmap that is 256 x 256 pixels, and we define the tile size to be 64 x 64 pixels. Each of the bitmap tile regions would be numbered from 0 to 15. - The tutorial encourages you to use a vertical spritesheet, because it's easy to create and simplifies indexing. But a horizontal one works just as well. For example, my spritesheet lives in a bitmap of 160 x 32 pixels, so each one of the five tiles is 32 x 32 pixels. Since we only need to display one frame at a time, we make the
TileGrid
have a width and height of 1:
bitmap = displayio.OnDiskBitmap(filename)
sprite = displayio.TileGrid(
bitmap,
pixel_shader=bitmap.pixel_shader,
width=1,
height=1,
tile_width=32,
tile_height=32
)
frame_count = int(bitmap.width / 32)
- To make the sprite move back and forth across the screen, I update the sprite's
x
member, keeping track of the current direction and when it's moved off-screen. In addition, I mirror the character when it is walking in the opposite direction by toggling the sprite'sflip_x
attribute: this eliminates the need to have a second, flipped version of the spritesheet.
def advance_frame():
global current_frame
current_frame = current_frame + 1
if current_frame >= frame_count:
current_frame = 0
sprite_group[1][0] = current_frame
if sprite_group[1].x < -32 or sprite_group[1].x > 64:
sprite_group[1].flip_x = not sprite_group[1].flip_x
direction = -1 if not sprite_group[1].flip_x else 1
sprite_group[1].x += direction