create-bloop 0.0.19 → 0.0.20

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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bloop",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "Create a new Bloop game",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
@@ -5,11 +5,11 @@ export const MOVE_SPEED = 3;
5
5
  export const MAX_FALL_SPEED = 10;
6
6
 
7
7
  // World (0,0 is center of screen, Y+ is up)
8
- export const GROUND_Y = -100;
8
+ export const GROUND_Y = -80;
9
9
  export const BLOCK_Y = -10;
10
10
  export const BLOCK_SPEED = 1;
11
- export const BLOCK_MIN_X = -100;
12
- export const BLOCK_MAX_X = 100;
11
+ export const BLOCK_MIN_X = -55;
12
+ export const BLOCK_MAX_X = 55;
13
13
 
14
14
  // Player sizes
15
15
  export const PLAYER_WIDTH = 16;
@@ -18,8 +18,8 @@ export const BLOCK_SIZE = 16;
18
18
  export const COIN_SIZE = 12;
19
19
 
20
20
  // Starting positions
21
- export const P1_START_X = -60;
22
- export const P2_START_X = 60;
21
+ export const P1_START_X = -55;
22
+ export const P2_START_X = 55;
23
23
 
24
24
  // Animation timings
25
25
  export const COIN_VISIBLE_DURATION = 0.8; // seconds
