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.root_group = 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.
# 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.root_group = 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.
Page last edited January 22, 2025
Text editor powered by tinymce.