Step 13 - More Types of Layers

For this example, we’ll switch to a different built-in map that has more layers we can do things with.

In our setup function, load this map instead of the one from Chapter 12:

self.tile_map = arcade.load_tilemap(":resources:tiled_maps/map2_level_1.json", scaling=TILE_SCALING, layer_options=layer_options)

You can run this and check out the map we will be working with this chapter. You’ll notice in addition to the normal platforms and coins we’ve had. We now have some extra signs and decoration objects, as well as a pit of lava.

Back in chapter 6 we made use of our setup function to reset the game. Let’s go ahead and use that system here to reset the game when the player touches the lava pit. You can remove the section for resetting when the Escape key is pressed if you want, or you can leave it in place. We can also play a game over sound when this happens.

Let’s first add a new sound to our __init__ function for this:

self.gameover_sound = arcade.load_sound(":resources:sounds/gameover1.wav")

In order to do this, we’ll add this code in our on_update function:

if arcade.check_for_collision_with_list(
    self.player_sprite, self.scene["Don't Touch"]
):
    arcade.play_sound(self.gameover_sound)
    self.setup()

The map we are using here has some extra layers in it we haven’t used yet. In the code above we made use of the Don't Touch to reset the game when the player touches it. In this section we will make use of two other layers in our new map, Background and Foreground.

We will use these layers as a way to separate objects that should be drawn in front of our player, and objects that should be drawn behind the player. In our setup function, before we create the player sprite, add this code.

self.scene.add_sprite_list_after("Player", "Foreground")

This code will cause our player spritelist to be inserted at a specific point in the Scene. Causing spritelists which are in front of it to be drawn before it, and ones behind it to be drawn after. By doing this we can make objects appear to be in front of or behind our player like the images below:

../../_images/13_foreground.png ../../_images/13_background.png

Source Code