@@ -31,7 +31,9 @@ export function draw(g: typeof game, toodle: Toodle) {
31
31
 
32
32
  toodle.startFrame();
33
33
 
34
- const root = toodle.Node({ scale: 3 });
34
+ const root = toodle.Node();
35
+
36
+ toodle.camera.zoom = 3;
35
37
 
36
38
  if (bag.phase !== "playing") {
37
39
  // Title screen
@@ -60,7 +62,7 @@ export function draw(g: typeof game, toodle: Toodle) {
60
62
  ? "Tap to find opponent"
61
63
  : "[Enter/Click] Online [Space] Local";
62
64
 
63
- const text = titleScreen.add(
65
+ titleScreen.add(
64
66
  toodle.Text("Roboto", subtitleText, {
65
67
  fontSize: 10,
66
68
  color: { r: 1, g: 1, b: 1, a: 1 },
@@ -78,13 +80,13 @@ export function draw(g: typeof game, toodle: Toodle) {
78
80
  });
79
81
 
80
82
  // Ground
81
- gameScreen.add(
83
+ const ground = gameScreen.add(
82
84
  toodle.shapes.Rect({
83
- size: { width: viewport.size!.width, height: 40 },
84
- position: { x: 0, y: GROUND_Y - 20 },
85
+ size: { width: viewport.size!.width, height: 1000 },
85
86
  color: GROUND_COLOR,
86
87
  }),
87
88
  );
89
+ ground.setBounds({ top: GROUND_Y });
88
90
 
89
91
  // Block
90
92
  gameScreen.add(
@@ -1,4 +1,4 @@
1
- import { Bloop } from "@bloopjs/bloop";
1
+ import { Bloop, Util } from "@bloopjs/bloop";
2
2
  import * as cfg from "./config";
3
3
  import type { Flipbook } from "./flipbook";
4
4
  import { FLIPBOOKS } from "./sprites";
@@ -100,12 +100,6 @@ 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
-
109
103
  game.system(
110
104
  "title-screen",
111
105
  PhaseSystem("title", {
@@ -1,167 +0,0 @@
1
- import "./style.css";
2
- import { Toodle } from "@bloopjs/toodle";
3
- import { start } from "@bloopjs/web";
4
- import { draw } from "./draw";
5
- import { game } from "./game";
6
-
7
- const wasmUrl = import.meta.env.DEV
8
- ? new URL("/bloop-wasm/bloop.wasm", window.location.href)
9
- : new URL("./bloop.wasm", import.meta.url);
10
-
11
- const DB_NAME = "mario-tapes";
12
- const STORE_NAME = "tapes";
13
- const TAPE_KEY = "last";
14
-
15
- const statusEl = document.getElementById("status")!;
16
- const inputEl = document.getElementById("tape-input") as HTMLInputElement;
17
- const replayBtn = document.getElementById("replay-last") as HTMLButtonElement;
18
-
19
- function openDB(): Promise<IDBDatabase> {
20
- return new Promise((resolve, reject) => {
21
- const request = indexedDB.open(DB_NAME, 1);
22
- request.onerror = () => reject(request.error);
23
- request.onsuccess = () => resolve(request.result);
24
- request.onupgradeneeded = () => {
25
- request.result.createObjectStore(STORE_NAME);
26
- };
27
- });
28
- }
29
-
30
- async function loadTape(bytes: Uint8Array, fileName: string) {
31
- statusEl.textContent = "Loading tape...";
32
-
33
- // Start the app with recording disabled (we're loading a tape)
34
- const app = await start({
35
- game,
36
- wasmUrl: wasmUrl,
37
- debugUi: true,
38
- startRecording: false,
39
- });
40
-
41
- import.meta.hot?.accept("./game", async (newModule) => {
42
- await app.acceptHmr(newModule?.game, {
43
- wasmUrl,
44
- files: ["./game"],
45
- });
46
- });
47
-
48
- // Load the tape and pause playback
49
- app.loadTape(bytes, {
50
- checkpointInterval: 30,
51
- });
52
- app.sim.pause();
53
-
54
- const canvas = app.canvas;
55
- if (!canvas) throw new Error("No canvas element found");
56
-
57
- const toodle = await Toodle.attach(canvas, {
58
- filter: "nearest",
59
- backend: "webgpu",
60
- limits: { textureArrayLayers: 5 },
61
- });
62
-
63
- toodle.clearColor = { r: 0.36, g: 0.58, b: 0.99, a: 1 };
64
-
65
- // Load sprites
66
- const spriteUrl = (name: string) =>
67
- new URL(
68
- `${import.meta.env.BASE_URL}sprites/${name}.png`,
69
- window.location.href,
70
- );
71
-
72
- await toodle.assets.registerBundle("main", {
73
- textures: {
74
- marioIdle: spriteUrl("MarioIdle"),
75
- marioWalk: spriteUrl("MarioWalk"),
76
- marioJump: spriteUrl("MarioJump"),
77
- marioSkid: spriteUrl("MarioSkid"),
78
- brick: spriteUrl("Brick"),
79
- ground: spriteUrl("Ground"),
80
- },
81
- autoLoad: true,
82
- });
83
-
84
- await toodle.assets.loadFont(
85
- "Roboto",
86
- new URL("https://toodle.gg/fonts/Roboto-Regular-msdf.json"),
87
- );
88
-
89
- requestAnimationFrame(function frame() {
90
- draw(app.game, toodle);
91
- requestAnimationFrame(frame);
92
- });
93
-
94
- statusEl.textContent = `Loaded tape: ${fileName}. Press Escape to toggle debug UI.`;
95
-
96
- // Hide the file input and show the game
97
- inputEl.style.display = "none";
98
- replayBtn.style.display = "none";
99
- document.querySelector(".container")!.remove();
100
- }
101
-
102
- async function saveTapeToStorage(
103
- bytes: Uint8Array,
104
- fileName: string,
105
- ): Promise<void> {
106
- const db = await openDB();
107
- return new Promise((resolve, reject) => {
108
- const tx = db.transaction(STORE_NAME, "readwrite");
109
- tx.objectStore(STORE_NAME).put({ bytes, fileName }, TAPE_KEY);
110
- tx.oncomplete = () => resolve();
111
- tx.onerror = () => reject(tx.error);
112
- });
113
- }
114
-
115
- async function loadTapeFromStorage(): Promise<{
116
- bytes: Uint8Array;
117
- fileName: string;
118
- } | null> {
119
- try {
120
- const db = await openDB();
121
- return new Promise((resolve, reject) => {
122
- const tx = db.transaction(STORE_NAME, "readonly");
123
- const request = tx.objectStore(STORE_NAME).get(TAPE_KEY);
124
- request.onsuccess = () => resolve(request.result ?? null);
125
- request.onerror = () => reject(request.error);
126
- });
127
- } catch {
128
- return null;
129
- }
130
- }
131
-
132
- // Check for saved tape on page load
133
- loadTapeFromStorage().then((savedTape) => {
134
- if (savedTape) {
135
- replayBtn.style.display = "block";
136
- replayBtn.textContent = `Replay last tape (${savedTape.fileName})`;
137
- }
138
- });
139
-
140
- replayBtn.addEventListener("click", async () => {
141
- const saved = await loadTapeFromStorage();
142
- if (!saved) {
143
- statusEl.textContent = "No saved tape found";
144
- return;
145
- }
146
-
147
- try {
148
- await loadTape(saved.bytes, saved.fileName);
149
- } catch (err) {
150
- statusEl.textContent = `Error loading tape: ${err}`;
151
- console.error(err);
152
- }
153
- });
154
-
155
- inputEl.addEventListener("change", async () => {
156
- const file = inputEl.files?.[0];
157
- if (!file) return;
158
-
159
- try {
160
- const bytes = new Uint8Array(await file.arrayBuffer());
161
- await saveTapeToStorage(bytes, file.name);
162
- await loadTape(bytes, file.name);
163
- } catch (err) {
164
- statusEl.textContent = `Error loading tape: ${err}`;
165
- console.error(err);
166
- }
167
- });
@@ -1,68 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Load Tape - Mario</title>
7
- <style>
8
- body {
9
- margin: 0;
10
- padding: 20px;
11
- font-family: system-ui, sans-serif;
12
- background: #1a1a2e;
13
- color: #eee;
14
- }
15
- .container {
16
- max-width: 600px;
17
- margin: 0 auto;
18
- }
19
- h1 {
20
- margin-bottom: 20px;
21
- }
22
- .file-input-wrapper {
23
- margin-bottom: 20px;
24
- }
25
- input[type="file"] {
26
- padding: 10px;
27
- background: #16213e;
28
- border: 2px dashed #0f3460;
29
- border-radius: 8px;
30
- color: #eee;
31
- cursor: pointer;
32
- width: 100%;
33
- box-sizing: border-box;
34
- }
35
- input[type="file"]:hover {
36
- border-color: #e94560;
37
- }
38
- .status {
39
- padding: 10px;
40
- background: #16213e;
41
- border-radius: 8px;
42
- margin-bottom: 20px;
43
- }
44
- .instructions {
45
- font-size: 14px;
46
- color: #888;
47
- line-height: 1.6;
48
- }
49
- </style>
50
- </head>
51
- <body>
52
- <div class="container">
53
- <h1>Load Tape</h1>
54
- <div class="file-input-wrapper">
55
- <input type="file" id="tape-input" accept=".bloop" />
56
- </div>
57
- <button id="replay-last" style="display: none; margin-bottom: 20px; padding: 10px 20px; background: #e94560; border: none; border-radius: 8px; color: #eee; cursor: pointer; font-size: 16px;">
58
- Replay last tape
59
- </button>
60
- <div class="status" id="status">Select a .bloop tape file to load</div>
61
- <div class="instructions">
62
- <p>To save a tape: Run the mario game and press Ctrl+S (or Cmd+S on Mac)</p>
63
- <p>Playback controls: Use the bottom bar buttons or hotkeys (5=back, 6=pause, 7=forward)</p>
64
- </div>
65
- </div>
66
- <script type="module" src="/src/tape-load.ts"></script>
67
- </body>
68
- </html>