create-bloop 0.0.1
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/dist/index.js +5029 -0
- package/package.json +23 -0
- package/templates/hello/index.html +16 -0
- package/templates/hello/package.json +20 -0
- package/templates/hello/src/config.ts +1 -0
- package/templates/hello/src/draw.ts +26 -0
- package/templates/hello/src/game.ts +28 -0
- package/templates/hello/src/main.ts +30 -0
- package/templates/hello/src/style.css +15 -0
- package/templates/hello/test/game.test.ts +19 -0
- package/templates/hello/tsconfig.json +26 -0
- package/templates/hello/vite.config.ts +5 -0
- package/templates/mario/.claude/prototype-next-steps.md +19 -0
- package/templates/mario/.claude/sidequests.md +10 -0
- package/templates/mario/index.html +17 -0
- package/templates/mario/package.json +22 -0
- package/templates/mario/public/sprites/MarioIdle.json +26 -0
- package/templates/mario/public/sprites/MarioIdle.png +0 -0
- package/templates/mario/public/sprites/MarioJump.json +26 -0
- package/templates/mario/public/sprites/MarioJump.png +0 -0
- package/templates/mario/public/sprites/MarioSkid.json +26 -0
- package/templates/mario/public/sprites/MarioSkid.png +0 -0
- package/templates/mario/public/sprites/MarioWalk.json +54 -0
- package/templates/mario/public/sprites/MarioWalk.png +0 -0
- package/templates/mario/src/chromatic-aberration.ts +107 -0
- package/templates/mario/src/config.ts +26 -0
- package/templates/mario/src/draw.ts +312 -0
- package/templates/mario/src/flipbook.ts +159 -0
- package/templates/mario/src/game.ts +171 -0
- package/templates/mario/src/main.ts +126 -0
- package/templates/mario/src/sprites.ts +14 -0
- package/templates/mario/src/style.css +7 -0
- package/templates/mario/src/systems/animation.ts +30 -0
- package/templates/mario/src/systems/collision.ts +41 -0
- package/templates/mario/src/systems/inputs.ts +66 -0
- package/templates/mario/src/systems/phase.ts +12 -0
- package/templates/mario/src/systems/physics.ts +22 -0
- package/templates/mario/src/tape-load.ts +165 -0
- package/templates/mario/tape-load.html +68 -0
- package/templates/mario/tsconfig.json +27 -0
- package/templates/mario/vite.config.ts +5 -0
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-bloop",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Create a new Bloop game",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": "./dist/index.js",
|
|
7
|
+
"files": ["dist", "templates"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "bun build index.ts --outdir=dist --target=node"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/bloopgames/bloop"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["bloop", "game", "scaffold", "create"],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"prompts": "^2.4.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/prompts": "^2.4.9"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<title>hello</title>
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<canvas></canvas>
|
|
13
|
+
<script type="module" src="/src/main.ts"></script>
|
|
14
|
+
</body>
|
|
15
|
+
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"typescript": "~5.9.3",
|
|
13
|
+
"vite": "^7.2.2"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@bloopjs/bloop": "^0.0.72",
|
|
17
|
+
"@bloopjs/toodle": "^0.0.100",
|
|
18
|
+
"@bloopjs/web": "^0.0.72"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const moveSpeed = 5;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Colors, type Toodle } from "@bloopjs/toodle";
|
|
2
|
+
import type { game } from "../src/game";
|
|
3
|
+
|
|
4
|
+
export function draw(g: typeof game, toodle: Toodle) {
|
|
5
|
+
const { bag } = g.context;
|
|
6
|
+
toodle.startFrame();
|
|
7
|
+
toodle.draw(
|
|
8
|
+
toodle.shapes.Circle({
|
|
9
|
+
idealSize: { width: 100, height: 100 },
|
|
10
|
+
scale: bag.scale,
|
|
11
|
+
position: { x: bag.x, y: bag.y },
|
|
12
|
+
color: Colors.web.hotPink,
|
|
13
|
+
}),
|
|
14
|
+
);
|
|
15
|
+
toodle.draw(
|
|
16
|
+
toodle.shapes.Rect({
|
|
17
|
+
idealSize: { width: 10, height: 10 },
|
|
18
|
+
position: toodle.convertSpace(
|
|
19
|
+
{ x: bag.mouse.x, y: bag.mouse.y },
|
|
20
|
+
{ from: "screen", to: "world" },
|
|
21
|
+
),
|
|
22
|
+
color: Colors.web.lightGreen,
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
toodle.endFrame();
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Bloop } from "@bloopjs/bloop";
|
|
2
|
+
import { moveSpeed } from "./config";
|
|
3
|
+
|
|
4
|
+
export const game = Bloop.create({
|
|
5
|
+
bag: {
|
|
6
|
+
x: 0,
|
|
7
|
+
y: 0,
|
|
8
|
+
scale: 1,
|
|
9
|
+
mouse: {
|
|
10
|
+
x: 0,
|
|
11
|
+
y: 0,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
game.system("move", {
|
|
17
|
+
update({ bag, inputs }) {
|
|
18
|
+
if (inputs.keys.a.held) bag.x -= moveSpeed;
|
|
19
|
+
if (inputs.keys.d.held) bag.x += moveSpeed;
|
|
20
|
+
if (inputs.keys.w.held) bag.y += moveSpeed;
|
|
21
|
+
if (inputs.keys.s.held) bag.y -= moveSpeed;
|
|
22
|
+
|
|
23
|
+
bag.mouse.x = inputs.mouse.x;
|
|
24
|
+
bag.mouse.y = inputs.mouse.y;
|
|
25
|
+
|
|
26
|
+
bag.scale = 2;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
// temp - use a monorepo dev wasm url instead of cdn
|
|
8
|
+
const monorepoWasmUrl = new URL("/bloop-wasm/bloop.wasm", window.location.href);
|
|
9
|
+
|
|
10
|
+
// 1. Set up simulation
|
|
11
|
+
const app = await start({
|
|
12
|
+
game,
|
|
13
|
+
engineWasmUrl: monorepoWasmUrl,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// 2. Set up rendering
|
|
17
|
+
const canvas = document.querySelector("canvas");
|
|
18
|
+
if (!canvas) throw new Error("Canvas element not found");
|
|
19
|
+
const toodle = await Toodle.attach(canvas);
|
|
20
|
+
requestAnimationFrame(function frame() {
|
|
21
|
+
draw(app.game, toodle);
|
|
22
|
+
requestAnimationFrame(frame);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 3. Set up Hot Module Replacement (HMR)
|
|
26
|
+
import.meta.hot?.accept("./game", async (newModule) => {
|
|
27
|
+
await app.acceptHmr(newModule?.game, {
|
|
28
|
+
wasmUrl: monorepoWasmUrl,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { mount } from "@bloopjs/bloop";
|
|
3
|
+
import { moveSpeed } from "../src/config";
|
|
4
|
+
import { game } from "../src/game";
|
|
5
|
+
|
|
6
|
+
describe("game", () => {
|
|
7
|
+
it("should initialize bag correctly", () => {
|
|
8
|
+
expect(game.bag).toMatchObject({ x: 0, y: 0 });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should update bag correctly after system execution", async () => {
|
|
12
|
+
const { sim } = await mount(game);
|
|
13
|
+
|
|
14
|
+
sim.emit.keydown("KeyD");
|
|
15
|
+
sim.emit.keydown("KeyW");
|
|
16
|
+
sim.step();
|
|
17
|
+
expect(game.bag).toMatchObject({ x: moveSpeed, y: moveSpeed });
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"types": ["vite/client"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Mario Rollback Prototype - Next Steps
|
|
2
|
+
|
|
3
|
+
## Rendering
|
|
4
|
+
- [ ] Load Mario/Luigi sprites from Aseprite
|
|
5
|
+
- [ ] Load block sprite
|
|
6
|
+
- [ ] Load coin sprite (animated?)
|
|
7
|
+
- [ ] Add coin collection animation/effect
|
|
8
|
+
|
|
9
|
+
## Physics & Gamefeel
|
|
10
|
+
- [ ] Tune jump velocity and gravity for better arc
|
|
11
|
+
- [ ] Add acceleration/deceleration for horizontal movement
|
|
12
|
+
- [ ] Consider coyote time (jump buffer after leaving ground)
|
|
13
|
+
- [ ] Tune collision detection for better "bonk" feel
|
|
14
|
+
|
|
15
|
+
## Polish
|
|
16
|
+
- [ ] Add jump sound effect
|
|
17
|
+
- [ ] Add coin collect sound effect
|
|
18
|
+
- [ ] Add block bump animation
|
|
19
|
+
- [ ] Score display positioning/styling
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<link rel="icon"
|
|
8
|
+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🍄</text></svg>">
|
|
9
|
+
|
|
10
|
+
<title>Mario Rollback</title>
|
|
11
|
+
</head>
|
|
12
|
+
|
|
13
|
+
<body>
|
|
14
|
+
<script type="module" src="/src/main.ts"></script>
|
|
15
|
+
</body>
|
|
16
|
+
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"ci:tsc": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@webgpu/types": "^0.1.67",
|
|
14
|
+
"typescript": "~5.9.3",
|
|
15
|
+
"vite": "^7.2.2"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@bloopjs/bloop": "^0.0.72",
|
|
19
|
+
"@bloopjs/toodle": "^0.1.1",
|
|
20
|
+
"@bloopjs/web": "^0.0.72"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{ "frames": {
|
|
2
|
+
"MarioIdle.aseprite": {
|
|
3
|
+
"frame": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
4
|
+
"rotated": false,
|
|
5
|
+
"trimmed": false,
|
|
6
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
7
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
8
|
+
"duration": 100
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"meta": {
|
|
12
|
+
"app": "https://www.aseprite.org/",
|
|
13
|
+
"version": "1.3.8.1-arm64",
|
|
14
|
+
"image": "MarioIdle.png",
|
|
15
|
+
"format": "I8",
|
|
16
|
+
"size": { "w": 16, "h": 16 },
|
|
17
|
+
"scale": "1",
|
|
18
|
+
"frameTags": [
|
|
19
|
+
],
|
|
20
|
+
"layers": [
|
|
21
|
+
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
|
22
|
+
],
|
|
23
|
+
"slices": [
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{ "frames": {
|
|
2
|
+
"MarioJump.aseprite": {
|
|
3
|
+
"frame": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
4
|
+
"rotated": false,
|
|
5
|
+
"trimmed": false,
|
|
6
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
7
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
8
|
+
"duration": 100
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"meta": {
|
|
12
|
+
"app": "https://www.aseprite.org/",
|
|
13
|
+
"version": "1.3.8.1-arm64",
|
|
14
|
+
"image": "MarioJump.png",
|
|
15
|
+
"format": "I8",
|
|
16
|
+
"size": { "w": 16, "h": 16 },
|
|
17
|
+
"scale": "1",
|
|
18
|
+
"frameTags": [
|
|
19
|
+
],
|
|
20
|
+
"layers": [
|
|
21
|
+
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
|
22
|
+
],
|
|
23
|
+
"slices": [
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{ "frames": {
|
|
2
|
+
"MarioSkid.aseprite": {
|
|
3
|
+
"frame": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
4
|
+
"rotated": false,
|
|
5
|
+
"trimmed": false,
|
|
6
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
7
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
8
|
+
"duration": 100
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"meta": {
|
|
12
|
+
"app": "https://www.aseprite.org/",
|
|
13
|
+
"version": "1.3.8.1-arm64",
|
|
14
|
+
"image": "MarioSkid.png",
|
|
15
|
+
"format": "I8",
|
|
16
|
+
"size": { "w": 16, "h": 16 },
|
|
17
|
+
"scale": "1",
|
|
18
|
+
"frameTags": [
|
|
19
|
+
],
|
|
20
|
+
"layers": [
|
|
21
|
+
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
|
22
|
+
],
|
|
23
|
+
"slices": [
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{ "frames": [
|
|
2
|
+
{
|
|
3
|
+
"filename": "MarioWalk 0.aseprite",
|
|
4
|
+
"frame": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
5
|
+
"rotated": false,
|
|
6
|
+
"trimmed": false,
|
|
7
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
8
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
9
|
+
"duration": 100
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"filename": "MarioWalk 1.aseprite",
|
|
13
|
+
"frame": { "x": 16, "y": 0, "w": 16, "h": 16 },
|
|
14
|
+
"rotated": false,
|
|
15
|
+
"trimmed": false,
|
|
16
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
17
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
18
|
+
"duration": 100
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"filename": "MarioWalk 2.aseprite",
|
|
22
|
+
"frame": { "x": 32, "y": 0, "w": 16, "h": 16 },
|
|
23
|
+
"rotated": false,
|
|
24
|
+
"trimmed": false,
|
|
25
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
26
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
27
|
+
"duration": 100
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"filename": "MarioWalk 3.aseprite",
|
|
31
|
+
"frame": { "x": 48, "y": 0, "w": 16, "h": 16 },
|
|
32
|
+
"rotated": false,
|
|
33
|
+
"trimmed": false,
|
|
34
|
+
"spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 16 },
|
|
35
|
+
"sourceSize": { "w": 16, "h": 16 },
|
|
36
|
+
"duration": 100
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"meta": {
|
|
40
|
+
"app": "https://www.aseprite.org/",
|
|
41
|
+
"version": "1.3.8.1-arm64",
|
|
42
|
+
"image": "MarioWalk.png",
|
|
43
|
+
"format": "I8",
|
|
44
|
+
"size": { "w": 64, "h": 16 },
|
|
45
|
+
"scale": "1",
|
|
46
|
+
"frameTags": [
|
|
47
|
+
],
|
|
48
|
+
"layers": [
|
|
49
|
+
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
|
50
|
+
],
|
|
51
|
+
"slices": [
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Backends, Colors, type Toodle } from "@bloopjs/toodle";
|
|
2
|
+
|
|
3
|
+
export function createChromaticAberrationEffect(toodle: Toodle): Backends.PostProcess {
|
|
4
|
+
if (!(toodle.backend instanceof Backends.WebGPUBackend)) {
|
|
5
|
+
throw new Error("Post-processing requires WebGPU backend");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const device = toodle.backend.device;
|
|
9
|
+
const presentationFormat = toodle.backend.presentationFormat;
|
|
10
|
+
|
|
11
|
+
const pipeline = device.createRenderPipeline({
|
|
12
|
+
label: "chromatic aberration pipeline",
|
|
13
|
+
layout: "auto",
|
|
14
|
+
primitive: { topology: "triangle-strip" },
|
|
15
|
+
vertex: {
|
|
16
|
+
module: Backends.PostProcessDefaults.vertexShader(device),
|
|
17
|
+
},
|
|
18
|
+
fragment: {
|
|
19
|
+
targets: [{ format: presentationFormat }],
|
|
20
|
+
module: device.createShaderModule({
|
|
21
|
+
label: "chromatic aberration fragment shader",
|
|
22
|
+
code: /*wgsl*/ `
|
|
23
|
+
@group(0) @binding(0) var inputTex: texture_2d<f32>;
|
|
24
|
+
@group(0) @binding(1) var inputSampler: sampler;
|
|
25
|
+
@group(0) @binding(2) var<uniform> time: f32;
|
|
26
|
+
|
|
27
|
+
@fragment
|
|
28
|
+
fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
|
|
29
|
+
// Time-based jitter for animation
|
|
30
|
+
let jitter = sin(time * 0.1) * 0.003;
|
|
31
|
+
let baseOffset = 0.008 + jitter;
|
|
32
|
+
|
|
33
|
+
// Sample RGB at different horizontal offsets
|
|
34
|
+
let r = textureSample(inputTex, inputSampler, uv + vec2f(baseOffset, 0.0)).r;
|
|
35
|
+
let g = textureSample(inputTex, inputSampler, uv).g;
|
|
36
|
+
let b = textureSample(inputTex, inputSampler, uv - vec2f(baseOffset, 0.0)).b;
|
|
37
|
+
let a = textureSample(inputTex, inputSampler, uv).a;
|
|
38
|
+
|
|
39
|
+
// Scanline effect
|
|
40
|
+
let scanline = sin(uv.y * 800.0) * 0.04 + 0.96;
|
|
41
|
+
|
|
42
|
+
// Horizontal glitch bands (occasional)
|
|
43
|
+
let glitchBand = step(0.98, fract(sin(floor(uv.y * 20.0 + time * 0.05)) * 43758.5453));
|
|
44
|
+
let glitchOffset = glitchBand * 0.02;
|
|
45
|
+
|
|
46
|
+
// Apply glitch offset to final sample
|
|
47
|
+
let glitchedUv = uv + vec2f(glitchOffset, 0.0);
|
|
48
|
+
let glitchedR = textureSample(inputTex, inputSampler, glitchedUv + vec2f(baseOffset, 0.0)).r;
|
|
49
|
+
let glitchedG = textureSample(inputTex, inputSampler, glitchedUv).g;
|
|
50
|
+
let glitchedB = textureSample(inputTex, inputSampler, glitchedUv - vec2f(baseOffset, 0.0)).b;
|
|
51
|
+
|
|
52
|
+
// Mix glitched and non-glitched based on band
|
|
53
|
+
let finalR = mix(r, glitchedR, glitchBand);
|
|
54
|
+
let finalG = mix(g, glitchedG, glitchBand);
|
|
55
|
+
let finalB = mix(b, glitchedB, glitchBand);
|
|
56
|
+
|
|
57
|
+
return vec4f(finalR, finalG, finalB, a) * scanline;
|
|
58
|
+
}
|
|
59
|
+
`,
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const sampler = Backends.PostProcessDefaults.sampler(device);
|
|
65
|
+
|
|
66
|
+
// Create a buffer for the time uniform
|
|
67
|
+
const timeBuffer = device.createBuffer({
|
|
68
|
+
label: "time uniform buffer",
|
|
69
|
+
size: 4,
|
|
70
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
process(queue, encoder, pingpong, screen) {
|
|
75
|
+
// Update time uniform
|
|
76
|
+
const timeData = new Float32Array([toodle.diagnostics.frames]);
|
|
77
|
+
queue.writeBuffer(timeBuffer, 0, timeData);
|
|
78
|
+
|
|
79
|
+
const renderPass = encoder.beginRenderPass({
|
|
80
|
+
label: "chromatic aberration render pass",
|
|
81
|
+
colorAttachments: [
|
|
82
|
+
{
|
|
83
|
+
view: screen.createView(),
|
|
84
|
+
clearValue: Colors.web.black,
|
|
85
|
+
loadOp: "clear" as const,
|
|
86
|
+
storeOp: "store" as const,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const bindGroup = device.createBindGroup({
|
|
92
|
+
label: "chromatic aberration bind group",
|
|
93
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
94
|
+
entries: [
|
|
95
|
+
{ binding: 0, resource: pingpong[0].createView() },
|
|
96
|
+
{ binding: 1, resource: sampler },
|
|
97
|
+
{ binding: 2, resource: { buffer: timeBuffer } },
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
renderPass.setPipeline(pipeline);
|
|
102
|
+
renderPass.setBindGroup(0, bindGroup);
|
|
103
|
+
renderPass.draw(4);
|
|
104
|
+
renderPass.end();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Physics (Y+ is up in Toodle)
|
|
2
|
+
export const GRAVITY = 0.5;
|
|
3
|
+
export const JUMP_VELOCITY = 9; // positive = up
|
|
4
|
+
export const MOVE_SPEED = 3;
|
|
5
|
+
export const MAX_FALL_SPEED = 10;
|
|
6
|
+
|
|
7
|
+
// World (0,0 is center of screen, Y+ is up)
|
|
8
|
+
export const GROUND_Y = -100;
|
|
9
|
+
export const BLOCK_Y = -10;
|
|
10
|
+
export const BLOCK_SPEED = 1;
|
|
11
|
+
export const BLOCK_MIN_X = -100;
|
|
12
|
+
export const BLOCK_MAX_X = 100;
|
|
13
|
+
|
|
14
|
+
// Player sizes
|
|
15
|
+
export const PLAYER_WIDTH = 16;
|
|
16
|
+
export const PLAYER_HEIGHT = 16;
|
|
17
|
+
export const BLOCK_SIZE = 16;
|
|
18
|
+
export const COIN_SIZE = 12;
|
|
19
|
+
|
|
20
|
+
// Starting positions
|
|
21
|
+
export const P1_START_X = -60;
|
|
22
|
+
export const P2_START_X = 60;
|
|
23
|
+
|
|
24
|
+
// Animation timings
|
|
25
|
+
export const COIN_VISIBLE_DURATION = 0.8; // seconds
|
|
26
|
+
export const COIN_V_Y = 0.7;
|