OK, let's get fancy with those flakes. Allowing for custom flakes is a perfect use of creating a "sprite sheet" to hold the flake shapes, as discussed here.

This comes at the cost of requiring you to actually create this sprite sheet. You'll also need to modify a few lines of code to provide some info about the sprite sheet layout. But with this feature, along with a settable background image, you can really have fun customizing your snow globe. We'll provide a few examples to get you started.

Here's the fancy version of the snow globe code:

# SPDX-FileCopyrightText: 2019 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT

from random import randrange
import board
import busio
import displayio
from adafruit_gizmo import tft_gizmo
import adafruit_imageload
import adafruit_lis3dh

#---| User Config |---------------
BACKGROUND = "/blinka_dark.bmp"    # specify color or background BMP file

NUM_FLAKES = 50                    # total number of snowflakes
FLAKE_SHEET = "/flakes_sheet.bmp"  # flake sprite sheet
FLAKE_WIDTH = 4                    # sprite width
FLAKE_HEIGHT = 4                   # sprite height
FLAKE_TRAN_COLOR = 0x000000        # transparency color

SNOW_COLOR = 0xFFFFFF              # snow color

SHAKE_THRESHOLD = 27               # shake sensitivity, lower=more sensitive
#---| User Config |---------------

# Accelerometer setup
accelo_i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
accelo = adafruit_lis3dh.LIS3DH_I2C(accelo_i2c, address=0x19)

# Create the TFT Gizmo display
display = tft_gizmo.TFT_Gizmo()

# Load background image
try:
    bg_bitmap, bg_palette = adafruit_imageload.load(BACKGROUND,
                                                    bitmap=displayio.Bitmap,
                                                    palette=displayio.Palette)
# Or just use solid color
except (OSError, TypeError, AttributeError):
    BACKGROUND = BACKGROUND if isinstance(BACKGROUND, int) else 0x000000
    bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
    bg_palette = displayio.Palette(1)
    bg_palette[0] = BACKGROUND
background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)


# Snowflake setup
flake_bitmap, flake_palette = adafruit_imageload.load(FLAKE_SHEET,
                                                      bitmap=displayio.Bitmap,
                                                      palette=displayio.Palette)
if FLAKE_TRAN_COLOR is not None:
    for i, color in enumerate(flake_palette):
        if color == FLAKE_TRAN_COLOR:
            flake_palette.make_transparent(i)
            break
NUM_SPRITES = flake_bitmap.width // FLAKE_WIDTH * flake_bitmap.height // FLAKE_HEIGHT
flake_pos = [0.0] * NUM_FLAKES
flakes = displayio.Group()
for _ in range(NUM_FLAKES):
    flakes.append(displayio.TileGrid(flake_bitmap, pixel_shader=flake_palette,
                                     width = 1,
                                     height = 1,
                                     tile_width = FLAKE_WIDTH,
                                     tile_height = FLAKE_HEIGHT,
                                     x = randrange(0, display.width),
                                     default_tile=randrange(0, NUM_SPRITES)))

# Snowfield setup
snow_depth = [display.height] * display.width
snow_palette = displayio.Palette(2)
snow_palette[0] = 0xADAF00   # transparent color
snow_palette[1] = SNOW_COLOR # snow color
snow_palette.make_transparent(0)
snow_bitmap = displayio.Bitmap(display.width, display.height, len(snow_palette))
snow = displayio.TileGrid(snow_bitmap, pixel_shader=snow_palette)

# Add everything to display
splash = displayio.Group()
splash.append(background)
splash.append(flakes)
splash.append(snow)
display.root_group = splash

def clear_the_snow():
    #pylint: disable=global-statement, redefined-outer-name
    global flakes, flake_pos, snow_depth
    display.auto_refresh = False
    for flake in flakes:
        # set to a random sprite
        flake[0] = randrange(0, NUM_SPRITES)
        # set to a random x location
        flake.x = randrange(0, display.width)
    # set random y locations, off screen to start
    flake_pos = [-1.0*randrange(0, display.height) for _ in range(NUM_FLAKES)]
    # reset snow level
    snow_depth = [display.height] * display.width
    # and snow bitmap
    for i in range(display.width*display.height):
        snow_bitmap[i] = 0
    display.auto_refresh = True

def add_snow(index, amount, steepness=2):
    location = []
    # local steepness check
    for x in range(index - amount, index + amount):
        add = False
        if x == 0:
            # check depth to right
            if snow_depth[x+1] - snow_depth[x] < steepness:
                add = True
        elif x == display.width - 1:
            # check depth to left
            if snow_depth[x-1] - snow_depth[x] < steepness:
                add = True
        elif 0 < x < display.width - 1:
            # check depth to left AND right
            if snow_depth[x-1] - snow_depth[x] < steepness and \
               snow_depth[x+1] - snow_depth[x] < steepness:
                add = True
        if add:
            location.append(x)
    # add where snow is not too steep
    for x in location:
        new_level = snow_depth[x] - 1
        if new_level >= 0:
            snow_depth[x] = new_level
            snow_bitmap[x, new_level] = 1

