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())