nodes/ui.pyΒΆ
Part of HeartBeast Action RPG.
1"""Title card: the screen the port opens on, and the one way into play.
2
3It is a CanvasLayer, so it draws in screen space above the world and the camera
4never moves it. Its actions travel with it: the ``start`` context is live
5exactly while the card is in the tree, and binds the keys, a left click (which
6is what the web runtime delivers a tap as) and the pad's A and Start, so every
7device can begin.
8"""
9
10from __future__ import annotations
11
12from controls import TITLE
13from settings import COLOUR_UI_BG, COLOUR_UI_TEXT, COLOUR_UI_TEXT_DIM, COLOUR_WHITE
14
15from simvx.core import CanvasLayer, Input, Signal
16
17TITLE_TEXT = "HEARTBEAST ACTION RPG"
18SUBTITLE = "A SimVX port of HeartBeast's Godot Action RPG tutorial"
19START_PROMPT = "PRESS SPACE / ENTER, TAP, OR PRESS A TO PLAY"
20
21
22class StartScreen(CanvasLayer):
23 """Title card over the live world. A key, a tap or a pad button begins play.
24
25 The card sits in the lower third so the world it is introducing, and the
26 heart bar above it, stay visible behind it.
27 """
28
29 # The prompt blinks off a timer, which is not Property state.
30 dynamic = True
31
32 input_context = TITLE
33
34 start_requested = Signal()
35
36 def __init__(self, **kwargs):
37 super().__init__(name="StartScreen", layer=CanvasLayer.Band.OVERLAY, **kwargs)
38 self._blink = 0.0
39
40 def on_update(self, dt: float):
41 self._blink += dt
42 if Input.just_pressed("start"):
43 self.start_requested.emit()
44
45 def on_draw(self, renderer):
46 sw, sh = self.viewport_size
47 card_h = sh * 0.34
48 card_y = sh - card_h - sh * 0.06
49 renderer.rect((0, card_y), (sw, card_h), fill=COLOUR_UI_BG)
50
51 title_scale = renderer.fit_scale(TITLE_TEXT, sw * 0.8, base_scale=5.0)
52 self._centred(renderer, TITLE_TEXT, sw, card_y + card_h * 0.10, title_scale, COLOUR_WHITE)
53 sub_scale = renderer.fit_scale(SUBTITLE, sw * 0.8, base_scale=1.8)
54 self._centred(renderer, SUBTITLE, sw, card_y + card_h * 0.42, sub_scale, COLOUR_UI_TEXT_DIM)
55
56 if int(self._blink * 2) % 2 == 0:
57 prompt_scale = renderer.fit_scale(START_PROMPT, sw * 0.8, base_scale=2.4)
58 self._centred(renderer, START_PROMPT, sw, card_y + card_h * 0.68, prompt_scale, COLOUR_UI_TEXT)
59
60 @staticmethod
61 def _centred(renderer, text: str, sw: float, y: float, scale: float, colour):
62 renderer.text(text, ((sw - renderer.text_width(text, scale)) / 2, y), fill=colour, scale=scale)