nodes/menu.pyΒΆ

Part of Clumsy Bird.

  1"""MenuScene: the title screen shown before a run starts."""
  2
  3from config import ASSETS, WIDTH
  4
  5from simvx.core import Node, Sprite2D, Vec2, on_input
  6from simvx.core.ui import AnchorPreset, Button, Control, Label, Panel
  7
  8from .ground import Ground
  9from .play_scene import PlayScene
 10
 11
 12class TitlePanel(Control):
 13    """Centred title card: game name, credit, Play button and controls."""
 14
 15    def __init__(self, on_play, **kwargs):
 16        super().__init__(**kwargs)
 17        self.set_anchor_preset(AnchorPreset.FULL_RECT)
 18
 19        # A centred control's box comes from its margin pair, so both margins
 20        # of an axis are set: left/right = -w/2 / +w/2 around the anchor.
 21        card = self.add_child(Panel(name="Card"))
 22        card.set_anchor_preset(AnchorPreset.CENTER)
 23        card.margin_left, card.margin_right = -280, 280
 24        card.margin_top, card.margin_bottom = -170, 170
 25        card.bg_colour = (0.03, 0.06, 0.11, 0.88)
 26
 27        title = card.add_child(Label("CLUMSY BIRD", name="Title"))
 28        title.set_anchor_preset(AnchorPreset.CENTER)
 29        title.margin_left, title.margin_right = -260, 260
 30        title.margin_top, title.margin_bottom = -140, -80
 31        title.font_size = 46
 32        title.alignment = "center"
 33        title.text_colour = (1.0, 0.95, 0.6, 1.0)
 34
 35        credit = card.add_child(Label("SimVX port of ellisonleao/clumsy-bird", name="Credit"))
 36        credit.set_anchor_preset(AnchorPreset.CENTER)
 37        credit.margin_left, credit.margin_right = -260, 260
 38        credit.margin_top, credit.margin_bottom = -70, -40
 39        credit.font_size = 16
 40        credit.alignment = "center"
 41        credit.text_colour = (0.85, 0.9, 0.95, 1.0)
 42
 43        play = card.add_child(Button("Play", on_press=on_play, name="Play"))
 44        play.set_anchor_preset(AnchorPreset.CENTER)
 45        play.margin_left, play.margin_right = -100, 100
 46        play.margin_top, play.margin_bottom = -20, 34
 47        play.font_size = 20
 48
 49        controls = card.add_child(Label("Space or Click: flap", name="Controls"))
 50        controls.set_anchor_preset(AnchorPreset.CENTER)
 51        controls.margin_left, controls.margin_right = -260, 260
 52        controls.margin_top, controls.margin_bottom = 60, 90
 53        controls.font_size = 18
 54        controls.alignment = "center"
 55        controls.text_colour = (1.0, 1.0, 1.0, 1.0)
 56
 57        quit_hint = card.add_child(Label("Esc: quit", name="QuitHint"))
 58        quit_hint.set_anchor_preset(AnchorPreset.CENTER)
 59        quit_hint.margin_left, quit_hint.margin_right = -260, 260
 60        quit_hint.margin_top, quit_hint.margin_bottom = 95, 121
 61        quit_hint.font_size = 15
 62        quit_hint.alignment = "center"
 63        quit_hint.text_colour = (0.85, 0.9, 0.95, 1.0)
 64
 65
 66class MenuScene(Node):
 67    """Title screen. The Play button, a click, or Space starts a run.
 68
 69    Shares PlayScene's action table so the same bindings are live on both
 70    roots; the scene tree re-registers them on every ``change_scene``.
 71    """
 72
 73    input_actions = PlayScene.input_actions
 74
 75    def __init__(self, **kwargs):
 76        super().__init__(name="MenuScene", **kwargs)
 77        self._start_queued = False
 78
 79    def on_ready(self):
 80        self.add_child(
 81            Sprite2D(
 82                texture=str(ASSETS / "bg.png"),
 83                width=WIDTH,
 84                height=504,
 85                position=Vec2(WIDTH / 2, 252),
 86                name="Background",
 87            )
 88        )
 89        self.add_child(Ground(name="Ground"))
 90        self.add_child(TitlePanel(self._queue_start, name="TitlePanel"))
 91
 92    @on_input(action="flap")
 93    def _on_start_input(self, event) -> bool:
 94        self._queue_start()
 95        return True
 96
 97    @on_input(action="quit")
 98    def _on_quit_input(self, event) -> bool:
 99        self.app.quit()
100        return True
101
102    def _queue_start(self):
103        """Ask for the swap. ``change_scene`` destroys this node, so it never
104        runs inside input dispatch: ``on_update`` performs it."""
105        self._start_queued = True
106
107    def on_update(self, dt: float):
108        if self._start_queued and self.tree:
109            self._start_queued = False
110            self.tree.change_scene(PlayScene())