plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
e0be96f
— Peter John
2026-11-02T18:24:45+05:30
scene demo: y-sorted, culled crowd of walkers with toFront on click
- plum-examples/scene.plum +61 -2
plum-examples/scene.plum
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
# is a group whose rotation carries everything inside it, so a moon only ever
|
|
4
4
|
# says "I sit 40 units from my planet" and the maths composes the rest. Click a
|
|
5
5
|
# body to select it (a halo and its name), click again or the background to clear;
|
|
6
|
+
# Space pauses. A crowd of small walkers crosses the bottom of the screen: they are
|
|
7
|
+
# y-sorted every frame with `sortChildren` (lower ones draw in front), culled
|
|
8
|
+
# against the camera view with `setCullingArea`, and a clicked walker is brought
|
|
6
|
-
#
|
|
9
|
+
# to the front with `toFront`.
|
|
7
10
|
#
|
|
8
11
|
# The scene is built and queried by plain functions that never call the host, so
|
|
9
12
|
# the `test` blocks below run under `plum test`; only `init`, `update` and `draw`
|
|
@@ -29,6 +32,9 @@ primitive Layout =
|
|
|
29
32
|
MapH = 120
|
|
30
33
|
MapScale = 0.2
|
|
31
34
|
Planets = 4
|
|
35
|
+
Walkers = 60
|
|
36
|
+
WalkMin = 500.0
|
|
37
|
+
WalkMax = 1300.0
|
|
32
38
|
|
|
33
39
|
# Everything `update` and `draw` need. `bodies` and `names` are parallel (every
|
|
34
40
|
# planet and moon, each planet followed by its moons), as are `halos` (the glow shown when selected).
|
|
@@ -39,6 +45,8 @@ record Sky(
|
|
|
39
45
|
names: List[Str],
|
|
40
46
|
sun: Node,
|
|
41
47
|
label: Node,
|
|
48
|
+
crowd: Node,
|
|
49
|
+
front: Node?,
|
|
42
50
|
selected: Int,
|
|
43
51
|
clock: Float,
|
|
44
52
|
paused: Bool,
|
|
@@ -50,6 +58,17 @@ fun spun(n: Node, speed: Float) -> Node =
|
|
|
50
58
|
m.rotation = m.rotation + speed * dt
|
|
51
59
|
n.setAct(turn)
|
|
52
60
|
|
|
61
|
+
# A walker's behaviour: it moves along x at `speed` and bobs on a wave of its x.
|
|
62
|
+
fun walking(n: Node, pace: Float, phase: Float) -> Node =
|
|
63
|
+
step: fn(Node, Float) := |m, dt|
|
|
64
|
+
nx := m.x + dt * pace
|
|
65
|
+
if nx > Layout.WalkMax
|
|
66
|
+
nx = 0.0 - Layout.WalkMin
|
|
67
|
+
if nx < 0.0 - Layout.WalkMin
|
|
68
|
+
nx = Layout.WalkMax
|
|
69
|
+
_ := m.at(nx, 510.0 + 50.0 * Math.sin(nx / 90.0 + phase))
|
|
70
|
+
n.setAct(step)
|
|
71
|
+
|
|
53
72
|
# A body sprite and its (hidden) halo in a group `slot` centred on the group's own
|
|
54
73
|
# origin. The caller places the slot; the sprite spins on its own axis.
|
|
55
74
|
fun addBody(sky: Sky, sheet: Int, name: Str, row: Int, frame: Int, size: Float, spin: Float) -> Node =
|
|
@@ -76,7 +95,7 @@ fun newSky(sheet: Int) -> Sky =
|
|
|
76
95
|
label := Node.text("Click a planet", 24.0, Color.White).at(16.0, 12.0)
|
|
77
96
|
sky := Sky(
|
|
78
97
|
stage: stage, bodies: List[Node](), halos: List[Node](), names: List[Str](),
|
|
79
|
-
sun: sun, label: label, selected: -1, clock: 0.0, paused: false, map: -1)
|
|
98
|
+
sun: sun, label: label, crowd: Node.group(), front: null, selected: -1, clock: 0.0, paused: false, map: -1)
|
|
80
99
|
# faint orbit rings, added first so every body draws (and is hit) above them
|
|
81
100
|
for i := range Layout.Planets
|
|
82
101
|
r := 90.0 + Float(i) * 60.0
|
|
@@ -94,6 +113,13 @@ fun newSky(sheet: Int) -> Sky =
|
|
|
94
113
|
planet.add(moonOrbit)
|
|
95
114
|
stage.root.add(orbit)
|
|
96
115
|
stage.root.add(sun)
|
|
116
|
+
for i := range Layout.Walkers
|
|
117
|
+
speed := 20.0 + Float(i % 7) * 9.0
|
|
118
|
+
if i % 2 == 0
|
|
119
|
+
speed = 0.0 - speed
|
|
120
|
+
walker := Node.sprite(sheet, Float(i % 3) * Layout.Cell, Layout.Cell, Layout.Cell, Layout.Cell, 24.0, 24.0).at(Layout.WalkMax - 1800.0 + Float(i * 61 % 1800), 510.0)
|
|
121
|
+
sky.crowd.add(walking(walker, speed, Float(i)))
|
|
122
|
+
stage.root.add(sky.crowd)
|
|
97
123
|
stage.root.add(label)
|
|
98
124
|
sky
|
|
99
125
|
|
|
@@ -103,6 +129,7 @@ fun fit(sky: Sky, sw: Float, sh: Float) =
|
|
|
103
129
|
sky.stage.camScale = zoom
|
|
104
130
|
sky.stage.camX = 0.0 - {sw / zoom - Layout.Width} / 2.0
|
|
105
131
|
sky.stage.camY = 0.0 - {sh / zoom - Layout.Height} / 2.0
|
|
132
|
+
sky.crowd.setCullingArea(sky.stage.camX, sky.stage.camY, sw / zoom, sh / zoom)
|
|
106
133
|
|
|
107
134
|
# The index of a body (or its halo) in `bodies`, or -1.
|
|
108
135
|
fun indexOf(sky: Sky, n: Node) -> Int =
|
|
@@ -127,6 +154,10 @@ fun clickAt(sky: Sky, x: Float, y: Float) =
|
|
|
127
154
|
i := -1
|
|
128
155
|
if n := sky.stage.hit(x, y)
|
|
129
156
|
i = indexOf(sky, n)
|
|
157
|
+
if p := n.parent
|
|
158
|
+
if p == sky.crowd
|
|
159
|
+
n.toFront()
|
|
160
|
+
sky.front = n
|
|
130
161
|
if i == sky.selected
|
|
131
162
|
i = -1
|
|
132
163
|
select(sky, i)
|
|
@@ -142,6 +173,9 @@ fun tick(sky: Sky, dt: Float) =
|
|
|
142
173
|
return
|
|
143
174
|
sky.clock = sky.clock + dt
|
|
144
175
|
sky.stage.act(dt)
|
|
176
|
+
sky.crowd.sortChildren(|a, b| a.y < b.y)
|
|
177
|
+
if f := sky.front
|
|
178
|
+
f.toFront()
|
|
145
179
|
sky.sun.srcX = Float(Int(sky.clock * 6.0) % 4) * Layout.Cell
|
|
146
180
|
|
|
147
181
|
# --- host glue ---
|
|
@@ -264,3 +298,28 @@ test "fit centres the scene and screen points map back"
|
|
|
264
298
|
p := sky.stage.screenToStage(800.0, 300.0)
|
|
265
299
|
assert near(p.x, 400.0)
|
|
266
300
|
assert near(p.y, 300.0)
|
|
301
|
+
|
|
302
|
+
test "the crowd is y-sorted every tick and culled against the view"
|
|
303
|
+
sky := newSky(-1)
|
|
304
|
+
tick(sky, 0.5)
|
|
305
|
+
tick(sky, 0.5)
|
|
306
|
+
kids := sky.crowd.children
|
|
307
|
+
assert kids.length() == 60
|
|
308
|
+
for i := range 59
|
|
309
|
+
assert kids.at(i).y <= kids.at(i + 1).y
|
|
310
|
+
fit(sky, 800.0, 600.0)
|
|
311
|
+
shown := flatten(sky.stage.root).length()
|
|
312
|
+
fit(sky, 100000.0, 600.0)
|
|
313
|
+
assert flatten(sky.stage.root).length() > shown
|
|
314
|
+
for i := range 60
|
|
315
|
+
assert kids.at(i).x >= 0.0 - 500.0 && kids.at(i).x <= 1300.0
|
|
316
|
+
|
|
317
|
+
test "clicking a walker brings it to the front, even after the next sort"
|
|
318
|
+
sky := newSky(-1)
|
|
319
|
+
fit(sky, 1800.0, 600.0)
|
|
320
|
+
w := sky.crowd.children.at(0)
|
|
321
|
+
p := w.localToStage(Vec2(x: 14.0, y: 14.0))
|
|
322
|
+
clickAt(sky, p.x, p.y)
|
|
323
|
+
assert w.zIndex() == 59
|
|
324
|
+
tick(sky, 0.1)
|
|
325
|
+
assert w.zIndex() == 59
|