Parallax#

Warning

This example is experimental!

The features it uses may change rapidly or be replaced in future arcade releases!

Screen shot of a car which drives over a multi-layer parallax scrolling background.
background_parallax.py#
  1"""
  2Parallax scrolling layers move slower the "farther" away they are.
  3
  4Use the right and left arrow keys to move the car.
  5
  6Arcade's ParallaxGroup allows you to implement this technique quickly
  7to create more satisfying backgrounds to your games. The example below
  8demonstrates how to fake an endless world by adjusting ParallaxGroup's
  9position & offset values. For limited worlds or backgrounds, limit the
 10repositioning to only occur within certain bounds, or delete it.
 11
 12If Python and Arcade are installed, this example can be run from the command line with:
 13python -m arcade.examples.background_parallax
 14"""
 15
 16import arcade
 17import arcade.experimental.background as background
 18
 19
 20SCREEN_TITLE = "Background Group Example"
 21SCREEN_WIDTH = 800
 22
 23# How much we'll scale up our pixel art
 24PIXEL_SCALE = 3
 25
 26# The original & scaled heights of our background layer image data in pixels.
 27ORIGINAL_BG_LAYER_HEIGHT_PX = 240
 28SCALED_BG_LAYER_HEIGHT_PX = ORIGINAL_BG_LAYER_HEIGHT_PX * PIXEL_SCALE
 29
 30
 31PLAYER_SPEED = 300  # The player's speed in pixels / second
 32
 33
 34class MyGame(arcade.Window):
 35    def __init__(self):
 36        super().__init__(SCREEN_WIDTH, SCALED_BG_LAYER_HEIGHT_PX, SCREEN_TITLE, resizable=True)
 37
 38        # Set the background color to match the sky in the background images
 39        self.background_color = (162, 84, 162, 255)
 40
 41        self.camera = arcade.SimpleCamera()
 42
 43        # Create a background group to hold all the landscape's layers
 44        self.backgrounds = background.ParallaxGroup()
 45
 46        # Calculate the current size of each background fill layer in pixels
 47        bg_layer_size_px = (SCREEN_WIDTH, SCALED_BG_LAYER_HEIGHT_PX)
 48
 49        # Import the image data for each background layer.
 50        # Unlike sprites, the scale argument doesn't resize the layer
 51        # itself. Instead, it changes the zoom level, while depth
 52        # controls how fast each layer scrolls. This means you have to
 53        # pass a correct size value when adding a layer. We calculated
 54        # this above.
 55        self.backgrounds.add_from_file(
 56            ":resources:/images/miami_synth_parallax/layers/back.png",
 57            size=bg_layer_size_px,
 58            depth=10.0,
 59            scale=PIXEL_SCALE
 60        )
 61        self.backgrounds.add_from_file(
 62            ":resources:/images/miami_synth_parallax/layers/buildings.png",
 63            size=bg_layer_size_px,
 64            depth=5.0,
 65            scale=PIXEL_SCALE
 66        )
 67        self.backgrounds.add_from_file(
 68            ":resources:/images/miami_synth_parallax/layers/palms.png",
 69            size=bg_layer_size_px,
 70            depth=3.0,
 71            scale=PIXEL_SCALE
 72        )
 73        self.backgrounds.add_from_file(
 74            ":resources:/images/miami_synth_parallax/layers/highway.png",
 75            size=bg_layer_size_px,
 76            depth=1.0,
 77            scale=PIXEL_SCALE
 78        )
 79
 80        # Create & position the player sprite in the center of the camera's view
 81        self.player_sprite = arcade.Sprite(
 82            ":resources:/images/miami_synth_parallax/car/car-idle.png",
 83            center_x=self.camera.viewport_width // 2, scale=PIXEL_SCALE
 84        )
 85        self.player_sprite.bottom = 0
 86
 87        # Track the player's x velocity
 88        self.x_velocity = 0
 89
 90    def pan_camera_to_player(self):
 91        # Move the camera toward the center of the player's sprite
 92        target_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
 93        self.camera.move_to((target_x, 0.0), 0.1)
 94
 95    def on_update(self, delta_time: float):
 96        # Move the player in our infinite world
 97        self.player_sprite.center_x += self.x_velocity * delta_time
 98        self.pan_camera_to_player()
 99
100    def on_draw(self):
101
102        # Set up our drawing
103        self.clear()
104        self.camera.use()
105
106        # Store a reference to the background layers as shorthand
107        bg = self.backgrounds
108
109        # Fake an endless world with scrolling terrain
110        # Try experimenting with commenting out 1 or both of the 2 lines
111        # below to get an intuitive understanding of what each does!
112        bg.offset = self.camera.position  # Fake depth by moving layers
113        bg.pos = self.camera.position  # Follow the car to fake infinity
114
115        # Draw the background & the player's car
116        bg.draw()
117        self.player_sprite.draw(pixelated=True)
118
119    def update_car_direction(self):
120        """
121        Don't use the trick below in a real game!
122
123        It will cause problems! Instead, use different textures, either
124        from different files or by using Texture.flop_left_to_right().
125        """
126        if self.x_velocity < 0:
127            self.player_sprite.scale_xy = (-PIXEL_SCALE, PIXEL_SCALE)
128            print(self.player_sprite.width)
129        elif self.x_velocity > 0:
130            self.player_sprite.scale_xy = (PIXEL_SCALE, PIXEL_SCALE)
131
132    def on_key_press(self, symbol: int, modifiers: int):
133        if symbol == arcade.key.LEFT:
134            self.x_velocity -= PLAYER_SPEED
135            self.update_car_direction()
136        elif symbol == arcade.key.RIGHT:
137            self.x_velocity += PLAYER_SPEED
138            self.update_car_direction()
139
140    def on_key_release(self, symbol: int, modifiers: int):
141        if symbol == arcade.key.LEFT:
142            self.x_velocity += PLAYER_SPEED
143            self.update_car_direction()
144        elif symbol == arcade.key.RIGHT:
145            self.x_velocity -= PLAYER_SPEED
146            self.update_car_direction()
147
148    def on_resize(self, width: int, height: int):
149        super().on_resize(width, height)
150        self.camera.resize(width, height)
151        full_width_size = (width, SCALED_BG_LAYER_HEIGHT_PX)
152
153        # We can iterate through a background group,
154        # but in the case of a parallax group the iter returns
155        # both the Backgrounds and the depths. (tuple[Background, float])
156        for layer, depth in self.backgrounds:
157            layer.size = full_width_size
158
159
160def main():
161    app = MyGame()
162    app.run()
163
164
165if __name__ == "__main__":
166    main()