Basic Rendering Code

If you are brand new to CircuitPython or displayio based programs, or even if it's just been a while, you should start by going over the Multiple Tilegrids page in the displayio guide. After you've done that then come back here.

To start, lets look at this example:

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

import board
import displayio
import adafruit_imageload

display = board.DISPLAY

# Load the sprite sheet (bitmap)
sprite_sheet, palette = adafruit_imageload.load(
    "tilegame_assets/castle_sprite_sheet.bmp",
    bitmap=displayio.Bitmap,
    palette=displayio.Palette,
)

# Create the sprite TileGrid
sprite = displayio.TileGrid(
    sprite_sheet,
    pixel_shader=palette,
    width=1,
    height=1,
    tile_width=16,
    tile_height=16,
    default_tile=0,
)

# Create the castle TileGrid
castle = displayio.TileGrid(
    sprite_sheet,
    pixel_shader=palette,
    width=10,
    height=8,
    tile_width=16,
    tile_height=16,
)

# Create a Group to hold the sprite and add it
sprite_group = displayio.Group()
sprite_group.append(sprite)

# Create a Group to hold the castle and add it
castle_group = displayio.Group(scale=1)
castle_group.append(castle)

# Create a Group to hold the sprite and castle
group = displayio.Group()

# Add the sprite and castle to the group
group.append(castle_group)
group.append(sprite_group)

# Castle tile assignments
# corners
castle[0, 0] = 3  # upper left
castle[9, 0] = 5  # upper right
castle[0, 7] = 9  # lower left
castle[9, 7] = 11  # lower right
# top / bottom walls
for x in range(1, 9):
    castle[x, 0] = 4  # top
    castle[x, 7] = 10  # bottom
# left/ right walls
for y in range(1, 7):
    castle[0, y] = 6  # left
    castle[9, y] = 8  # right
# floor
for x in range(1, 9):
    for y in range(1, 7):
        castle[x, y] = 7  # floor

# put the sprite somewhere in the castle
sprite.x = 16 * 4
sprite.y = 16 * 3

# Add the Group to the Display
display.show(group)
while True:
    pass

In this code we have two Groups, one for the castle and one for the sprite or player icon, Blinka. These groups each get a TileGrid created and added to them. The castle TileGrid is sized 10x8 and divides evenly into the 160x128 pixel screen with each tile being 16x16 pixels. It holds the tiles that make up the portion of the map visible currently. The sprite TileGrid is only 1x1 but does still use the same 16x16 pixel size. It holds the Blinka image for the player.

If you do you do not understand the basics of how this layout works take a look back at the Multiple Tilegrids page in the displayio guide. We will build on these concepts to create the rest of the game map. The first thing we'll do is add a movement system so we can make Blinka move around using the d-pad or joystick. 

Basic Movement Code

# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import board
import displayio
import adafruit_imageload

# from tilegame_assets.controls_helper import controls
import ugame

display = board.DISPLAY
player_loc = {"x": 4, "y": 3}

# Load the sprite sheet (bitmap)
sprite_sheet, palette = adafruit_imageload.load(
    "tilegame_assets/castle_sprite_sheet.bmp",
    bitmap=displayio.Bitmap,
    palette=displayio.Palette,
)

# Create the sprite TileGrid
sprite = displayio.TileGrid(
    sprite_sheet,
    pixel_shader=palette,
    width=1,
    height=1,
    tile_width=16,
    tile_height=16,
    default_tile=0,
)

# Create the castle TileGrid
castle = displayio.TileGrid(
    sprite_sheet,
    pixel_shader=palette,
    width=10,
    height=8,
    tile_width=16,
    tile_height=16,
)

# Create a Group to hold the sprite and add it
sprite_group = displayio.Group()
sprite_group.append(sprite)

# Create a Group to hold the castle and add it
castle_group = displayio.Group(scale=1)
castle_group.append(castle)

# Create a Group to hold the sprite and castle
group = displayio.Group()

# Add the sprite and castle to the group
group.append(castle_group)
group.append(sprite_group)

# Castle tile assignments
# corners
castle[0, 0] = 3  # upper left
castle[9, 0] = 5  # upper right
castle[0, 7] = 9  # lower left
castle[9, 7] = 11  # lower right
# top / bottom walls
for x in range(1, 9):
    castle[x, 0] = 4  # top
    castle[x, 7] = 10  # bottom
