nodes/menu.pyΒΆ

Part of Clumsy Bird.

  1"""MenuScene: the title screen shown before a run starts."""
  2
  3from config import ASSETS, ground_top
  4
  5from simvx.core import Controller, Key, Mouse, Node, Sprite2D, Vec2, on_input
  6from simvx.core.ui import Anchor, Button, Control, Label, Panel, PointerFilter
  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    The panel itself is transparent to the pointer so a tap anywhere outside
 16    the card starts a run; the card keeps the pointer, so a tap on it presses
 17    Play or nothing at all.
 18    """
 19
 20    def __init__(self, on_play, **kwargs):
 21        super().__init__(**kwargs)
 22        self.anchor = Anchor.FULL_RECT
 23        self.pointer_filter = PointerFilter.IGNORE
 24
 25        # Every row is centred on the card; an inset on one side shifts a row
 26        # off the centre line by half that inset.
 27        self._card = card = self.add_child(Panel(name="Card", anchor=Anchor.CENTER))
 28        card.bg_colour = (0.03, 0.06, 0.11, 0.88)
 29
 30        title = card.add_child(
 31            Label("CLUMSY BIRD", name="Title", anchor=Anchor.CENTER, size=(520, 60), inset=(0, 0, 0, 220))
 32        )
 33        title.font_size = 46
 34        title.alignment = "centre"
 35        title.text_colour = (1.0, 0.95, 0.6, 1.0)
 36
 37        credit = card.add_child(
 38            Label(
 39                "SimVX port of ellisonleao/clumsy-bird",
 40                name="Credit",
 41                anchor=Anchor.CENTER,
 42                size=(520, 30),
 43                inset=(0, 0, 0, 110),
 44            )
 45        )
 46        credit.font_size = 16
 47        credit.alignment = "centre"
 48        credit.text_colour = (0.85, 0.9, 0.95, 1.0)
 49
 50        play = card.add_child(
 51            Button("Play", on_press=on_play, name="Play", anchor=Anchor.CENTER, size=(200, 54), inset=(0, 14, 0, 0))
 52        )
 53        play.font_size = 20
 54
 55        controls = card.add_child(
 56            Label(
 57                "Tap, Space or (A): flap", name="Controls", anchor=Anchor.CENTER, size=(520, 30), inset=(0, 150, 0, 0)
 58            )
 59        )
 60        controls.font_size = 18
 61        controls.alignment = "centre"
 62        controls.text_colour = (1.0, 1.0, 1.0, 1.0)
 63
 64        quit_hint = card.add_child(
 65            Label("Esc: quit", name="QuitHint", anchor=Anchor.CENTER, size=(520, 26), inset=(0, 216, 0, 0))
 66        )
 67        quit_hint.font_size = 15
 68        quit_hint.alignment = "centre"
 69        quit_hint.text_colour = (0.85, 0.9, 0.95, 1.0)
 70
 71    def on_update(self, dt: float):
 72        # Keep the card inside the window: on a small viewport it shrinks
 73        # rather than spilling off the edges.
 74        width, height = self.viewport_size
 75        half_w = min(280.0, float(width) / 2 - 20.0)
 76        half_h = min(170.0, float(height) / 2 - 20.0)
 77        self._card.size = (2 * half_w, 2 * half_h)
 78
 79
 80class MenuScene(Node):
 81    """Title screen. The Play button, a tap, Space or a pad's A starts a run."""
 82
 83    #: Leaving the title screen is its own action: a tap counts, because the
 84    #: web runtime delivers touch as a left click, and so does a pad's A or
 85    #: Start, because a pad player has to be able to get into the game.
 86    input_context = {
 87        "start": [Key.SPACE, Key.ENTER, Mouse.LEFT, Controller.A, Controller.START],
 88        "quit": [Key.ESCAPE],
 89    }
 90
 91    def __init__(self, **kwargs):
 92        super().__init__(name="MenuScene", **kwargs)
 93
 94    def on_ready(self):
 95        self.background = self.add_child(Sprite2D(texture=str(ASSETS / "bg.png"), name="Background"))
 96        self.add_child(Ground(name="Ground"))
 97        self.add_child(TitlePanel(self._start, name="TitlePanel"))
 98
 99    def on_update(self, dt: float):
100        width, height = self.viewport_size
101        sky_height = ground_top(height)
102        self.background.width = width
103        self.background.height = sky_height
104        self.background.position = Vec2(width / 2, sky_height / 2)
105
106    @on_input("start")
107    def _on_start_input(self, event) -> None:
108        self._start()
109
110    @on_input("quit")
111    def _on_quit_input(self, event) -> bool:
112        self.app.quit()
113        return True
114
115    def _start(self):
116        self.tree.change_scene(PlayScene())