Parallax#

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
 16from __future__ import annotations
 17
 18import arcade
 19import arcade.background as background
 20
 21
 22SCREEN_TITLE = "Background Group Example"
 23SCREEN_WIDTH = 800
 24
 25# How much we'll scale up our pixel art
 26PIXEL_SCALE = 3
 27
 28# The original & scaled heights of our background layer image data in pixels.
 29ORIGINAL_BG_LAYER_HEIGHT_PX = 240
 30SCALED_BG_LAYER_HEIGHT_PX = ORIGINAL_BG_LAYER_HEIGHT_PX * PIXEL_SCALE
 31
 32
 33PLAYER_SPEED = 300  # The player's speed in pixels / second
 34
 35
 36class MyGame(arcade.Window):
 37    def __init__(self):
 38        super().__init__(SCREEN_WIDTH, SCALED_BG_LAYER_HEIGHT_PX, SCREEN_TITLE, resizable=True)
 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.SimpleCamera()
 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 = (SCREEN_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        # Create & position the player sprite in the center of the camera's view
 83        self.player_sprite = arcade.Sprite(
 84            ":resources:/images/miami_synth_parallax/car/car-idle.png",
 85            center_x=self.camera.viewport_width // 2, scale=PIXEL_SCALE
 86        )
 87        self.player_sprite.bottom = 0
 88
 89        # Track the player's x velocity
 90        self.x_velocity = 0
 91
 92    def pan_camera_to_player(self):
 93        # Move the camera toward the center of the player's sprite
 94        target_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
 95        self.camera.move_to((target_x, 0.0), 0.1)
 96
 97    def on_update(self, delta_time: float):
 98        # Move the player in our infinite world
 99        self.player_sprite.center_x += self.x_velocity * delta_time
100        self.pan_camera_to_player()
101
102    def on_draw(self):
103
104        # Set up our drawing
105        self.clear()
106        self.camera.use()
107
108        # Store a reference to the background layers as shorthand
109        bg = self.backgrounds
110
111        # Fake an endless world with scrolling terrain
112        # Try experimenting with commenting out 1 or both of the 2 lines
113        # below to get an intuitive understanding of what each does!
114        bg.offset = self.camera.position  # Fake depth by moving layers
115        bg.pos = self.camera.position  # Follow the car to fake infinity
116
117        # Draw the background & the player's car
118        bg.draw()
119        self.player_sprite.draw(pixelated=True)
120
121    def update_car_direction(self):
122        """
123        Don't use the trick below in a real game!
124
125        It will cause problems! Instead, use different textures, either
126        from different files or by using Texture.flop_left_to_right().
127        """
128        if self.x_velocity < 0:
129            self.player_sprite.scale_xy = (-PIXEL_SCALE, PIXEL_SCALE)
130            print(self.player_sprite.width)
131        elif self.x_velocity > 0:
132            self.player_sprite.scale_xy = (PIXEL_SCALE, PIXEL_SCALE)
133
134    def on_key_press(self, symbol: int, modifiers: int):
135        if symbol == arcade.key.LEFT:
136            self.x_velocity -= PLAYER_SPEED
137            self.update_car_direction()
138        elif symbol == arcade.key.RIGHT:
139            self.x_velocity += PLAYER_SPEED
140            self.update_car_direction()
141
142    def on_key_release(self, symbol: int, modifiers: int):
143        if symbol == arcade.key.LEFT:
144            self.x_velocity += PLAYER_SPEED
145            self.update_car_direction()
146        elif symbol == arcade.key.RIGHT:
147            self.x_velocity -= PLAYER_SPEED
148            self.update_car_direction()
149
150    def on_resize(self, width: int, height: int):
151        super().on_resize(width, height)
152        self.camera.resize(width, height)
153        full_width_size = (width, SCALED_BG_LAYER_HEIGHT_PX)
154
155        # We can iterate through a background group,
156        # but in the case of a parallax group the iter returns
157        # both the Backgrounds and the depths. (tuple[Background, float])
158        for layer, depth in self.backgrounds:
159            layer.size = full_width_size
160
161
162def main():
163    app = MyGame()
164    app.run()
165
166
167if __name__ == "__main__":
168    main()