create-bloop 0.0.18 → 0.0.19
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/package.json
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { unwrap } from "@bloopjs/bloop";
|
|
2
|
-
import type {
|
|
3
|
-
Color,
|
|
4
|
-
QuadNode,
|
|
5
|
-
SceneNode,
|
|
6
|
-
TextNode,
|
|
7
|
-
Toodle,
|
|
8
|
-
} from "@bloopjs/toodle";
|
|
2
|
+
import type { Color, Toodle } from "@bloopjs/toodle";
|
|
9
3
|
import { Colors } from "@bloopjs/toodle";
|
|
10
4
|
import {
|
|
11
5
|
BLOCK_SIZE,
|
|
@@ -20,253 +14,200 @@ import type { game, Player, Pose } from "./game";
|
|
|
20
14
|
// Placeholder colors until sprites are added
|
|
21
15
|
const MARIO_COLOR = Colors.web.red;
|
|
22
16
|
const LUIGI_COLOR = Colors.web.green;
|
|
23
|
-
const BLOCK_COLOR = Colors.web.sienna;
|
|
24
17
|
const COIN_COLOR = Colors.web.gold;
|
|
25
18
|
const GROUND_COLOR = Colors.web.saddleBrown;
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
p2: PoseQuads;
|
|
41
|
-
viewport: SceneNode;
|
|
42
|
-
p1Score: TextNode;
|
|
43
|
-
p2Score: TextNode;
|
|
44
|
-
// Title elements (under titleScreen)
|
|
45
|
-
titleText: TextNode;
|
|
46
|
-
subtitleText: TextNode;
|
|
47
|
-
}
|
|
20
|
+
// Map pose to texture name
|
|
21
|
+
const POSE_TEXTURES: Record<Pose, string> = {
|
|
22
|
+
idle: "marioIdle",
|
|
23
|
+
run: "marioWalk",
|
|
24
|
+
jump: "marioJump",
|
|
25
|
+
skid: "marioSkid",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function draw(g: typeof game, toodle: Toodle) {
|
|
29
|
+
const { bag } = g.context;
|
|
30
|
+
const isMobile = toodle.resolution.width < toodle.resolution.height;
|
|
31
|
+
|
|
32
|
+
toodle.startFrame();
|
|
48
33
|
|
|
49
|
-
export function createDrawState(toodle: Toodle): DrawState {
|
|
50
34
|
const root = toodle.Node({ scale: 3 });
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
36
|
+
if (bag.phase !== "playing") {
|
|
37
|
+
// Title screen
|
|
38
|
+
const titleScreen = root.add(toodle.Node({}));
|
|
39
|
+
|
|
40
|
+
titleScreen.add(
|
|
41
|
+
toodle.Text("Roboto", "Mario Rollback", {
|
|
42
|
+
fontSize: 20,
|
|
43
|
+
align: "center",
|
|
44
|
+
color: { r: 1, g: 1, b: 1, a: 1 },
|
|
45
|
+
position: { x: 0, y: 20 },
|
|
46
|
+
size: {
|
|
47
|
+
width: toodle.resolution.width - 100,
|
|
48
|
+
height: toodle.resolution.height,
|
|
49
|
+
},
|
|
50
|
+
shrinkToFit: {
|
|
51
|
+
minFontSize: 4,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
68
55
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
const subtitleText =
|
|
57
|
+
bag.phase === "waiting"
|
|
58
|
+
? "Waiting for opponent..."
|
|
59
|
+
: isMobile
|
|
60
|
+
? "Tap to find opponent"
|
|
61
|
+
: "[Enter/Click] Online [Space] Local";
|
|
62
|
+
|
|
63
|
+
const text = titleScreen.add(
|
|
64
|
+
toodle.Text("Roboto", subtitleText, {
|
|
65
|
+
fontSize: 10,
|
|
66
|
+
color: { r: 1, g: 1, b: 1, a: 1 },
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
// Game screen
|
|
71
|
+
const gameScreen = root.add(toodle.Node({}));
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
79
|
-
);
|
|
73
|
+
const viewport = toodle.Node({
|
|
74
|
+
size: {
|
|
75
|
+
width: toodle.resolution.width,
|
|
76
|
+
height: toodle.resolution.height,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
// Ground
|
|
81
|
+
gameScreen.add(
|
|
82
|
+
toodle.shapes.Rect({
|
|
83
|
+
size: { width: viewport.size!.width, height: 40 },
|
|
84
|
+
position: { x: 0, y: GROUND_Y - 20 },
|
|
85
|
+
color: GROUND_COLOR,
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
// Block
|
|
90
|
+
gameScreen.add(
|
|
91
|
+
toodle.Quad("brick", {
|
|
92
|
+
size: { width: BLOCK_SIZE, height: BLOCK_SIZE },
|
|
93
|
+
position: { x: bag.block.x, y: BLOCK_Y + BLOCK_SIZE / 2 },
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
size: { width: PLAYER_WIDTH, height: PLAYER_HEIGHT },
|
|
109
|
-
region: { x: 0, y: 0, width: PLAYER_WIDTH, height: PLAYER_HEIGHT },
|
|
110
|
-
color,
|
|
97
|
+
// Coin
|
|
98
|
+
if (bag.coin.visible) {
|
|
99
|
+
const coinColor =
|
|
100
|
+
bag.coin.winner === 1
|
|
101
|
+
? MARIO_COLOR
|
|
102
|
+
: bag.coin.winner === 2
|
|
103
|
+
? LUIGI_COLOR
|
|
104
|
+
: COIN_COLOR;
|
|
105
|
+
|
|
106
|
+
gameScreen.add(
|
|
107
|
+
toodle.shapes.Circle({
|
|
108
|
+
radius: COIN_SIZE / 2,
|
|
109
|
+
color: coinColor,
|
|
110
|
+
position: { x: bag.coin.x, y: bag.coin.y },
|
|
111
111
|
}),
|
|
112
112
|
);
|
|
113
|
-
quad.isActive = false; // Start inactive, draw will activate the right one
|
|
114
|
-
poses[pose] = quad;
|
|
115
113
|
}
|
|
116
|
-
return poses;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const p1 = createPoseQuads();
|
|
120
|
-
const p2 = createPoseQuads(LUIGI_COLOR);
|
|
121
|
-
|
|
122
|
-
const p1Score = gameScreen.add(
|
|
123
|
-
toodle.Text("Roboto", "P9: 0", {
|
|
124
|
-
fontSize: 16,
|
|
125
|
-
color: MARIO_COLOR,
|
|
126
|
-
}),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const p2Score = gameScreen.add(
|
|
130
|
-
toodle.Text("Roboto", "P2: 0", {
|
|
131
|
-
fontSize: 16,
|
|
132
|
-
color: LUIGI_COLOR,
|
|
133
|
-
}),
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const viewport = toodle.Node({
|
|
137
|
-
size: { width: toodle.resolution.width, height: toodle.resolution.height },
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
root,
|
|
142
|
-
titleScreen,
|
|
143
|
-
gameScreen,
|
|
144
|
-
ground,
|
|
145
|
-
block,
|
|
146
|
-
coin,
|
|
147
|
-
p1,
|
|
148
|
-
p2,
|
|
149
|
-
p1Score,
|
|
150
|
-
p2Score,
|
|
151
|
-
titleText,
|
|
152
|
-
subtitleText,
|
|
153
|
-
viewport,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function draw(g: typeof game, toodle: Toodle, state: DrawState) {
|
|
158
|
-
const { bag } = g.context;
|
|
159
|
-
|
|
160
|
-
// Toggle screens
|
|
161
|
-
state.titleScreen.isActive = bag.phase !== "playing";
|
|
162
|
-
state.gameScreen.isActive = bag.phase === "playing";
|
|
163
|
-
|
|
164
|
-
// Update title text based on phase
|
|
165
|
-
if (bag.phase === "title") {
|
|
166
|
-
const isMobile = toodle.resolution.width < toodle.resolution.height;
|
|
167
|
-
state.subtitleText.text = isMobile
|
|
168
|
-
? "Tap to find opponent"
|
|
169
|
-
: "[Enter/Click] Online [Space] Local";
|
|
170
|
-
} else if (bag.phase === "waiting") {
|
|
171
|
-
state.subtitleText.text = "Waiting for opponent...";
|
|
172
|
-
}
|
|
173
114
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
state.coin.position = {
|
|
179
|
-
x: bag.coin.x,
|
|
180
|
-
y: bag.coin.y,
|
|
181
|
-
};
|
|
182
|
-
state.coin.isActive = bag.coin.visible;
|
|
183
|
-
state.coin.color =
|
|
184
|
-
bag.coin.winner === 1
|
|
185
|
-
? MARIO_COLOR
|
|
186
|
-
: bag.coin.winner === 2
|
|
187
|
-
? LUIGI_COLOR
|
|
188
|
-
: COIN_COLOR;
|
|
189
|
-
// Update player sprites
|
|
190
|
-
updatePlayerQuads(state.p1, bag.p1);
|
|
191
|
-
updatePlayerQuads(state.p2, bag.p2);
|
|
115
|
+
// Players
|
|
116
|
+
const p1Quad = drawPlayer(toodle, gameScreen, bag.p1);
|
|
117
|
+
const p2Quad = drawPlayer(toodle, gameScreen, bag.p2, LUIGI_COLOR);
|
|
192
118
|
|
|
119
|
+
// Scores
|
|
193
120
|
const padding = 20;
|
|
194
121
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
state.p1Score.setBounds({
|
|
206
|
-
left: state.viewport.bounds.left + padding,
|
|
207
|
-
top: state.viewport.bounds.top - padding,
|
|
122
|
+
const p1Score = gameScreen.add(
|
|
123
|
+
toodle.Text("Roboto", `P1: ${bag.p1.score}`, {
|
|
124
|
+
fontSize: 16,
|
|
125
|
+
color: MARIO_COLOR,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
p1Score.setBounds({
|
|
129
|
+
left: viewport.bounds.left + padding,
|
|
130
|
+
top: viewport.bounds.top - padding,
|
|
208
131
|
});
|
|
209
132
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
133
|
+
const p2Score = gameScreen.add(
|
|
134
|
+
toodle.Text("Roboto", `P2: ${bag.p2.score}`, {
|
|
135
|
+
fontSize: 16,
|
|
136
|
+
color: LUIGI_COLOR,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
p2Score.setBounds({
|
|
140
|
+
right: viewport.bounds.right - padding,
|
|
141
|
+
top: viewport.bounds.top - padding,
|
|
213
142
|
});
|
|
214
|
-
}
|
|
215
143
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
144
|
+
// Debug hitboxes
|
|
145
|
+
if (bag.debugHitboxes) {
|
|
146
|
+
toodle.draw(root);
|
|
147
|
+
drawHitbox(toodle, p1Quad.bounds);
|
|
148
|
+
drawHitbox(toodle, p2Quad.bounds);
|
|
149
|
+
// Need to get block bounds - draw a hitbox at block position
|
|
150
|
+
drawHitbox(toodle, {
|
|
151
|
+
left: bag.block.x - BLOCK_SIZE / 2,
|
|
152
|
+
right: bag.block.x + BLOCK_SIZE / 2,
|
|
153
|
+
bottom: BLOCK_Y,
|
|
154
|
+
top: BLOCK_Y + BLOCK_SIZE,
|
|
155
|
+
});
|
|
156
|
+
if (bag.coin.visible) {
|
|
157
|
+
drawHitbox(toodle, {
|
|
158
|
+
left: bag.coin.x - COIN_SIZE / 2,
|
|
159
|
+
right: bag.coin.x + COIN_SIZE / 2,
|
|
160
|
+
bottom: bag.coin.y - COIN_SIZE / 2,
|
|
161
|
+
top: bag.coin.y + COIN_SIZE / 2,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
toodle.endFrame();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
220
167
|
}
|
|
168
|
+
|
|
169
|
+
toodle.draw(root);
|
|
221
170
|
toodle.endFrame();
|
|
222
171
|
}
|
|
223
172
|
|
|
224
|
-
/**
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Update position
|
|
237
|
-
quad.position = {
|
|
238
|
-
x: player.x,
|
|
239
|
-
y: player.y + PLAYER_HEIGHT / 2,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// Update flip based on facing direction
|
|
243
|
-
quad.flipX = player.facingDir === -1;
|
|
244
|
-
|
|
245
|
-
// Update region from flipbook frame using static flipbooks + bag AnimState
|
|
246
|
-
const flipbook = unwrap(
|
|
247
|
-
player.anims[pose],
|
|
248
|
-
`No runtime flipbook for pose ${pose}`,
|
|
249
|
-
);
|
|
250
|
-
const frameIndex = flipbook.frameIndex % flipbook.frames.length;
|
|
251
|
-
const frame = flipbook.frames[frameIndex];
|
|
252
|
-
quad.region.x = frame.pos.x;
|
|
253
|
-
quad.region.y = frame.pos.y;
|
|
254
|
-
quad.region.width = frame.width;
|
|
255
|
-
quad.region.height = frame.height;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
173
|
+
/** Draws a player sprite and returns the quad for hitbox drawing */
|
|
174
|
+
function drawPlayer(
|
|
175
|
+
toodle: Toodle,
|
|
176
|
+
parent: ReturnType<Toodle["Node"]>,
|
|
177
|
+
player: Player,
|
|
178
|
+
color?: Color,
|
|
179
|
+
) {
|
|
180
|
+
const pose = player.pose;
|
|
181
|
+
const textureName = POSE_TEXTURES[pose];
|
|
258
182
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
183
|
+
const quad = parent.add(
|
|
184
|
+
toodle.Quad(textureName, {
|
|
185
|
+
size: { width: PLAYER_WIDTH, height: PLAYER_HEIGHT },
|
|
186
|
+
region: { x: 0, y: 0, width: PLAYER_WIDTH, height: PLAYER_HEIGHT },
|
|
187
|
+
color,
|
|
188
|
+
position: {
|
|
189
|
+
x: player.x,
|
|
190
|
+
y: player.y + PLAYER_HEIGHT / 2,
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
262
194
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
toodle.draw(makeHitbox(toodle, state.block.bounds));
|
|
195
|
+
// Update flip based on facing direction
|
|
196
|
+
quad.flipX = player.facingDir === -1;
|
|
266
197
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
198
|
+
// Update region from flipbook frame
|
|
199
|
+
const flipbook = unwrap(
|
|
200
|
+
player.anims[pose],
|
|
201
|
+
`No runtime flipbook for pose ${pose}`,
|
|
202
|
+
);
|
|
203
|
+
const frameIndex = flipbook.frameIndex % flipbook.frames.length;
|
|
204
|
+
const frame = flipbook.frames[frameIndex];
|
|
205
|
+
quad.region.x = frame.pos.x;
|
|
206
|
+
quad.region.y = frame.pos.y;
|
|
207
|
+
quad.region.width = frame.width;
|
|
208
|
+
quad.region.height = frame.height;
|
|
209
|
+
|
|
210
|
+
return quad;
|
|
270
211
|
}
|
|
271
212
|
|
|
272
213
|
const hitboxDefaultColor = { r: 1, g: 0, b: 1, a: 0.4 };
|
|
@@ -302,19 +243,21 @@ fn frag(vertex: VertexOutput) -> @location(0) vec4f {
|
|
|
302
243
|
return hitboxShader;
|
|
303
244
|
}
|
|
304
245
|
|
|
305
|
-
function
|
|
246
|
+
function drawHitbox(
|
|
306
247
|
toodle: Toodle,
|
|
307
248
|
bounds: { left: number; right: number; top: number; bottom: number },
|
|
308
249
|
color: Color = hitboxDefaultColor,
|
|
309
250
|
) {
|
|
310
|
-
|
|
311
|
-
.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
251
|
+
toodle.draw(
|
|
252
|
+
toodle.shapes
|
|
253
|
+
.Rect({
|
|
254
|
+
color,
|
|
255
|
+
size: {
|
|
256
|
+
width: bounds.right - bounds.left,
|
|
257
|
+
height: bounds.top - bounds.bottom,
|
|
258
|
+
},
|
|
259
|
+
shader: getHitboxShader(toodle),
|
|
260
|
+
})
|
|
261
|
+
.setBounds(bounds),
|
|
262
|
+
);
|
|
320
263
|
}
|
|
@@ -100,6 +100,12 @@ game.system("session-watcher", {
|
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
game.system("screen", {
|
|
104
|
+
resize({ screen }) {
|
|
105
|
+
console.log("resize", { width: screen.width, height: screen.height });
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
103
109
|
game.system(
|
|
104
110
|
"title-screen",
|
|
105
111
|
PhaseSystem("title", {
|
|
@@ -2,11 +2,9 @@ import "./style.css";
|
|
|
2
2
|
import { Toodle } from "@bloopjs/toodle";
|
|
3
3
|
import { start } from "@bloopjs/web";
|
|
4
4
|
import { createChromaticAberrationEffect } from "./chromatic-aberration";
|
|
5
|
-
import {
|
|
5
|
+
import { draw } from "./draw";
|
|
6
6
|
import { game } from "./game";
|
|
7
7
|
|
|
8
|
-
let draw = drawFn;
|
|
9
|
-
|
|
10
8
|
// boot up the game
|
|
11
9
|
const app = await start({
|
|
12
10
|
game,
|
|
@@ -22,9 +20,9 @@ if (import.meta.hot) {
|
|
|
22
20
|
await app.acceptHmr(newModule?.game);
|
|
23
21
|
});
|
|
24
22
|
|
|
25
|
-
import.meta.hot.accept("./draw", async (newModule) => {
|
|
26
|
-
|
|
27
|
-
});
|
|
23
|
+
// import.meta.hot.accept("./draw", async (newModule) => {
|
|
24
|
+
// draw = newModule?.draw;
|
|
25
|
+
// });
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
const canvas = app.canvas;
|
|
@@ -61,10 +59,8 @@ await toodle.assets.loadFont(
|
|
|
61
59
|
new URL("https://toodle.gg/fonts/Roboto-Regular-msdf.json"),
|
|
62
60
|
);
|
|
63
61
|
|
|
64
|
-
const drawState = createDrawState(toodle);
|
|
65
|
-
|
|
66
62
|
requestAnimationFrame(function frame() {
|
|
67
|
-
draw(app.game, toodle
|
|
63
|
+
draw(app.game, toodle);
|
|
68
64
|
requestAnimationFrame(frame);
|
|
69
65
|
});
|
|
70
66
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./style.css";
|
|
2
2
|
import { Toodle } from "@bloopjs/toodle";
|
|
3
3
|
import { start } from "@bloopjs/web";
|
|
4
|
-
import {
|
|
4
|
+
import { draw } from "./draw";
|
|
5
5
|
import { game } from "./game";
|
|
6
6
|
|
|
7
7
|
const wasmUrl = import.meta.env.DEV
|
|
@@ -86,10 +86,8 @@ async function loadTape(bytes: Uint8Array, fileName: string) {
|
|
|
86
86
|
new URL("https://toodle.gg/fonts/Roboto-Regular-msdf.json"),
|
|
87
87
|
);
|
|
88
88
|
|
|
89
|
-
const drawState = createDrawState(toodle);
|
|
90
|
-
|
|
91
89
|
requestAnimationFrame(function frame() {
|
|
92
|
-
draw(app.game, toodle
|
|
90
|
+
draw(app.game, toodle);
|
|
93
91
|
requestAnimationFrame(frame);
|
|
94
92
|
});
|
|
95
93
|
|