Animation Engine For The Pi Pico

The SSD1306 OLED display is commonly used by hobbyists to add an informative screen to their projects. But, as humans, we know what we truly want to do with screens… and that is to goof off.

The end goal is to make a game for this screen, but first we need to draw something other than text and lines.

The Beginnings

After reviewing the Micropython SSD1306 driver documentation I found that it had no native way to display an image, that you had to specify what it is you wanted to draw.

First I needed a way to represent transparent/visible portions of a sprite. This was relatively easy and I settled on a JSON format for sprites:

{
    "w": 16, 
    "h": 16, 
    "pixel_string": "0001111111111000001000000000010001000000000000100100000000000010010000000000001010000010010000011000001001000001100000100100000110000000000000011000000000000001100001000010000101000011110000100100000000000010010000000000001000100001100001000001111001111000"
}

The width/height are defined and passed to the draw function. The string is a list of all pixels in the image from top-left to bottom-right. This JSON string is then passed to a function that simply draws what is asked of it to the screen.

def draw_sprite(sp_dict, x, y, display):
    local_x = 0
    local_y = 0
    for hy in range(sp_dict["h"]):
        local_x = 0

        for wx in range(sp_dict["w"]):
            string_loc = local_y * sp_dict["w"] + local_x

            if sp_dict["pixel_string"][string_loc] == "1":
                display.pixel(x+local_x,y+local_y,1)

            local_x += 1

        local_y += 1

It works as advertised.

A photograph of the Pi Pico showing four little cloud sprites on the OLED screen. The sprites are from some old Mario game

After this it was time to decide how to animate some sprites. I have had experience writing of animation engines with the Pico-8 and Picotron, so I decided to pull on that. Usually what I do is have each animated object in the world keep track of its own tick and change sprites when their tick reaches a certain value.

def create_sprite_object(sprite_dict,name,x,y,anim=False,anim_fps=6):
    """
    Creates a sprite object dictionary. This takes the sprite information
    And adds in elements such as x, y, animated, anim_frame, anim_fps
    anim_tick 
    """

    return_dict = {
        "name": name,
        "x": x,
        "y": y,
        "anim": anim,                       # Animated True/False
        "anim_fps": anim_fps,               # Frames per second for the animation
        "anim_tick_count": math.floor(1/anim_fps),      # This is how high the ticket counts to
        "anim_spr": 1,                      # The current sprite to draw
        "num_spr": len(sprite_dict[name])  # Total number of sprites in animation
    }

    return return_dict

This time I decided to have each animated object only keep track of the tick up until the tick equals the appropriate time to change sprites then reset itself back to zero.

I also had to modify my script that created the sprite objects. I decided to have it look at all sprites in a directory and create one for each file that is similarly named. That means that if the “coin” needed multiple sprites in its animation then the files needed to be named coin_1.png coin_2.png and so on.

{
    "coin": {
        "4": {
            "w": 8,
            "h": 8,
            "pixel_string": "0000100000001000000010000000100000001000000010000000100000001000"
        },
        "1": {
            "w": 8,
            "h": 8,
            "pixel_string": "0001100000100100010010100100101001001010010010100010010000011000"
        },
        "3": {
            "w": 8,
            "h": 8,
            "pixel_string": "0000100000011000000110000001100000011000000110000001100000001000"
        },
        "2": {
            "w": 8,
            "h": 8,
            "pixel_string": "0000100000010100001010100010101000101010001010100001010000001000"
        }
    }
}

Here is a test animation I settled on for display:

The Pi Pico displaying a small ball bouncing around in a circle

At this point I also changed over to a Pi Pico 2 for when I make a game, mainly due to having had it arrive in the mail and I suppose I might as well work with the newer board that has more RAM!