while True:
    clear_the_snow()
    # loop until globe is full of snow
    while snow_depth.count(0) < display.width:
        # check for shake
        if accelo.shake(SHAKE_THRESHOLD, 5, 0):
            break
        # update snowflakes
        for i, flake in enumerate(flakes):
            # speed based on sprite index
            flake_pos[i] += 1 - flake[0] / NUM_SPRITES
            # check if snowflake has hit the ground
            if flake_pos[i] >= snow_depth[flake.x]:
                # add snow where it fell
                add_snow(flake.x, FLAKE_WIDTH)
                # reset flake to top
                flake_pos[i] = 0
                # at a new x location
                flake.x = randrange(0, display.width)
            flake.y = int(flake_pos[i])
        display.refresh()

As you can see, there are more items in the customization section:

#---| User Config |---------------
BACKGROUND = "/blinka_dark.bmp"    # specify color or background BMP file

NUM_FLAKES = 50                    # total number of snowflakes
FLAKE_SHEET = "/flakes_sheet.bmp"  # flake sprite sheet
FLAKE_WIDTH = 4                    # sprite width
FLAKE_HEIGHT = 4                   # sprite height
FLAKE_TRAN_COLOR = 0x000000        # transparency color

SNOW_COLOR = 0xFFFFFF              # snow color

SHAKE_THRESHOLD = 27               # shake sensitivity, lower=more sensitive
#---| User Config |---------------

Most of these are the same as the simple version in the previous section. The new ones are for specifying a flake sprite sheet. They are:

  • FLAKE_SHEET - the BMP file containing the flake sprites
  • FLAKE_WIDTH - the width of each sprite
  • FLAKE_HEIGHT - the height of each sprite
  • FLAKE_TRAN_COLOR - the color to use as transparency

For FLAKE_TRAN_COLOR, it's best to just pick something simple when creating your sprite sheet bitmap. Don't use one of the colors in the flake itself, since then it wouldn't show up.

The two that are probably the most confusing are FLAKE_WIDTH and FLAKE_HEIGHT. You can think of these as the width and height of each of your flakes. These should be the same for each flake. It's not the total width and height of your flake sheet - just the sprites themselves. For a more in depth discussion see here and here.

Background Image

To start out, the code is reusing the background image from the previous section of the guide. Download it from the link there. Be sure to have the file blinka_dark.bmp in your CIRCUITPY folder.

Simple Snowglobe Redux

Let's start by simply recreating the same flakes from the previous section, but this time by using a sprite sheet. Here's the file:

Download that and copy it to your CIRCUITPY folder.

If you open it in an image viewer, you won't see much since it's so small. But zoomed in, it looks something like this:

The border and grids have been added for reference. They aren't part of the actual file. You can see how there are 3 different 4x4 bitmaps. We need to tell the code this, which is what these lines do:

FLAKE_WIDTH = 4                    # sprite width
FLAKE_HEIGHT = 4                   # sprite height

With the background (blinka_dark.bmp) and flake sprite sheet (flake_sheet.bmp) files copied to your CIRCUITPY folder, you can try running the code above. You should get the same as before - a Blinka background with simple white snow flakes.

Now let's try something different.

Who Watches The...Squids?

This example better shows how you can customize the snow globe. Hmmm. What else can we make fall from the sky. How about squids? Sure. And with a background that matches the reference. Grab these two files:

and save to your CIRCUITPY folder.

Then, change the customization settings to this:

#---| User Config |---------------
BACKGROUND = "/watchmen_bg.bmp"    # specify color or background BMP file

NUM_FLAKES = 20                    # total number of snowflakes
FLAKE_SHEET = "/squid_sheet_16.bmp"  # flake sprite sheet
FLAKE_WIDTH = 16                   # sprite width
FLAKE_HEIGHT = 16                  # sprite height
FLAKE_TRAN_COLOR = 0x000000        # transparency color

SNOW_COLOR = 0x279ED5              # snow color

SHAKE_THRESHOLD = 27               # shake sensitivity, lower=more sensitive
#---| User Config |---------------

The background image is just another BMP file. Other than that, nothing new.

The sprite sheet is pretty different though. The flakes are larger - 16 x 16. They also contain multiple colors. Zoomed in, it looks something like this:

The total number of flakes is reduced a bit, since these are larger flakes. And to make the snow match the squids, it is set to a blue-ish color.

With everything copied over and the other changes in place, run the code again and you should get:

Falling squids!

Have Fun!

Have fun with this flake customization feature. Like maybe have cat and dog flakes? Or maybe frogs? Falling hearts might be nice. Or just fancier snow flakes. Up to you.

What else can fall from the sky? Here's one more for you. But you'll have to load these up to see...

#---| User Config |---------------
BACKGROUND = "/wg_bg.bmp"    # specify color or background BMP file

NUM_FLAKES = 20                    # total number of snowflakes
FLAKE_SHEET = "/wg_sheet.bmp"  # flake sprite sheet
FLAKE_WIDTH = 20                   # sprite width
FLAKE_HEIGHT = 20                  # sprite height
FLAKE_TRAN_COLOR = 0x000000        # transparency color

SNOW_COLOR = 0xFF00FF              # snow color

SHAKE_THRESHOLD = 27               # shake sensitivity, lower=more sensitive
#---| User Config |---------------

This guide was first published on Nov 02, 2019. It was last updated on Mar 28, 2024.

This page (Fancy Snow Globe) was last updated on Mar 28, 2024.

Text editor powered by tinymce.