nodes/hud.py¶
Part of Dodge the Creeps.
1"""HUD: anchored Label widgets for score / message / start prompt."""
2
3from __future__ import annotations
4
5from simvx.core import Input, Node, Signal, Timer
6from simvx.core.ui import Anchor, Control, Label, PointerFilter
7
8
9class HUD(Control):
10 """Top-of-screen score, centred message, bottom restart prompt.
11
12 Faithful to upstream `hud.gd`: shows "Get Ready" briefly at game start,
13 "Game Over" then "Dodge the Creeps" + a restart prompt at game over,
14 and emits `start_game` when the player answers that prompt.
15 """
16
17 start_game = Signal()
18
19 MESSAGE_FADE_SEC = 2.0
20 GAME_OVER_HOLD_SEC = 1.0
21
22 def __init__(self, **kwargs):
23 super().__init__(name="HUD", anchor=Anchor.FULL_RECT, **kwargs)
24 # A read-only overlay: a tap anywhere on it is a tap on the game, which
25 # is what starts and restarts a run.
26 self.pointer_filter = PointerFilter.IGNORE
27
28 # Score label: top-centre, anchored to TOP_WIDE so it scales with the
29 # window width and stays centred horizontally.
30 self.score_label = self.add_child(
31 Label("0", name="ScoreLabel", anchor=Anchor.TOP_WIDE, size=(0, 0), inset=(0, 12, 0, 0))
32 )
33 self.score_label.pointer_filter = PointerFilter.IGNORE
34 self.score_label.font_size = 56
35 self.score_label.alignment = "centre"
36 self.score_label.text_colour = (1.0, 1.0, 1.0, 1.0)
37
38 # Splash + restart prompt rendered by Main.on_draw via fit-scaled
39 # text (the Label widget's text_width doesn't handle newlines
40 # or wider-than-rect text, so it renders left-shifted at default size).
41 # We expose plain string state here; Main reads .message_text and
42 # .show_prompt to render them.
43 self.message_text: str = "Dodge the Creeps"
44 self.message_visible: bool = True
45 self.show_prompt: bool = True
46
47 self.message_timer = self.add_child(Timer(duration=self.MESSAGE_FADE_SEC, one_shot=True, name="MessageTimer"))
48 self.message_timer.timeout.connect(self._hide_message)
49
50 def on_update(self, dt: float) -> None:
51 # The prompt is the only thing that arms a (re)start, so the HUD owns
52 # the input that answers it and reports back through `start_game`.
53 # One action covers the keys, a tap and a pad button alike.
54 if self.show_prompt and Input.just_pressed("start"):
55 self.start_game.emit()
56
57 # ------------------------------------------------------------------
58 # Public API mirroring hud.gd
59 # ------------------------------------------------------------------
60
61 def update_score(self, score: int) -> None:
62 self.score_label.text = str(score)
63
64 def show_message(self, text: str) -> None:
65 self.message_text = text
66 self.message_visible = True
67 self.message_timer.start()
68
69 def show_game_over(self) -> Node:
70 """Run the `Game Over` → splash → restart-prompt sequence as a coroutine."""
71 return self.start_coroutine(self._game_over_sequence())
72
73 def _game_over_sequence(self):
74 from simvx.core import wait, wait_signal
75
76 self.show_message("Game Over")
77 yield from wait_signal(self.message_timer.timeout)
78
79 self.message_text = "Dodge the Creeps"
80 self.message_visible = True
81 yield from wait(self.GAME_OVER_HOLD_SEC)
82
83 self.show_prompt = True
84
85 def hide_start_prompt(self) -> None:
86 self.show_prompt = False
87
88 def _hide_message(self) -> None:
89 self.message_visible = False