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, MouseButton, 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.is_action_just_pressed("start") or Input.is_mouse_button_just_pressed(MouseButton.LEFT):
26            self.start.emit()
27
28    def on_draw(self, renderer):
29        if self.tree is not None:
30            w, h = self.tree.screen_size
31        else:
32            w, h = 1280, 720
33        # Solid dark background
34        renderer.draw_rect((0, 0), (w, h), colour=(0.06, 0.07, 0.09, 1.0), filled=True)
35        # Subtle horizon stripes
36        for i in range(20):
37            y = (i * 38 + int(self._t * 14)) % h
38            renderer.draw_rect((0, y), (w, 1), colour=(0.12, 0.14, 0.18, 1.0), filled=True)
39
40        # Title: fake neon stack
41        title = "SNKRX"
42        for offset, c in [
43            ((6, 6), (RED[0], RED[1], RED[2], 0.45)),
44            ((-6, -6), (GREEN[0], GREEN[1], GREEN[2], 0.45)),
45            ((0, 0), FG),
46        ]:
47            tw = renderer.text_width(title, 14)
48            renderer.draw_text(title, (w // 2 - tw // 2 + offset[0], 180 + offset[1]), scale=14, colour=c)
49
50        # Subtitle
51        sub = "snake auto-battler arena roguelite"
52        sw = renderer.text_width(sub, 2)
53        renderer.draw_text(sub, (w // 2 - sw // 2, 320), scale=2, colour=GREY)
54
55        # Class chips
56        chips = [
57            ("WARRIOR", YELLOW, "melee swipe"),
58            ("ARCHER", GREEN, "pierce shot"),
59            ("MAGE", BLUE, "AOE bolt"),
60        ]
61        chip_w = 220
62        gap = 30
63        total = len(chips) * chip_w + (len(chips) - 1) * gap
64        x0 = w // 2 - total // 2
65        for i, (label, colour, desc) in enumerate(chips):
66            x = x0 + i * (chip_w + gap)
67            renderer.draw_rect((x, 380), (chip_w, 110), colour=BG2, filled=True)
68            renderer.draw_rect((x, 380), (chip_w, 110), colour=colour, filled=False, thickness=2.0)
69            lw = renderer.text_width(label, 3)
70            renderer.draw_text(label, (x + chip_w // 2 - lw // 2, 405), scale=3, colour=colour)
71            dw = renderer.text_width(desc, 2)
72            renderer.draw_text(desc, (x + chip_w // 2 - dw // 2, 450), scale=2, colour=FG)
73
74        # Controls strip
75        bottom = h - 56
76        renderer.draw_rect((0, bottom), (w, 56), colour=(0.85, 0.85, 0.88, 1.0), filled=True)
77        controls = "MOUSE / A D  STEER       CLICK / ENTER  START       ESC  QUIT"
78        cw = renderer.text_width(controls, 2)
79        renderer.draw_text(controls, (w // 2 - cw // 2, bottom + 20), scale=2, colour=(0.10, 0.10, 0.12, 1.0))
80
81        # Pulse prompt
82        if int(self._t * 2) % 2 == 0:
83            prompt = "CLICK OR ENTER TO BEGIN"
84            pw = renderer.text_width(prompt, 4)
85            renderer.draw_text(prompt, (w // 2 - pw // 2, 560), scale=4, colour=YELLOW)