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()