nodes/menu.pyΒΆ

Part of SNKRX.

 1"""SNKRX title screen: neon block logo + start prompt."""
 2
 3from __future__ import annotations
 4
 5from simvx.core import Input, Mouse, Node, Signal
 6
 7from .colours import BG2, BLUE, FG, GREEN, GREY, RED, YELLOW
 8
 9
10class TitleScreen(Node):
11    start = Signal()
12
13    # ``on_draw`` animates every frame from ``self._t`` (scrolling horizon
14    # stripes + pulsing prompt), so it genuinely produces new geometry per
15    # frame. Declare it dynamic rather than leaning on view-decoupling.
16    dynamic = True
17
18    def __init__(self, **kwargs):
19        super().__init__(name="TitleScreen", **kwargs)
20        self._t = 0.0
21
22    def on_update(self, dt: float):
23        self._t += dt
24        # Keyboard or click/tap: a touch-only player starts the same way.
25        if Input.just_pressed("start") or Input.is_mouse_button_just_pressed(Mouse.LEFT):
26            self.start.emit()
27
28    def on_draw(self, renderer):
29        w, h = self.viewport_size
30        # Solid dark background
31        renderer.rect((0, 0), (w, h), fill=(0.06, 0.07, 0.09, 1.0))
32        # Subtle horizon stripes
33        for i in range(20):
34            y = (i * 38 + int(self._t * 14)) % h
35            renderer.rect((0, y), (w, 1), fill=(0.12, 0.14, 0.18, 1.0))
36
37        # Title: fake neon stack
38        title = "SNKRX"
39        for offset, c in [
40            ((6, 6), (RED[0], RED[1], RED[2], 0.45)),
41            ((-6, -6), (GREEN[0], GREEN[1], GREEN[2], 0.45)),
42            ((0, 0), FG),
43        ]:
44            tw = renderer.text_width(title, 14)
45            renderer.text(title, (w // 2 - tw // 2 + offset[0], 180 + offset[1]), fill=c, scale=14)
46
47        # Subtitle
48        sub = "snake auto-battler arena roguelite"
49        sw = renderer.text_width(sub, 2)
50        renderer.text(sub, (w // 2 - sw // 2, 320), fill=GREY, scale=2)
51
52        # Class chips
53        chips = [
54            ("WARRIOR", YELLOW, "melee swipe"),
55            ("ARCHER", GREEN, "pierce shot"),
56            ("MAGE", BLUE, "AOE bolt"),
57        ]
58        chip_w = 220
59        gap = 30
60        total = len(chips) * chip_w + (len(chips) - 1) * gap
61        x0 = w // 2 - total // 2
62        for i, (label, colour, desc) in enumerate(chips):
63            x = x0 + i * (chip_w + gap)
64            renderer.rect((x, 380), (chip_w, 110), fill=BG2)
65            renderer.rect((x, 380), (chip_w, 110), stroke=colour, width=2.0)
66            lw = renderer.text_width(label, 3)
67            renderer.text(label, (x + chip_w // 2 - lw // 2, 405), fill=colour, scale=3)
68            dw = renderer.text_width(desc, 2)
69            renderer.text(desc, (x + chip_w // 2 - dw // 2, 450), fill=FG, scale=2)
70
71        # Controls strip
72        bottom = h - 56
73        renderer.rect((0, bottom), (w, 56), fill=(0.85, 0.85, 0.88, 1.0))
74        controls = "MOUSE / A D  STEER       CLICK / ENTER  START       ESC  QUIT"
75        cw = renderer.text_width(controls, 2)
76        renderer.text(controls, (w // 2 - cw // 2, bottom + 20), fill=(0.10, 0.10, 0.12, 1.0), scale=2)
77
78        # Pulse prompt
79        if int(self._t * 2) % 2 == 0:
80            prompt = "CLICK OR ENTER TO BEGIN"
81            pw = renderer.text_width(prompt, 4)
82            renderer.text(prompt, (w // 2 - pw // 2, 560), fill=YELLOW, scale=4)