# left/ right walls
for y in range(1, 7):
    castle[0, y] = 6  # left
    castle[9, y] = 8  # right
# floor
for x in range(1, 9):
    for y in range(1, 7):
        castle[x, y] = 7  # floor

# put the sprite somewhere in the castle
sprite.x = 16 * player_loc["x"]
sprite.y = 16 * player_loc["y"]

# Add the Group to the Display
display.show(group)

prev_btn_vals = ugame.buttons.get_pressed()

while True:
    cur_btn_vals = ugame.buttons.get_pressed()
    if not prev_btn_vals & ugame.K_UP and cur_btn_vals & ugame.K_UP:
        player_loc["y"] = max(1, player_loc["y"] - 1)
    if not prev_btn_vals & ugame.K_DOWN and cur_btn_vals & ugame.K_DOWN:
        player_loc["y"] = min(6, player_loc["y"] + 1)

    if not prev_btn_vals & ugame.K_RIGHT and cur_btn_vals & ugame.K_RIGHT:
        player_loc["x"] = min(8, player_loc["x"] + 1)
    if not prev_btn_vals & ugame.K_LEFT and cur_btn_vals & ugame.K_LEFT:
        player_loc["x"] = max(1, player_loc["x"] - 1)

    # update the the player sprite position
    sprite.x = 16 * player_loc["x"]
    sprite.y = 16 * player_loc["y"]

    # update the previous values
    prev_btn_vals = cur_btn_vals

In the code above we have a variable player_loc that stores the coordinates of the player in tile coordinates. It's set to {"x":4, "y":3} by default. These correspond to the indexes of the tiles starting from the top left corner at {"x":0, "y":0}.

To handle player movement we will use a library called ugame. This helper module abstracts away the differences between different boards and leaves us with common signals to use for D-pad and other buttons.

The code above already contains the import for it, here is what it looks like:

import ugame

After it's been imported we can access the current button states with ugame.buttons.get_pressed() it will return a number which holds each button state accessible by anding with the button constants like this:

ugame.buttons.get_pressed() & ugame.K_UP # True if up btn is pressed.
ugame.buttons.get_pressed() & ugame.K_DOWN # True if down btn is pressed.
ugame.buttons.get_pressed() & ugame.K_RIGHT # True if right btn is pressed.
ugame.buttons.get_pressed() & ugame.K_LEFT # True if left btn is pressed.

We'll use these values in the main loop along with the prev_btn_vals to decide when we need to change the player_loc. Inside the main loop we update the sprite.x and sprite.y values to match the player_loc. This will make Blinka move around when the player presses D-pad buttons or uses the joystick. 

Update the code from above to use this main loop.

# Variable to old previous button state
prev_btn_vals = ugame.buttons.get_pressed()
while True:
    cur_btn_vals = ugame.buttons.get_pressed() # update button sate
    # if up button was pressed
    if not prev_btn_vals & ugame.K_UP and cur_btn_vals & ugame.K_UP:
        player_loc["y"] = max(1, player_loc["y"]-1)
    # if down button was pressed
    if not prev_btn_vals & ugame.K_DOWN and cur_btn_vals & ugame.K_DOWN:
        player_loc["y"] = min(6, player_loc["y"]+1)
    # if right button was pressed    
    if not prev_btn_vals & ugame.K_RIGHT and cur_btn_vals & ugame.K_RIGHT:
        player_loc["x"] = min(8, player_loc["x"]+1)
    # if left button was pressed    
    if not prev_btn_vals & ugame.K_LEFT and cur_btn_vals & ugame.K_LEFT:
        player_loc["x"] = max(1, player_loc["x"]-1)
        
    # update the the player sprite position
    sprite.x = 16 * player_loc["x"]
    sprite.y = 16 * player_loc["y"]
    
    # update the previous values
    prev_btn_vals = cur_btn_vals

Now we can use the D-pad or joystick to move Blinka around!

Next we will change the graphics to get rid of the white square around Blinka.

This guide was first published on Jun 10, 2020. It was last updated on Mar 21, 2020.

This page (Rendering and Movement) was last updated on Jun 05, 2023.

Text editor powered by tinymce.