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