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 the TileGrid.
  • 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's flip_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
Eric Fung

Eric Fung