@ludra/cli 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blocks/chasing-enemy/assets/slime.svg +21 -0
- package/blocks/chasing-enemy/block.json +64 -0
- package/blocks/chasing-enemy/demo.json +32 -0
- package/blocks/chasing-enemy/scripts/chaser.ts +19 -0
- package/blocks/coin-row/assets/coin.svg +22 -0
- package/blocks/coin-row/block.json +157 -0
- package/blocks/coin-row/demo.json +15 -0
- package/blocks/falling-platform/assets/falling-platform.svg +13 -0
- package/blocks/falling-platform/block.json +55 -0
- package/blocks/falling-platform/demo.json +24 -0
- package/blocks/falling-platform/scripts/falling-platform.ts +56 -0
- package/blocks/jump-pad/assets/jump-pad.svg +6 -0
- package/blocks/jump-pad/block.json +56 -0
- package/blocks/jump-pad/demo.json +17 -0
- package/blocks/jump-pad/scripts/jump-pad.ts +10 -0
- package/blocks/key-and-door/assets/door.svg +12 -0
- package/blocks/key-and-door/assets/key.svg +9 -0
- package/blocks/key-and-door/block.json +90 -0
- package/blocks/key-and-door/demo.json +31 -0
- package/blocks/key-and-door/scripts/key.ts +12 -0
- package/blocks/key-and-door/scripts/locked-door.ts +15 -0
- package/blocks/moving-platform/assets/lift.svg +9 -0
- package/blocks/moving-platform/block.json +38 -0
- package/blocks/moving-platform/demo.json +27 -0
- package/blocks/patrol-enemy/assets/slime.svg +21 -0
- package/blocks/patrol-enemy/block.json +60 -0
- package/blocks/patrol-enemy/demo.json +22 -0
- package/blocks/spikes/assets/spikes.svg +8 -0
- package/blocks/spikes/block.json +49 -0
- package/blocks/spikes/demo.json +16 -0
- package/blocks/teleporter/assets/teleporter.svg +14 -0
- package/blocks/teleporter/block.json +89 -0
- package/blocks/teleporter/demo.json +17 -0
- package/blocks/teleporter/scripts/teleporter.ts +17 -0
- package/blocks/time-limit/block.json +36 -0
- package/blocks/time-limit/demo.json +25 -0
- package/blocks/time-limit/scripts/time-limit.ts +16 -0
- package/blocks/turret/assets/bullet.svg +5 -0
- package/blocks/turret/assets/turret.svg +10 -0
- package/blocks/turret/block.json +59 -0
- package/blocks/turret/demo.json +35 -0
- package/blocks/turret/scripts/bullet.ts +11 -0
- package/blocks/turret/scripts/turret.ts +36 -0
- package/dist/blocks.d.ts +65 -0
- package/dist/blocks.js +95 -0
- package/dist/commands/blocks.d.ts +12 -0
- package/dist/commands/blocks.js +49 -0
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.js +8 -3
- package/dist/commands/components.js +5 -1
- package/dist/commands/dev.js +3 -2
- package/dist/commands/new.js +1 -1
- package/dist/commands/play.js +25 -9
- package/dist/commands/test.d.ts +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/headless.js +6 -1
- package/dist/index.js +23 -3
- package/dist/project.d.ts +17 -1
- package/dist/project.js +91 -8
- package/dist/server.d.ts +1 -0
- package/dist/server.js +27 -2
- package/package.json +4 -3
- package/templates/breakout/CLAUDE.md +42 -0
- package/templates/breakout/_gitignore +2 -0
- package/templates/breakout/ludra.json +5 -0
- package/templates/breakout/package.json +13 -0
- package/templates/breakout/scenes/main.scene.json +648 -0
- package/templates/breakout/scripts/backdrop.ts +101 -0
- package/templates/breakout/scripts/ball.ts +90 -0
- package/templates/breakout/scripts/brick.ts +51 -0
- package/templates/breakout/scripts/paddle.ts +53 -0
- package/templates/breakout/scripts/rules.ts +128 -0
- package/templates/breakout/scripts/wall.ts +21 -0
- package/templates/breakout/shaders/neon.frag +42 -0
- package/templates/breakout/tests/gameplay.test.json +31 -0
- package/templates/platformer/CLAUDE.md +4 -1
- package/templates/platformer/package.json +1 -1
- package/templates/topdown/CLAUDE.md +38 -0
- package/templates/topdown/_gitignore +2 -0
- package/templates/topdown/assets/gem.svg +16 -0
- package/templates/topdown/assets/grass.svg +8 -0
- package/templates/topdown/assets/hedge.svg +10 -0
- package/templates/topdown/assets/hero.svg +23 -0
- package/templates/topdown/assets/portal.svg +15 -0
- package/templates/topdown/assets/slime.svg +21 -0
- package/templates/topdown/assets/thorns.svg +7 -0
- package/templates/topdown/ludra.json +5 -0
- package/templates/topdown/package.json +13 -0
- package/templates/topdown/scenes/main.scene.json +499 -0
- package/templates/topdown/scripts/hud.ts +94 -0
- package/templates/topdown/scripts/night.ts +67 -0
- package/templates/topdown/scripts/portal.ts +34 -0
- package/templates/topdown/shaders/dream.frag +31 -0
- package/templates/topdown/tests/gameplay.test.json +30 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="26" viewBox="0 0 64 26">
|
|
2
|
+
<!-- Spritesheet: 2 frames of 32x26, a bouncing slime facing right. -->
|
|
3
|
+
<g>
|
|
4
|
+
<path d="M2 25 C 2 10, 8 3, 16 3 C 24 3, 30 10, 30 25 Z" fill="#c77dff"/>
|
|
5
|
+
<path d="M5 25 C 5 18, 9 14, 13 14 C 9 17, 8 21, 8 25 Z" fill="#e0b0ff" opacity="0.7"/>
|
|
6
|
+
<path d="M14 11 l 6 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
|
|
7
|
+
<path d="M27 11 l -4 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
|
|
8
|
+
<circle cx="18" cy="15" r="2" fill="#2a1440"/>
|
|
9
|
+
<circle cx="24" cy="15" r="2" fill="#2a1440"/>
|
|
10
|
+
<path d="M17 20 q 4 2 8 0" stroke="#2a1440" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
|
11
|
+
</g>
|
|
12
|
+
<g transform="translate(32 0)">
|
|
13
|
+
<path d="M0 25 C 0 14, 7 8, 16 8 C 25 8, 32 14, 32 25 Z" fill="#c77dff"/>
|
|
14
|
+
<path d="M3 25 C 3 20, 7 17, 11 17 C 7 19, 6 22, 6 25 Z" fill="#e0b0ff" opacity="0.7"/>
|
|
15
|
+
<path d="M14 15 l 6 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
|
|
16
|
+
<path d="M28 15 l -4 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
|
|
17
|
+
<circle cx="18" cy="18.5" r="2" fill="#2a1440"/>
|
|
18
|
+
<circle cx="24.5" cy="18.5" r="2" fill="#2a1440"/>
|
|
19
|
+
<path d="M17 22.5 q 4 1.5 8 0" stroke="#2a1440" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
|
20
|
+
</g>
|
|
21
|
+
</svg>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chasing-enemy",
|
|
3
|
+
"description": "An enemy that waits, then runs at the player once they come close. Landing on it stomps it.",
|
|
4
|
+
"tags": [
|
|
5
|
+
"platformer",
|
|
6
|
+
"enemy",
|
|
7
|
+
"script"
|
|
8
|
+
],
|
|
9
|
+
"usage": "--at x,y puts it on the surface whose top is y. params.range is how close the player must be (px), params.speed how fast it runs. It does not stop at ledges; keep it on a wide floor.",
|
|
10
|
+
"entities": [
|
|
11
|
+
{
|
|
12
|
+
"id": "{id}",
|
|
13
|
+
"components": {
|
|
14
|
+
"transform": {
|
|
15
|
+
"dx": 0,
|
|
16
|
+
"dy": -13
|
|
17
|
+
},
|
|
18
|
+
"body": {
|
|
19
|
+
"type": "kinematic",
|
|
20
|
+
"width": 32,
|
|
21
|
+
"height": 26,
|
|
22
|
+
"sensor": true
|
|
23
|
+
},
|
|
24
|
+
"sprite": {
|
|
25
|
+
"color": "#c77dff",
|
|
26
|
+
"image": "assets/slime.svg",
|
|
27
|
+
"frameWidth": 32,
|
|
28
|
+
"idle": "0",
|
|
29
|
+
"run": "0-1",
|
|
30
|
+
"fps": 6,
|
|
31
|
+
"layer": 2
|
|
32
|
+
},
|
|
33
|
+
"hazard": {
|
|
34
|
+
"stompable": true,
|
|
35
|
+
"score": 1
|
|
36
|
+
},
|
|
37
|
+
"script": {
|
|
38
|
+
"src": "scripts/chaser.ts",
|
|
39
|
+
"params": {
|
|
40
|
+
"speed": 70,
|
|
41
|
+
"range": 220
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"files": [
|
|
48
|
+
"scripts/chaser.ts",
|
|
49
|
+
"assets/slime.svg"
|
|
50
|
+
],
|
|
51
|
+
"effects": {
|
|
52
|
+
"stomp": {
|
|
53
|
+
"sound": "hit",
|
|
54
|
+
"shake": 5,
|
|
55
|
+
"particles": {
|
|
56
|
+
"count": 18,
|
|
57
|
+
"color": "#c77dff",
|
|
58
|
+
"speed": 180,
|
|
59
|
+
"life": 0.5,
|
|
60
|
+
"size": 4
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"at": [
|
|
3
|
+
600,
|
|
4
|
+
500
|
|
5
|
+
],
|
|
6
|
+
"tests": [
|
|
7
|
+
{
|
|
8
|
+
"name": "waits while the player is far away",
|
|
9
|
+
"duration": "1s",
|
|
10
|
+
"assert": [
|
|
11
|
+
"chasing-enemy.state.chasing == false",
|
|
12
|
+
"chasing-enemy.x == 600"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "chases once the player comes within range",
|
|
17
|
+
"inputs": "right:1.3s",
|
|
18
|
+
"duration": "1.5s",
|
|
19
|
+
"assert": [
|
|
20
|
+
"chasing-enemy.state.chasing == true",
|
|
21
|
+
"chasing-enemy.x < 600"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "kills the player on contact",
|
|
26
|
+
"inputs": "right:2.5s",
|
|
27
|
+
"assert": [
|
|
28
|
+
"deaths >= 1"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Script } from "@ludra/core";
|
|
2
|
+
|
|
3
|
+
interface Params {
|
|
4
|
+
/** Run speed in px/s. */
|
|
5
|
+
speed: number;
|
|
6
|
+
/** Starts chasing when the player is this close horizontally. */
|
|
7
|
+
range: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Waits until the player comes near, then runs toward them. Pair with a stompable hazard. */
|
|
11
|
+
export default {
|
|
12
|
+
update({ self, world, params }) {
|
|
13
|
+
const { speed, range } = params as unknown as Params;
|
|
14
|
+
const player = world.player();
|
|
15
|
+
const distance = player ? player.x - self.x : Infinity;
|
|
16
|
+
self.state.chasing = Math.abs(distance) < range;
|
|
17
|
+
self.vx = self.state.chasing ? Math.sign(distance) * speed : 0;
|
|
18
|
+
},
|
|
19
|
+
} satisfies Script;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="20" viewBox="0 0 80 20">
|
|
2
|
+
<!-- Spritesheet: 4 frames of 20x20, a spinning coin. -->
|
|
3
|
+
<g>
|
|
4
|
+
<circle cx="10" cy="10" r="9" fill="#e0a526"/>
|
|
5
|
+
<circle cx="10" cy="10" r="7" fill="#ffd166"/>
|
|
6
|
+
<rect x="8.5" y="5" width="3" height="10" rx="1.5" fill="#e0a526"/>
|
|
7
|
+
<circle cx="7" cy="6.5" r="1.6" fill="#fff3b0"/>
|
|
8
|
+
</g>
|
|
9
|
+
<g transform="translate(20 0)">
|
|
10
|
+
<ellipse cx="10" cy="10" rx="6.5" ry="9" fill="#e0a526"/>
|
|
11
|
+
<ellipse cx="10" cy="10" rx="4.8" ry="7" fill="#ffd166"/>
|
|
12
|
+
<rect x="9" y="5" width="2" height="10" rx="1" fill="#e0a526"/>
|
|
13
|
+
</g>
|
|
14
|
+
<g transform="translate(40 0)">
|
|
15
|
+
<ellipse cx="10" cy="10" rx="2.2" ry="9" fill="#e0a526"/>
|
|
16
|
+
<rect x="9.3" y="2" width="1.4" height="16" fill="#fff3b0"/>
|
|
17
|
+
</g>
|
|
18
|
+
<g transform="translate(60 0)">
|
|
19
|
+
<ellipse cx="10" cy="10" rx="6.5" ry="9" fill="#e0a526"/>
|
|
20
|
+
<ellipse cx="10" cy="10" rx="4.8" ry="7" fill="#f2bb3d"/>
|
|
21
|
+
</g>
|
|
22
|
+
</svg>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coin-row",
|
|
3
|
+
"description": "Five spinning coins in a row, 40px apart.",
|
|
4
|
+
"tags": [
|
|
5
|
+
"platformer",
|
|
6
|
+
"collectible"
|
|
7
|
+
],
|
|
8
|
+
"usage": "--at x,y centers the row 40px above (x, y), at head height for a player walking on that surface. Each coin is worth 1 point.",
|
|
9
|
+
"entities": [
|
|
10
|
+
{
|
|
11
|
+
"id": "{id}-1",
|
|
12
|
+
"components": {
|
|
13
|
+
"transform": {
|
|
14
|
+
"dx": -80,
|
|
15
|
+
"dy": -40
|
|
16
|
+
},
|
|
17
|
+
"body": {
|
|
18
|
+
"type": "static",
|
|
19
|
+
"width": 20,
|
|
20
|
+
"height": 20,
|
|
21
|
+
"sensor": true
|
|
22
|
+
},
|
|
23
|
+
"sprite": {
|
|
24
|
+
"shape": "circle",
|
|
25
|
+
"color": "#ffd166",
|
|
26
|
+
"image": "assets/coin.svg",
|
|
27
|
+
"frameWidth": 20,
|
|
28
|
+
"idle": "0-3",
|
|
29
|
+
"layer": 1
|
|
30
|
+
},
|
|
31
|
+
"collectible": {
|
|
32
|
+
"score": 1
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "{id}-2",
|
|
38
|
+
"components": {
|
|
39
|
+
"transform": {
|
|
40
|
+
"dx": -40,
|
|
41
|
+
"dy": -40
|
|
42
|
+
},
|
|
43
|
+
"body": {
|
|
44
|
+
"type": "static",
|
|
45
|
+
"width": 20,
|
|
46
|
+
"height": 20,
|
|
47
|
+
"sensor": true
|
|
48
|
+
},
|
|
49
|
+
"sprite": {
|
|
50
|
+
"shape": "circle",
|
|
51
|
+
"color": "#ffd166",
|
|
52
|
+
"image": "assets/coin.svg",
|
|
53
|
+
"frameWidth": 20,
|
|
54
|
+
"idle": "0-3",
|
|
55
|
+
"layer": 1
|
|
56
|
+
},
|
|
57
|
+
"collectible": {
|
|
58
|
+
"score": 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "{id}-3",
|
|
64
|
+
"components": {
|
|
65
|
+
"transform": {
|
|
66
|
+
"dx": 0,
|
|
67
|
+
"dy": -40
|
|
68
|
+
},
|
|
69
|
+
"body": {
|
|
70
|
+
"type": "static",
|
|
71
|
+
"width": 20,
|
|
72
|
+
"height": 20,
|
|
73
|
+
"sensor": true
|
|
74
|
+
},
|
|
75
|
+
"sprite": {
|
|
76
|
+
"shape": "circle",
|
|
77
|
+
"color": "#ffd166",
|
|
78
|
+
"image": "assets/coin.svg",
|
|
79
|
+
"frameWidth": 20,
|
|
80
|
+
"idle": "0-3",
|
|
81
|
+
"layer": 1
|
|
82
|
+
},
|
|
83
|
+
"collectible": {
|
|
84
|
+
"score": 1
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "{id}-4",
|
|
90
|
+
"components": {
|
|
91
|
+
"transform": {
|
|
92
|
+
"dx": 40,
|
|
93
|
+
"dy": -40
|
|
94
|
+
},
|
|
95
|
+
"body": {
|
|
96
|
+
"type": "static",
|
|
97
|
+
"width": 20,
|
|
98
|
+
"height": 20,
|
|
99
|
+
"sensor": true
|
|
100
|
+
},
|
|
101
|
+
"sprite": {
|
|
102
|
+
"shape": "circle",
|
|
103
|
+
"color": "#ffd166",
|
|
104
|
+
"image": "assets/coin.svg",
|
|
105
|
+
"frameWidth": 20,
|
|
106
|
+
"idle": "0-3",
|
|
107
|
+
"layer": 1
|
|
108
|
+
},
|
|
109
|
+
"collectible": {
|
|
110
|
+
"score": 1
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "{id}-5",
|
|
116
|
+
"components": {
|
|
117
|
+
"transform": {
|
|
118
|
+
"dx": 80,
|
|
119
|
+
"dy": -40
|
|
120
|
+
},
|
|
121
|
+
"body": {
|
|
122
|
+
"type": "static",
|
|
123
|
+
"width": 20,
|
|
124
|
+
"height": 20,
|
|
125
|
+
"sensor": true
|
|
126
|
+
},
|
|
127
|
+
"sprite": {
|
|
128
|
+
"shape": "circle",
|
|
129
|
+
"color": "#ffd166",
|
|
130
|
+
"image": "assets/coin.svg",
|
|
131
|
+
"frameWidth": 20,
|
|
132
|
+
"idle": "0-3",
|
|
133
|
+
"layer": 1
|
|
134
|
+
},
|
|
135
|
+
"collectible": {
|
|
136
|
+
"score": 1
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"files": [
|
|
142
|
+
"assets/coin.svg"
|
|
143
|
+
],
|
|
144
|
+
"effects": {
|
|
145
|
+
"collect": {
|
|
146
|
+
"sound": "coin",
|
|
147
|
+
"particles": {
|
|
148
|
+
"count": 14,
|
|
149
|
+
"color": "#ffd166",
|
|
150
|
+
"speed": 140,
|
|
151
|
+
"life": 0.45,
|
|
152
|
+
"size": 3,
|
|
153
|
+
"gravity": 200
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="20" viewBox="0 0 100 20">
|
|
2
|
+
<!-- Old wooden planks with visible cracks: it looks like it won't hold. -->
|
|
3
|
+
<rect width="100" height="20" rx="3" fill="#a0714b"/>
|
|
4
|
+
<rect width="100" height="4" rx="2" fill="#c4935f"/>
|
|
5
|
+
<rect y="16" width="100" height="4" fill="#7a5233"/>
|
|
6
|
+
<rect x="24" y="4" width="2" height="12" fill="#7a5233"/>
|
|
7
|
+
<rect x="49" y="4" width="2" height="12" fill="#7a5233"/>
|
|
8
|
+
<rect x="74" y="4" width="2" height="12" fill="#7a5233"/>
|
|
9
|
+
<path d="M36 4 l3 5 l-4 3 l3 4" stroke="#3d2716" stroke-width="1.5" fill="none"/>
|
|
10
|
+
<path d="M62 4 l-2 4 l4 3 l-3 5" stroke="#3d2716" stroke-width="1.5" fill="none"/>
|
|
11
|
+
<circle cx="6" cy="10" r="1.5" fill="#4b5263"/>
|
|
12
|
+
<circle cx="94" cy="10" r="1.5" fill="#4b5263"/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "falling-platform",
|
|
3
|
+
"description": "A cracked platform that shakes when stood on, falls after params.delay seconds, then comes back after params.respawn seconds.",
|
|
4
|
+
"tags": [
|
|
5
|
+
"platformer",
|
|
6
|
+
"movement",
|
|
7
|
+
"script"
|
|
8
|
+
],
|
|
9
|
+
"usage": "--at x,y is the platform's center. Its state.phase is idle, shaking, falling or gone, so tests can check it.",
|
|
10
|
+
"entities": [
|
|
11
|
+
{
|
|
12
|
+
"id": "{id}",
|
|
13
|
+
"components": {
|
|
14
|
+
"transform": {
|
|
15
|
+
"dx": 0,
|
|
16
|
+
"dy": 0
|
|
17
|
+
},
|
|
18
|
+
"body": {
|
|
19
|
+
"type": "static",
|
|
20
|
+
"width": 100,
|
|
21
|
+
"height": 20
|
|
22
|
+
},
|
|
23
|
+
"sprite": {
|
|
24
|
+
"color": "#a0714b",
|
|
25
|
+
"image": "assets/falling-platform.svg"
|
|
26
|
+
},
|
|
27
|
+
"script": {
|
|
28
|
+
"src": "scripts/falling-platform.ts",
|
|
29
|
+
"params": {
|
|
30
|
+
"delay": 0.5,
|
|
31
|
+
"respawn": 3
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"files": [
|
|
38
|
+
"scripts/falling-platform.ts",
|
|
39
|
+
"assets/falling-platform.svg"
|
|
40
|
+
],
|
|
41
|
+
"effects": {
|
|
42
|
+
"platform-crack": {
|
|
43
|
+
"sound": "thud",
|
|
44
|
+
"particles": {
|
|
45
|
+
"count": 8,
|
|
46
|
+
"color": "#a0714b",
|
|
47
|
+
"speed": 60,
|
|
48
|
+
"direction": 90,
|
|
49
|
+
"spread": 120,
|
|
50
|
+
"life": 0.5,
|
|
51
|
+
"size": 3
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"at": [
|
|
3
|
+
400,
|
|
4
|
+
400
|
|
5
|
+
],
|
|
6
|
+
"tests": [
|
|
7
|
+
{
|
|
8
|
+
"name": "shakes, falls, and comes back",
|
|
9
|
+
"playerAt": [
|
|
10
|
+
400,
|
|
11
|
+
372
|
|
12
|
+
],
|
|
13
|
+
"duration": "5s",
|
|
14
|
+
"assert": [
|
|
15
|
+
"falling-platform.state.phase == \"shaking\" at 0.3s",
|
|
16
|
+
"falling-platform.state.phase == \"falling\" at 0.8s",
|
|
17
|
+
"player.grounded == true at 2s",
|
|
18
|
+
"player.y > 450 at 2s",
|
|
19
|
+
"falling-platform.state.phase == \"idle\" at 4.8s",
|
|
20
|
+
"falling-platform.y == 400 at 4.8s"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Script } from "@ludra/core";
|
|
2
|
+
|
|
3
|
+
const GRAVITY = 1800;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* idle → (a player stands on it) → shaking for params.delay s → falling →
|
|
7
|
+
* gone (below the world) for params.respawn s → back to idle at its start.
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
start({ self }) {
|
|
11
|
+
self.state.phase = "idle";
|
|
12
|
+
self.state.homeX = self.x;
|
|
13
|
+
self.state.homeY = self.y;
|
|
14
|
+
},
|
|
15
|
+
update({ self, world, params, dt }) {
|
|
16
|
+
const homeX = self.state.homeX as number;
|
|
17
|
+
const homeY = self.state.homeY as number;
|
|
18
|
+
switch (self.state.phase) {
|
|
19
|
+
case "idle": {
|
|
20
|
+
const player = world.player();
|
|
21
|
+
const standing = player && player.grounded && player.y < self.y && self.touches(player);
|
|
22
|
+
if (standing) {
|
|
23
|
+
self.state.phase = "shaking";
|
|
24
|
+
self.state.timer = params.delay as number;
|
|
25
|
+
world.emit("platform-crack");
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case "shaking":
|
|
30
|
+
self.state.timer = (self.state.timer as number) - dt;
|
|
31
|
+
self.x = homeX + (world.random() - 0.5) * 3;
|
|
32
|
+
if ((self.state.timer as number) <= 0) {
|
|
33
|
+
self.x = homeX;
|
|
34
|
+
self.state.phase = "falling";
|
|
35
|
+
self.state.speed = 0;
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
case "falling":
|
|
39
|
+
self.state.speed = (self.state.speed as number) + GRAVITY * dt;
|
|
40
|
+
self.y += (self.state.speed as number) * dt;
|
|
41
|
+
if (self.y > world.height + 200) {
|
|
42
|
+
self.state.phase = "gone";
|
|
43
|
+
self.state.timer = params.respawn as number;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case "gone":
|
|
47
|
+
self.state.timer = (self.state.timer as number) - dt;
|
|
48
|
+
if ((self.state.timer as number) <= 0) {
|
|
49
|
+
self.x = homeX;
|
|
50
|
+
self.y = homeY;
|
|
51
|
+
self.state.phase = "idle";
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
} satisfies Script;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="14" viewBox="0 0 44 14">
|
|
2
|
+
<!-- A springy jump pad. -->
|
|
3
|
+
<rect x="2" y="10" width="40" height="4" rx="1" fill="#4b5263"/>
|
|
4
|
+
<path d="M8 10 l4 -4 l4 4 l4 -4 l4 4 l4 -4 l4 4 l4 -4" stroke="#cfd8ff" stroke-width="2" fill="none"/>
|
|
5
|
+
<rect x="0" y="0" width="44" height="4" rx="2" fill="#ff7eb6"/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jump-pad",
|
|
3
|
+
"description": "A pad that launches players straight up when they step on it.",
|
|
4
|
+
"tags": [
|
|
5
|
+
"platformer",
|
|
6
|
+
"movement",
|
|
7
|
+
"script"
|
|
8
|
+
],
|
|
9
|
+
"usage": "--at x,y sets the pad on the surface whose top is y. Launch height is about power² / (2 · gravity): power 900 gives ~225px with the default gravity 1800.",
|
|
10
|
+
"entities": [
|
|
11
|
+
{
|
|
12
|
+
"id": "{id}",
|
|
13
|
+
"components": {
|
|
14
|
+
"transform": {
|
|
15
|
+
"dx": 0,
|
|
16
|
+
"dy": -7
|
|
17
|
+
},
|
|
18
|
+
"body": {
|
|
19
|
+
"type": "static",
|
|
20
|
+
"width": 44,
|
|
21
|
+
"height": 14,
|
|
22
|
+
"sensor": true
|
|
23
|
+
},
|
|
24
|
+
"sprite": {
|
|
25
|
+
"color": "#ff7eb6",
|
|
26
|
+
"image": "assets/jump-pad.svg",
|
|
27
|
+
"layer": 1
|
|
28
|
+
},
|
|
29
|
+
"script": {
|
|
30
|
+
"src": "scripts/jump-pad.ts",
|
|
31
|
+
"params": {
|
|
32
|
+
"power": 900
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"files": [
|
|
39
|
+
"scripts/jump-pad.ts",
|
|
40
|
+
"assets/jump-pad.svg"
|
|
41
|
+
],
|
|
42
|
+
"effects": {
|
|
43
|
+
"boing": {
|
|
44
|
+
"sound": "jump",
|
|
45
|
+
"particles": {
|
|
46
|
+
"count": 12,
|
|
47
|
+
"color": "#ff7eb6",
|
|
48
|
+
"speed": 160,
|
|
49
|
+
"direction": -90,
|
|
50
|
+
"spread": 70,
|
|
51
|
+
"life": 0.4,
|
|
52
|
+
"size": 3
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Script } from "@ludra/core";
|
|
2
|
+
|
|
3
|
+
/** Launches whatever lands on it straight up. params.power: launch speed in px/s. */
|
|
4
|
+
export default {
|
|
5
|
+
touch({ world, params }, other) {
|
|
6
|
+
if (!other.has("platformer")) return;
|
|
7
|
+
other.vy = -(params.power as number);
|
|
8
|
+
world.emit("boing");
|
|
9
|
+
},
|
|
10
|
+
} satisfies Script;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="120" viewBox="0 0 28 120">
|
|
2
|
+
<!-- A locked wooden door with iron bands and a keyhole. -->
|
|
3
|
+
<rect width="28" height="120" rx="4" fill="#7a4e2d"/>
|
|
4
|
+
<rect x="3" width="2" height="120" fill="#5e3a20"/>
|
|
5
|
+
<rect x="13" width="2" height="120" fill="#5e3a20"/>
|
|
6
|
+
<rect x="23" width="2" height="120" fill="#5e3a20"/>
|
|
7
|
+
<rect y="16" width="28" height="7" fill="#4b5263"/>
|
|
8
|
+
<rect y="97" width="28" height="7" fill="#4b5263"/>
|
|
9
|
+
<rect x="7" y="52" width="14" height="18" rx="3" fill="#f2c14e"/>
|
|
10
|
+
<circle cx="14" cy="59" r="2.6" fill="#2b1a0e"/>
|
|
11
|
+
<rect x="13" y="60" width="2" height="6" fill="#2b1a0e"/>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
|
2
|
+
<!-- A gold key. -->
|
|
3
|
+
<circle cx="8" cy="12" r="6" fill="#e0a526"/>
|
|
4
|
+
<circle cx="8" cy="12" r="2.6" fill="#1b2140"/>
|
|
5
|
+
<rect x="12" y="10.5" width="11" height="3" rx="1" fill="#e0a526"/>
|
|
6
|
+
<rect x="18" y="13" width="2.5" height="4" fill="#e0a526"/>
|
|
7
|
+
<rect x="21" y="13" width="2" height="3" fill="#e0a526"/>
|
|
8
|
+
<circle cx="6" cy="9.5" r="1.4" fill="#fff3b0"/>
|
|
9
|
+
</svg>
|