Pause Menu¶
freeze the game with tree.paused and show a modal overlay.
Tags: ui menu pause modal anchors
Setting self.tree.paused = True suspends the scene: every node whose
update_mode resolves to PAUSABLE (the default for a scene) stops running
on_update and stops hearing input, while it keeps drawing. A node that must
keep working during the pause, such as the menu that resumes the game, is
created with update_mode=UpdateMode.ALWAYS. The mode is inherited, so set it
on the menu, never on the scene root, or the whole game would ignore the pause.
Here the background clock lives in the root’s on_update, so it stops by
itself while paused; nothing in the game checks a pause flag. PauseMenu
declares the pause action (P or ESC) in its own input_context and toggles
the pause with @on_input("pause"); the Pause button in the top-right corner
does the same for a mouse or a finger. Opening the menu shows a dimmed
FULL_RECT scrim and a CENTER-anchored panel holding a VBoxContainer of
Resume, Restart and Quit buttons. Anchors keep the scrim covering the window
and the panel centred at any window size. Restart is a signal the menu emits
and the game connects, so the menu knows nothing about what it pauses.
Source¶
1# /// simvx
2# tags = ["ui", "menu", "pause", "modal", "anchors"]
3# web = { width = 800, height = 600, responsive = true }
4# ///
5"""Pause Menu: freeze the game with `tree.paused` and show a modal overlay.
6
7Setting `self.tree.paused = True` suspends the scene: every node whose
8`update_mode` resolves to `PAUSABLE` (the default for a scene) stops running
9`on_update` and stops hearing input, while it keeps drawing. A node that must
10keep working during the pause, such as the menu that resumes the game, is
11created with `update_mode=UpdateMode.ALWAYS`. The mode is inherited, so set it
12on the menu, never on the scene root, or the whole game would ignore the pause.
13
14Here the background clock lives in the root's `on_update`, so it stops by
15itself while paused; nothing in the game checks a pause flag. `PauseMenu`
16declares the `pause` action (P or ESC) in its own `input_context` and toggles
17the pause with `@on_input("pause")`; the Pause button in the top-right corner
18does the same for a mouse or a finger. Opening the menu shows a dimmed
19`FULL_RECT` scrim and a `CENTER`-anchored panel holding a `VBoxContainer` of
20Resume, Restart and Quit buttons. Anchors keep the scrim covering the window
21and the panel centred at any window size. Restart is a signal the menu emits
22and the game connects, so the menu knows nothing about what it pauses.
23"""
24
25import math
26
27from simvx.core import (
28 Anchor,
29 Button,
30 Colour,
31 Control,
32 Key,
33 Label,
34 Node2D,
35 Panel,
36 Signal,
37 UpdateMode,
38 VBoxContainer,
39 on_input,
40)
41from simvx.graphics import App
42
43
44class PauseMenu(Control):
45 """The pause button and the overlay it opens, alive while the scene is paused."""
46
47 input_context = {"pause": [Key.P, Key.ESCAPE]}
48 restart_requested = Signal()
49
50 def on_ready(self):
51 pause = self.add_child(Button("Pause", name="PauseButton", anchor=Anchor.TOP_RIGHT, inset=16, size=(96, 32)))
52 pause.pressed.connect(self.toggle)
53
54 self.overlay = self.add_child(Control(name="Overlay", anchor=Anchor.FULL_RECT, visible=False))
55 scrim = self.overlay.add_child(Panel(name="Scrim", anchor=Anchor.FULL_RECT))
56 scrim.bg_colour = Colour.rgba(0.0, 0.0, 0.0, 0.6)
57 panel = self.overlay.add_child(Panel(name="MenuPanel", anchor=Anchor.CENTER, size=(320, 300)))
58 panel.bg_colour = Colour.hex("#1A1A2E")
59 panel.add_child(
60 Label("Paused", anchor=Anchor.TOP_WIDE, inset=(0, 16), size=(0, 36), font_size=24.0, alignment="centre")
61 )
62
63 # A top inset on a CENTER anchor moves the centre down by half of it,
64 # into the room below the title.
65 buttons = panel.add_child(
66 VBoxContainer(anchor=Anchor.CENTER, inset=(0, 40, 0, 0), size=(220, 140), separation=12.0)
67 )
68 for text, handler in (("Resume", self.toggle), ("Restart", self.restart), ("Quit", self.quit)):
69 button = buttons.add_child(Button(text, name=f"{text}Button", size=(220, 36)))
70 button.pressed.connect(handler)
71
72 @on_input("pause")
73 def toggle(self, event=None):
74 self.tree.paused = not self.tree.paused
75 self.overlay.visible = self.tree.paused
76
77 def restart(self):
78 self.restart_requested.emit()
79 self.toggle()
80
81 def quit(self):
82 self.app.quit()
83
84
85class PauseMenuDemo(Node2D):
86 """Moving background content under the menu; it pauses because it is PAUSABLE."""
87
88 dynamic = True # on_draw reads the clock, which is not a Property
89
90 def on_ready(self):
91 self.clock = 0.0
92 hint = self.add_child(
93 Label(
94 "Press P or ESC to pause",
95 anchor=Anchor.CENTER_TOP,
96 inset=8,
97 size=(320, 24),
98 font_size=16.0,
99 alignment="centre",
100 )
101 )
102 hint.text_colour = Colour.LIGHT_GRAY
103 menu = self.add_child(PauseMenu(name="PauseMenu", anchor=Anchor.FULL_RECT, update_mode=UpdateMode.ALWAYS))
104 menu.restart_requested.connect(self.restart)
105
106 def restart(self):
107 self.clock = 0.0
108
109 def on_update(self, dt):
110 self.clock += dt
111
112 def on_draw(self, renderer):
113 w, h = self.viewport_size
114 renderer.rect((0, 0), (w, h), fill=Colour.hex("#10131C"))
115 for radius, speed, size, colour in (
116 (160, 1.5, 36, "#4FC3F7"),
117 (110, -0.9, 26, "#F4A261"),
118 (215, 0.6, 18, "#9B5DE5"),
119 ):
120 x = w / 2 + math.cos(self.clock * speed) * radius
121 y = h / 2 + math.sin(self.clock * speed) * radius * 0.75
122 renderer.rect((x - size / 2, y - size / 2), (size, size), fill=Colour.hex(colour))
123
124
125if __name__ == "__main__":
126 App(title="SimVX Pause Menu", width=800, height=600).run(PauseMenuDemo())