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.future.background as background
 18
 19# How much we'll scale up our pixel art
 20PIXEL_SCALE = 3
 21
 22# The original & scaled heights of our background layer image data in pixels.
 23ORIGINAL_BG_LAYER_HEIGHT_PX = 240
 24SCALED_BG_LAYER_HEIGHT_PX = ORIGINAL_BG_LAYER_HEIGHT_PX * PIXEL_SCALE
 25
 26
 27WINDOW_TITLE = "Background Group Example"
 28WINDOW_WIDTH = 1280
 29WINDOW_HEIGHT = SCALED_BG_LAYER_HEIGHT_PX
 30
 31
 32PLAYER_SPEED = 300  # The player's speed in pixels / second
 33CAMERA_SPEED = 0.1
 34
 35
 36class GameView(arcade.View):
 37    def __init__(self):
 38        super().__init__()
 39
 40        # Set the background color to match the sky in the background images
 41        self.background_color = (162, 84, 162, 255)
 42
 43        self.camera = arcade.camera.Camera2D()
 44
 45        # Create a background group to hold all the landscape's layers
 46        self.backgrounds = background.ParallaxGroup()
 47
 48        # Calculate the current size of each background fill layer in pixels
 49        bg_layer_size_px = (WINDOW_WIDTH, SCALED_BG_LAYER_HEIGHT_PX)
 50
 51        # Import the image data for each background layer.
 52        # Unlike sprites, the scale argument doesn't resize the layer
 53        # itself. Instead, it changes the zoom level, while depth
 54        # controls how fast each layer scrolls. This means you have to
 55        # pass a correct size value when adding a layer. We calculated
 56        # this above.
 57        self.backgrounds.add_from_file(
 58            ":resources:/images/miami_synth_parallax/layers/back.png",
 59            size=bg_layer_size_px,
 60            depth=10.0,
 61            scale=PIXEL_SCALE
 62        )
 63        self.backgrounds.add_from_file(
 64            ":resources:/images/miami_synth_parallax/layers/buildings.png",
 65            size=bg_layer_size_px,
 66            depth=5.0,
 67            scale=PIXEL_SCALE
 68        )
 69        self.backgrounds.add_from_file(
 70            ":resources:/images/miami_synth_parallax/layers/palms.png",
 71            size=bg_layer_size_px,
 72            depth=3.0,
 73            scale=PIXEL_SCALE
 74        )
 75        self.backgrounds.add_from_file(
 76            ":resources:/images/miami_synth_parallax/layers/highway.png",
 77            size=bg_layer_size_px,
 78            depth=1.0,
 79            scale=PIXEL_SCALE
 80        )
 81
 82        # Load the car texture and create a flipped version
 83        self.car_texture_right = arcade.texture.load_texture(
 84            ":resources:/images/miami_synth_parallax/car/car-idle.png")
 85        self.car_texture_left = self.car_texture_right.flip_left_right()
 86
 87        # Create & position the player sprite in the center of the camera's view
 88        self.player_sprite = arcade.Sprite(
 89            self.car_texture_right,
 90            center_x=self.camera.viewport_width // 2, center_y=-200.0, scale=PIXEL_SCALE
 91        )
 92
 93        self.player_sprite.bottom = 0
 94
 95        # Track the player's x velocity
 96        self.x_velocity = 0
 97
 98    def pan_camera_to_player(self):
 99        # Move the camera toward the center of the player's sprite
100        target_x = self.player_sprite.center_x
101        self.camera.position = arcade.math.lerp_2d(
102            self.camera.position,
103            (target_x, self.height//2),
104            CAMERA_SPEED
105        )
106
107    def on_update(self, delta_time: float):
108        # Move the player in our infinite world
109        self.player_sprite.center_x += self.x_velocity * delta_time
110        self.pan_camera_to_player()
111
112    def on_draw(self):
113
114        # Set up our drawing
115        self.clear()
116        with self.camera.activate():
117            # Store a reference to the background layers as shorthand
118            bg = self.backgrounds
119
120            # Fake an endless world with scrolling terrain
121            # Try experimenting with commenting out 1 or both of the 2 lines
122            # below to get an intuitive understanding of what each does!
123            bg.offset = self.camera.bottom_left  # Fake depth by moving layers
124            bg.pos = self.camera.bottom_left  # Follow the car to fake infinity
125
126            # Draw the background & the player's car
127            with self.window.ctx.enabled(self.window.ctx.BLEND):
128                bg.draw()
129                arcade.draw_sprite(self.player_sprite, pixelated=True)
130
131    def update_car_direction(self):
132        if self.x_velocity < 0:
133            self.player_sprite.texture = self.car_texture_left
134        elif self.x_velocity > 0:
135            self.player_sprite.texture = self.car_texture_right
136
137    def on_key_press(self, symbol: int, modifiers: int):
138        if symbol == arcade.key.LEFT:
139            self.x_velocity -= PLAYER_SPEED
140            self.update_car_direction()
141        elif symbol == arcade.key.RIGHT:
142            self.x_velocity += PLAYER_SPEED
143            self.update_car_direction()
144
145    def on_key_release(self, symbol: int, modifiers: int):
146        if symbol == arcade.key.LEFT:
147            self.x_velocity += PLAYER_SPEED
148            self.update_car_direction()
149        elif symbol == arcade.key.RIGHT:
150            self.x_velocity -= PLAYER_SPEED
151            self.update_car_direction()
152
153    def on_resize(self, width: int, height: int):
154        super().on_resize(width, height)
155        self.camera.match_window()
156        full_width_size = (width, SCALED_BG_LAYER_HEIGHT_PX)
157
158        # We can iterate through a background group,
159        # but in the case of a parallax group the iter returns
160        # both the Backgrounds and the depths. (tuple[Background, float])
161        for layer, depth in self.backgrounds:
162            layer.size = full_width_size
163
164
165def main():
166    """ Main function """
167    # Create a window class. This is what actually shows up on screen
168    window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, resizable=True)
169
170    # Create the GameView
171    game = GameView()
172
173    # Show GameView on screen
174    window.show_view(game)
175
176    # Start the arcade game loop
177    arcade.run()
178
179
180if __name__ == "__main__":
181    main()