GUI with Camera

Screen shot of advanced button usage
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()