Map File
The game will load the maps from CSV (Comma Separated Value) files. We can edit these with any spreadsheet program. Here is an example of a map:
Any blank cells will get treated as empty tiles that can't be walked on. Every cell that isn't empty must have an entry in the main TILES
dictionary which is located inside of tilegame_assets\tiles.py
.
Loading The Map
To load the CSV map we start by loop over each row. split(',')
breaks the row down into a list which we will also loop over. On each item within the row, we look it up in the TILES dictionary. Non-entities get added to the ORIGINAL_MAP
and CURRENT_MAP
game state objects.
The enumerate()
function is used on the loops to easily track x
and y
coordinates so we know where to add tiles to the map objects.
Entities also get added to the ENTITY_SPRITES_DICT
game state object. Their sprites are loaded and added to the sprite_group
so they'll be drawn on the screen. In the map objects a "floor"
tile is placed at the location of the entity. The entity will be drawn on top of the floor.
Here is the code that loads the map:
def load_map(file_name): # pylint: disable=global-statement,too-many-statements,too-many-nested-blocks,too-many-branches global ENTITY_SPRITES, CAMERA_VIEW # empty the sprites from the group for cur_s in ENTITY_SPRITES: group.remove(cur_s) # remove player sprite try: group.remove(GAME_STATE["PLAYER_SPRITE"]) except ValueError: pass # reset map and other game state objects GAME_STATE["ORIGINAL_MAP"] = {} GAME_STATE["CURRENT_MAP"] = {} ENTITY_SPRITES = [] GAME_STATE["ENTITY_SPRITES_DICT"] = {} CAMERA_VIEW = {} GAME_STATE["INVENTORY"] = [] GAME_STATE["TOTAL_HEARTS"] = 0 # Open and read raw string from the map csv file f = open("tilegame_assets/{}".format(file_name), "r") map_csv_str = f.read() f.close() # split the raw string into lines map_csv_lines = map_csv_str.replace("\r", "").split("\n") # set the WIDTH and HEIGHT variables. # this assumes the map is rectangular. GAME_STATE["MAP_HEIGHT"] = len(map_csv_lines) GAME_STATE["MAP_WIDTH"] = len(map_csv_lines[0].split(",")) # loop over each line storing index in y variable for y, line in enumerate(map_csv_lines): # ignore empty line if line != "": # loop over each tile type separated by commas, storing index in x variable for x, tile_name in enumerate(line.split(",")): print("%s '%s'" % (len(tile_name), str(tile_name))) # if the tile exists in our main dictionary if tile_name in TILES.keys(): # if the tile is an entity if ( "entity" in TILES[tile_name].keys() and TILES[tile_name]["entity"] ): # set the map tiles to floor GAME_STATE["ORIGINAL_MAP"][x, y] = "floor" GAME_STATE["CURRENT_MAP"][x, y] = "floor" if tile_name == "heart": GAME_STATE["TOTAL_HEARTS"] += 1 # if it's the player if tile_name == "player": # Create the sprite TileGrid GAME_STATE["PLAYER_SPRITE"] = displayio.TileGrid( sprite_sheet, pixel_shader=palette, width=1, height=1, tile_width=16, tile_height=16, default_tile=TILES[tile_name]["sprite_index"], ) # set the position of sprite on screen GAME_STATE["PLAYER_SPRITE"].x = x * 16 GAME_STATE["PLAYER_SPRITE"].y = y * 16 # set position in x,y tile coords for reference later GAME_STATE["PLAYER_LOC"] = (x, y) # add sprite to the group group.append(GAME_STATE["PLAYER_SPRITE"]) else: # not the player # Create the sprite TileGrid entity_srite = displayio.TileGrid( sprite_sheet, pixel_shader=palette, width=1, height=1, tile_width=16, tile_height=16, default_tile=TILES[tile_name]["sprite_index"], ) # set the position of sprite on screen # default to off the edge entity_srite.x = -16 entity_srite.y = -16 # add the sprite object to ENTITY_SPRITES list ENTITY_SPRITES.append(entity_srite) # print("setting GAME_STATE['ENTITY_SPRITES_DICT'][%s,%s]" % (x,y)) # create an entity obj _entity_obj = { "entity_sprite_index": len(ENTITY_SPRITES) - 1, "map_tile_name": tile_name, } # if there are no entities at this location yet if (x, y) not in GAME_STATE["ENTITY_SPRITES_DICT"]: # create a list and add it to the dictionary at the x,y location GAME_STATE["ENTITY_SPRITES_DICT"][x, y] = [_entity_obj] else: # append the entity to the existing list in the dictionary GAME_STATE["ENTITY_SPRITES_DICT"][x, y].append( _entity_obj ) else: # tile is not entity # set the tile_name into MAP dictionaries GAME_STATE["ORIGINAL_MAP"][x, y] = tile_name GAME_STATE["CURRENT_MAP"][x, y] = tile_name else: # tile type wasn't found in dict print("tile: %s not found in TILES dict" % tile_name) # add all entity sprites to the group print("appending {} sprites".format(len(ENTITY_SPRITES))) for entity in ENTITY_SPRITES: group.append(entity)
Tile Types
These entries define the look and behavior of the tiles. Here is an example of a tile type entry:
# ... behavior functions declared above... TILES = { # ... many tile type entries ... "mho": { "sprite_index": 2, "can_walk": True, "entity": True, "before_move": take_item }, # ... more entries ... }
The keys within the TILES
dictionary match up with the values from the map CSV file. One of the mho
tiles is located at cell D3
. There may be tile keys that aren't present in a particular map, they will have no effect when playing that map level in the game.
These entries define the look and behavior of tiles using the following properties:
-
sprite_index
- The index within the sprite sheet to show for this tile. -
can_walk
- Whether the player is allowed to walk on this tile. -
entity
- Whether this tile represents an entity. If so it will get drawn on top of a floor tile and theoretically it could move around the map. The player is an entity as well as Mho, Heart, Minerva, and Robot. However the player is the only entity in the example game that moves. -
before_move
- (Optional) If set to a function the function will get called when the player tries to walk on this tile. If the function returnsTrue
the player will be allowed to walk on the tile, if the function returnsFalse
the player will not be allowed to walk on it.
Behavior Functions
These functions must be declared before the main TILES dictionary because tiles that want special behaviors will get them by setting the before_move
property in their tile type entry to a behavior function. These are also declared inside the file: tilegame_assets\tiles.py. The existing behavior functions are:
-
take_item
- if the item is still here then add it to the player's inventory and remove it from this tile. In the example game both"mho"
and"heart"
tile types use this function. -
sparky_walk
- if the player has a"Mho"
in their inventory it gets consumed and the Sparky is removed from this tile. If they player has no"Mho"
they let the smoke out and must restart this level. -
minerva_walk
- change the game state toSTATE_MINERVA
in this state Minerva will show the player a fun fact and prompt them to press a button to continue. This function always returnsFalse
so the player is never allowed to actually move on to this tile. The fun facts are stored as a list inside he file: tilegame_assets\fun_facts.py. If you add your own facts to this list Minerva will show them to the player sometimes. -
robot_walk
- if the player has gathered all of the hearts from this map then they are allowed to move to the robot, and they win this level. Game state is changed toSTATE_MAPWIN
. If they have not gathered all available hearts then they are not allowed to move to this tile.
You can declare your own tile types and behavior functions in this file to customize your game!
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries # # SPDX-License-Identifier: MIT from tilegame_assets.states import ( STATE_MAPWIN, STATE_LOST_SPARKY, STATE_MINERVA, ) # pylint: disable=unused-argument # Minerva before_move. Set game state to STATE_MINERVA def minerva_walk(to_coords, from_coords, entity_obj, GAME_STATE): GAME_STATE["STATE"] = STATE_MINERVA return False # Sparky before_move. If user does not have a Mho in inventory they lose. # If user does have Mho subtract one from inventory and consume Sparky. def sparky_walk(to_coords, from_coords, entity_obj, GAME_STATE): if GAME_STATE["INVENTORY"].count("mho") > 0: GAME_STATE["INVENTORY"].remove("mho") GAME_STATE["ENTITY_SPRITES_DICT"][to_coords].remove(entity_obj) if len(GAME_STATE["ENTITY_SPRITES_DICT"][to_coords]) == 0: del GAME_STATE["ENTITY_SPRITES_DICT"][to_coords] if (-1, -1) in GAME_STATE["ENTITY_SPRITES_DICT"]: GAME_STATE["ENTITY_SPRITES_DICT"][-1, -1].append(entity_obj) else: GAME_STATE["ENTITY_SPRITES_DICT"][-1, -1] = [entity_obj] return True else: GAME_STATE["STATE"] = STATE_LOST_SPARKY return True # Robot before_move. If user has all Hearts they win the map. def robot_walk(to_coords, from_coords, entity_obj, GAME_STATE): if GAME_STATE["INVENTORY"].count("heart") == GAME_STATE["TOTAL_HEARTS"]: GAME_STATE["STATE"] = STATE_MAPWIN return True return False # Remove the item from this location and add it to player inventory. def take_item(to_coords, from_coords, entity_obj, GAME_STATE): print(entity_obj) GAME_STATE["INVENTORY"].append(entity_obj["map_tile_name"]) GAME_STATE["ENTITY_SPRITES_DICT"][to_coords].remove(entity_obj) if len(GAME_STATE["ENTITY_SPRITES_DICT"][to_coords]) == 0: del GAME_STATE["ENTITY_SPRITES_DICT"][to_coords] if (-1, -1) in GAME_STATE["ENTITY_SPRITES_DICT"]: GAME_STATE["ENTITY_SPRITES_DICT"][-1, -1].append(entity_obj) else: GAME_STATE["ENTITY_SPRITES_DICT"][-1, -1] = [entity_obj] return True # main dictionary that maps tile type strings to objects. # each one stores the sprite_sheet index and any necessary # behavioral stats like can_walk or before_move TILES = { # empty strings default to floor and no walk. "": {"sprite_index": 10, "can_walk": False}, "floor": {"sprite_index": 10, "can_walk": True}, "top_wall": {"sprite_index": 7, "can_walk": False}, "top_right_wall": {"sprite_index": 8, "can_walk": False}, "top_left_wall": {"sprite_index": 6, "can_walk": False}, "bottom_right_wall": {"sprite_index": 14, "can_walk": False}, "bottom_left_wall": {"sprite_index": 12, "can_walk": False}, "right_wall": {"sprite_index": 11, "can_walk": False}, "left_wall": {"sprite_index": 9, "can_walk": False}, "bottom_wall": {"sprite_index": 13, "can_walk": False}, "robot": { "sprite_index": 1, "can_walk": True, "entity": True, "before_move": robot_walk, }, "heart": { "sprite_index": 5, "can_walk": True, "entity": True, "before_move": take_item, }, "mho": { "sprite_index": 2, "can_walk": True, "entity": True, "before_move": take_item, }, "sparky": { "sprite_index": 4, "can_walk": True, "entity": True, "before_move": sparky_walk, }, "minerva": { "sprite_index": 3, "can_walk": True, "entity": True, "before_move": minerva_walk, }, "player": {"sprite_index": 0, "entity": True,}, }
Page last edited January 22, 2025
Text editor powered by tinymce.