More Layers
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.13_more_layers
  5"""
  6import arcade
  7
  8# Constants
  9WINDOW_WIDTH = 1280
 10WINDOW_HEIGHT = 720
 11WINDOW_TITLE = "Platformer"
 12
 13# Constants used to scale our sprites from their original size
 14TILE_SCALING = 0.5
 15COIN_SCALING = 0.5
 16
 17# Movement speed of player, in pixels per frame
 18PLAYER_MOVEMENT_SPEED = 5
 19GRAVITY = 1
 20PLAYER_JUMP_SPEED = 20
 21
 22
 23class GameView(arcade.Window):
 24    """
 25    Main application class.
 26    """
 27
 28    def __init__(self):
 29
 30        # Call the parent class and set up the window
 31        super().__init__(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
 32
 33        # Variable to hold our texture for our player
 34        self.player_texture = None
 35
 36        # Separate variable that holds the player sprite
 37        self.player_sprite = None
 38
 39        # Variable to hold our Tiled Map
 40        self.tile_map = None
 41
 42        # Replacing all of our SpriteLists with a Scene variable
 43        self.scene = None
 44
 45        # A variable to store our camera object
 46        self.camera = None
 47
 48        # A variable to store our gui camera object
 49        self.gui_camera = None
 50
 51        # This variable will store our score as an integer.
 52        self.score = 0
 53
 54        # This variable will store the text for score that we will draw to the screen.
 55        self.score_text = None
 56
 57        # Load sounds
 58        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 59        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 60        self.gameover_sound = arcade.load_sound(":resources:sounds/gameover1.wav")
 61
 62    def setup(self):
 63        """Set up the game here. Call this function to restart the game."""
 64        layer_options = {
 65            "Platforms": {
 66                "use_spatial_hash": True
 67            }
 68        }
 69
 70        # Load our TileMap
 71        self.tile_map = arcade.load_tilemap(
 72            ":resources:tiled_maps/map2_level_1.json",
 73            scaling=TILE_SCALING,
 74            layer_options=layer_options,
 75        )
 76
 77        # Create our Scene Based on the TileMap
 78        self.scene = arcade.Scene.from_tilemap(self.tile_map)
 79
 80        self.player_texture = arcade.load_texture(
 81            ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
 82        )
 83
 84        # Add Player Spritelist before "Foreground" layer. This will make the foreground
 85        # be drawn after the player, making it appear to be in front of the Player.
 86        # Setting before using scene.add_sprite allows us to define where the SpriteList
 87        # will be in the draw order. If we just use add_sprite, it will be appended to the
 88        # end of the order.
 89        self.scene.add_sprite_list_after("Player", "Foreground")
 90
 91        self.player_sprite = arcade.Sprite(self.player_texture)
 92        self.player_sprite.center_x = 128
 93        self.player_sprite.center_y = 128
 94        self.scene.add_sprite("Player", self.player_sprite)
 95
 96        # Create a Platformer Physics Engine, this will handle moving our
 97        # player as well as collisions between the player sprite and
 98        # whatever SpriteList we specify for the walls.
 99        # It is important to supply static to the walls parameter. There is a
100        # platforms parameter that is intended for moving platforms.
101        # If a platform is supposed to move, and is added to the walls list,
102        # it will not be moved.
103        self.physics_engine = arcade.PhysicsEnginePlatformer(
104            self.player_sprite, walls=self.scene["Platforms"], gravity_constant=GRAVITY
105        )
106
107        # Initialize our camera, setting a viewport the size of our window.
108        self.camera = arcade.camera.Camera2D()
109
110        # Initialize our gui camera, initial settings are the same as our world camera.
111        self.gui_camera = arcade.camera.Camera2D()
112
113        # Reset our score to 0
114        self.score = 0
115
116        # Initialize our arcade.Text object for score
117        self.score_text = arcade.Text(f"Score: {self.score}", x=0, y=5)
118
119        self.background_color = arcade.csscolor.CORNFLOWER_BLUE
120
121    def on_draw(self):
122        """Render the screen."""
123
124        # Clear the screen to the background color
125        self.clear()
126
127        # Activate our camera before drawing
128        self.camera.use()
129
130        # Draw our Scene
131        self.scene.draw()
132
133        # Activate our GUI camera
134        self.gui_camera.use()
135
136        # Draw our Score
137        self.score_text.draw()
138
139    def on_update(self, delta_time):
140        """Movement and Game Logic"""
141
142        # Move the player using our physics engine
143        self.physics_engine.update()
144
145        # See if we hit any coins
146        coin_hit_list = arcade.check_for_collision_with_list(
147            self.player_sprite, self.scene["Coins"]
148        )
149
150        # Loop through each coin we hit (if any) and remove it
151        for coin in coin_hit_list:
152            # Remove the coin
153            coin.remove_from_sprite_lists()
154            arcade.play_sound(self.collect_coin_sound)
155            self.score += 75
156            self.score_text.text = f"Score: {self.score}"
157
158        if arcade.check_for_collision_with_list(
159            self.player_sprite, self.scene["Don't Touch"]
160        ):
161            arcade.play_sound(self.gameover_sound)
162            self.setup()
163
164        # Center our camera on the player
165        self.camera.position = self.player_sprite.position
166
167    def on_key_press(self, key, modifiers):
168        """Called whenever a key is pressed."""
169
170        if key == arcade.key.ESCAPE:
171            self.setup()
172
173        if key == arcade.key.UP or key == arcade.key.W:
174            if self.physics_engine.can_jump():
175                self.player_sprite.change_y = PLAYER_JUMP_SPEED
176                arcade.play_sound(self.jump_sound)
177
178        if key == arcade.key.LEFT or key == arcade.key.A:
179            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
180        elif key == arcade.key.RIGHT or key == arcade.key.D:
181            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
182
183    def on_key_release(self, key, modifiers):
184        """Called whenever a key is released."""
185
186        if key == arcade.key.LEFT or key == arcade.key.A:
187            self.player_sprite.change_x = 0
188        elif key == arcade.key.RIGHT or key == arcade.key.D:
189            self.player_sprite.change_x = 0
190
191
192def main():
193    """Main function"""
194    window = GameView()
195    window.setup()
196    arcade.run()
197
198
199if __name__ == "__main__":
200    main()