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