GUI with Camera

4_with_camera.py
1"""This example shows how to use arcade.gui with a camera.
2It is a simple game where the player can move around and collect coins.
3The player can upgrade their speed and the spawn rate of the coins.
4The game has a timer and ends after 60 seconds.
5The game is controlled with the arrow keys or WASD.
6
7At the beginning of the game, the UI camera is used, to apply some animations.
8
9If Arcade and Python are properly installed, you can run this example with:
10python -m arcade.examples.gui.4_with_camera
11"""
12
13import math
14import random
15
16import arcade
17from arcade.gui import UIAnchorLayout, UIBoxLayout, UIFlatButton, UILabel, UIOnClickEvent, UIView
18
19COIN_PNG = ":resources:images/items/coinGold.png"
20ADV_PNG = ":resources:/images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
21
22
23class MyCoinGame(UIView):
24 """Main view of the game. This class is a subclass of UIView, which provides
25 basic GUI setup. We add UIManager to the view under `self.ui`.
26
27 The example showcases how to:
28 - use UIView to set up a basic GUI
29 - add a button to the view and connect it to a function
30 - use camera to move the view
31
32 """
33
34 def __init__(self):
35 super().__init__()
36 self.bg_color = arcade.uicolor.DARK_BLUE_MIDNIGHT_BLUE
37
38 # basic camera setup
39 self.keys = set()
40 self.in_game_camera = arcade.Camera2D()
41 self.in_game_camera.bottom_left = 100, 100
42
43 # in-game counter
44 self._total_time = 0
45 self._game_duration = 60
46 self._game_over = False
47 self._last_coin_spawn = 0
48 self._coin_spawn_delay = 3
49 self._coins_collected = 0
50
51 # upgradable player values
52 self._player_speed = 5
53
54 # setup in-game objects
55 self.sprites = arcade.SpriteList()
56
57 self.game_area = arcade.SpriteSolidColor(
58 width=1300,
59 height=730,
60 color=arcade.color.GRAY_ASPARAGUS,
61 center_x=1280 / 2,
62 center_y=720 / 2,
63 )
64
65 self.sprites.append(self.game_area)
66
67 self.player = arcade.Sprite(
68 ADV_PNG,
69 scale=0.5,
70 center_x=1280 / 2,
71 center_y=720 / 2,
72 )
73 self.sprites.append(self.player)
74
75 self.coins = arcade.SpriteList()
76 for i in range(12):
77 # place coins in a circle around the player, radius =100
78 coin = arcade.Sprite(
79 COIN_PNG,
80 scale=0.5,
81 center_x=1280 / 2 + 200 * math.cos(math.radians(i * 40)),
82 center_y=720 / 2 + 200 * math.sin(math.radians(i * 40)),
83 )
84 self.coins.append(coin)
85
86 # UI setup, we use UIView, which automatically adds UIManager as self.ui
87 anchor = self.ui.add(UIAnchorLayout())
88
89 shop_buttons = anchor.add(
90 UIBoxLayout(vertical=False, space_between=10),
91 anchor_x="center",
92 anchor_y="bottom",
93 align_y=10,
94 )
95
96 # speed upgrade button
97 speed_upgrade = UIFlatButton(text="Upgrade Speed (5C)", width=200, height=40)
98 shop_buttons.add(speed_upgrade)
99
100 @speed_upgrade.event("on_click")
101 def upgrade_speed(event: UIOnClickEvent):
102 cost = self._player_speed
103 if self._coins_collected >= cost:
104 self._coins_collected -= cost
105 self._player_speed += 1
106 speed_upgrade.text = f"Update Speed ({self._player_speed}C)"
107 print("Speed upgraded")
108
109 # update spawn rate button
110 spawn_rate_upgrade = UIFlatButton(text="Upgrade spawn rate: 10C", width=300, height=40)
111 shop_buttons.add(spawn_rate_upgrade)
112
113 @spawn_rate_upgrade.event("on_click")
114 def upgrade_spawn_rate(event: UIOnClickEvent):
115 cost = 10
116 if self._coins_collected >= cost:
117 self._coins_collected -= cost
118 self._coin_spawn_delay -= 0.5
119 print("Spawn rate upgraded")
120
121 # position top center, with a 40px offset
122 self.out_of_game_area = anchor.add(
123 UILabel(text="Out of game area", font_size=32),
124 anchor_x="center",
125 anchor_y="top",
126 align_y=-40,
127 )
128 self.out_of_game_area.visible = False
129
130 self.coin_counter = anchor.add(
131 UILabel(text="Collected coins 0", size_hint=(0, 0)),
132 anchor_x="left",
133 anchor_y="top",
134 align_y=-10,
135 align_x=10,
136 )
137 self.coin_counter.with_background(
138 color=arcade.color.TRANSPARENT_BLACK
139 # giving a background will make the label way more performant,
140 # because it will not re-render the whole UI after a text change
141 )
142
143 # Game timer
144 self.timer = anchor.add(
145 UILabel(
146 text="Time 30.0",
147 font_size=15,
148 size_hint=(0, 0), # take the whole width to prevent linebreaks
149 ),
150 anchor_x="center",
151 anchor_y="top",
152 align_y=-10,
153 align_x=-10,
154 )
155 self.timer.with_background(color=arcade.color.TRANSPARENT_BLACK)
156
157 self.cam_pos = self.ui.camera.position
158
159 def on_draw_before_ui(self):
160 self.in_game_camera.use() # use the in-game camera to draw in-game objects
161 self.sprites.draw()
162 self.coins.draw()
163
164 def on_update(self, delta_time: float) -> bool | None:
165 if self._total_time > self._game_duration:
166 # ad new UI label to show the end of the game
167 game_over_text = self.ui.add(
168 UILabel(
169 text="End of game!\n"
170 f"You achieved {self._coins_collected} coins!\n"
171 "Press ESC to exit.\n"
172 "Use ENTER to restart.",
173 font_size=32,
174 bold=True,
175 multiline=True,
176 align="center",
177 text_color=arcade.color.WHITE,
178 size_hint=(0, 0),
179 ),
180 )
181 game_over_text.with_padding(all=10)
182 game_over_text.with_background(color=arcade.types.Color(50, 50, 50, 120))
183 game_over_text.center_on_screen()
184
185 return True
186
187 self._total_time += delta_time
188 self._last_coin_spawn += delta_time
189
190 # update the timer
191 self.timer.text = f"Time {self._game_duration - self._total_time:.1f}"
192
193 # spawn new coins
194 if self._last_coin_spawn > self._coin_spawn_delay:
195 coin = arcade.Sprite(
196 COIN_PNG,
197 scale=0.5,
198 center_x=random.randint(0, 1280),
199 center_y=random.randint(0, 720),
200 )
201 self.coins.append(coin)
202 self._last_coin_spawn -= self._coin_spawn_delay
203
204 # move the player sprite
205 if {arcade.key.LEFT, arcade.key.A} & self.keys:
206 self.player.left -= self._player_speed
207 if {arcade.key.RIGHT, arcade.key.D} & self.keys:
208 self.player.left += self._player_speed
209 if {arcade.key.UP, arcade.key.W} & self.keys:
210 self.player.top += self._player_speed
211 if {arcade.key.DOWN, arcade.key.S} & self.keys:
212 self.player.top -= self._player_speed
213
214 # move the camera with the player
215 self.in_game_camera.position = self.player.position
216
217 # collect coins
218 collisions = self.player.collides_with_list(self.coins)
219 for coin in collisions:
220 coin.remove_from_sprite_lists()
221 self._coins_collected += 1
222 print("Coin collected")
223
224 # update the coin counter
225 self.coin_counter.text = f"Collected coins {self._coins_collected}"
226
227 # inform player if they are out of the game area
228 if not self.player.collides_with_sprite(self.game_area):
229 self.out_of_game_area.visible = True
230 else:
231 self.out_of_game_area.visible = False
232
233 # slide in the UI from bottom, until total time reaches 2 seconds
234 progress = min(1.0, self._total_time / 2)
235
236 # Because we allow for camera rotation we have work in the center
237 # and not the edge because it behaves oddly otherwise
238 cam_pos_x = self.window.center_x
239 cam_pos_y = 50 * (1 - progress) + self.window.center_y
240 self.ui.camera.position = (cam_pos_x, cam_pos_y)
241
242 return False
243
244 def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
245 self.keys.add(symbol)
246
247 if symbol == arcade.key.ESCAPE:
248 arcade.close_window()
249 if symbol == arcade.key.ENTER:
250 self.window.show_view(MyCoinGame())
251
252 return False
253
254 def on_key_release(self, symbol: int, modifiers: int) -> bool | None:
255 if symbol in self.keys:
256 self.keys.remove(symbol)
257 return False
258
259
260def main():
261 window = arcade.Window(1280, 720, "GUI Example: Coin Game (Camera)", resizable=False)
262 window.show_view(MyCoinGame())
263 window.run()
264
265
266if __name__ == "__main__":
267 main()