bevy-ts 0.1.0-alpha.2 → 0.1.0-alpha.4
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/bin/bevy-ts.js +19 -9
- package/bin/examples.js +141 -0
- package/examples/arena/assets/levels/arena.bsn +18 -0
- package/examples/arena/assets/levels/room.bsn +8 -0
- package/examples/arena/assets/models/crate.gltf +103 -0
- package/examples/arena/assets/sounds/hit.wav +0 -0
- package/examples/arena/assets/textures/checker.png +0 -0
- package/examples/arena/example.json +6 -0
- package/examples/arena/src/enemy.tsx +28 -0
- package/examples/arena/src/game.tsx +52 -0
- package/examples/arena/src/hud.tsx +12 -0
- package/examples/arena/src/player.tsx +14 -0
- package/examples/cube/example.json +6 -0
- package/examples/cube/src/game.ts +22 -0
- package/examples/cube/src/glow.tsx +18 -0
- package/examples/cube/src/glow.wgsl +13 -0
- package/index.d.ts +3 -0
- package/jsx-runtime.d.ts +12 -1
- package/material.d.ts +61 -0
- package/package.json +10 -8
- package/plugins/esbuild.js +5 -0
- package/plugins/vite.js +6 -0
- package/protocol.d.ts +219 -5
- package/runtime/core.js +16 -1
- package/runtime/material.js +101 -0
- package/wgsl.d.ts +6 -0
package/bin/bevy-ts.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Runs the precompiled engine binary from the platform package npm installed alongside this one
|
|
3
3
|
// (as esbuild and Biome ship), so `npx bevy-ts dev src/game.ts` needs no Rust toolchain.
|
|
4
|
-
// BEVY_TS_BINARY overrides the binary, for engine development.
|
|
4
|
+
// BEVY_TS_BINARY overrides the binary, for engine development. `new` and `examples` run here, in
|
|
5
|
+
// Node, with no engine.
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { create, examples } from "./examples.js";
|
|
6
8
|
import { binary } from "./resolve.js";
|
|
7
9
|
|
|
10
|
+
// exitCode, not exit(): stdout to a pipe is written asynchronously and must drain first.
|
|
11
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
12
|
+
if (command === "new") process.exitCode = create(rest);
|
|
13
|
+
else if (command === "examples") process.exitCode = examples(rest);
|
|
14
|
+
else runEngine();
|
|
15
|
+
|
|
8
16
|
function binaryOrExit() {
|
|
9
17
|
try {
|
|
10
18
|
return binary();
|
|
@@ -14,11 +22,13 @@ function binaryOrExit() {
|
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
function runEngine() {
|
|
26
|
+
// Forward signals so stopping the launcher stops the engine too.
|
|
27
|
+
const child = spawn(binaryOrExit(), process.argv.slice(2), { stdio: "inherit" });
|
|
28
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(sig, () => child.kill(sig));
|
|
29
|
+
child.on("error", (e) => {
|
|
30
|
+
console.error(`bevy-ts: ${e.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
child.on("exit", (code, signal) => process.exit(code ?? (signal ? 1 : 0)));
|
|
34
|
+
}
|
package/bin/examples.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// `bevy-ts examples` and `bevy-ts new`: the launcher's own commands. They read the examples this
|
|
2
|
+
// package ships (copied in at pack time by scripts/sync-examples.mjs) and never start the engine.
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { basename, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const root = fileURLToPath(new URL("../examples/", import.meta.url));
|
|
9
|
+
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
10
|
+
// What `new` leaves out of the copy: the metadata, not the game.
|
|
11
|
+
const META = ["example.json", "thumbnail.png"];
|
|
12
|
+
|
|
13
|
+
/** The shipped examples, in the order to show them: `cube` first, then by id. */
|
|
14
|
+
export function list() {
|
|
15
|
+
if (!existsSync(root)) return [];
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const name of readdirSync(root)) {
|
|
18
|
+
const file = `${root}${name}/example.json`;
|
|
19
|
+
if (!existsSync(file)) continue;
|
|
20
|
+
const { id, title, description, entry } = JSON.parse(readFileSync(file, "utf8"));
|
|
21
|
+
const e = { id, title, description, entry };
|
|
22
|
+
if (existsSync(`${root}${name}/thumbnail.png`)) e.thumbnail = `${root}${name}/thumbnail.png`;
|
|
23
|
+
e.dir = `${root}${name}`;
|
|
24
|
+
out.push(e);
|
|
25
|
+
}
|
|
26
|
+
return out.sort((a, b) => (a.id === "cube" ? -1 : b.id === "cube" ? 1 : a.id < b.id ? -1 : 1));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function refuse(msg, code = 1) {
|
|
30
|
+
console.error(`bevy-ts: ${msg}`);
|
|
31
|
+
return code;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function examples(args) {
|
|
35
|
+
const all = list().map(({ dir, ...e }) => e);
|
|
36
|
+
if (args.includes("--json")) {
|
|
37
|
+
process.stdout.write(`${JSON.stringify({ version, examples: all })}\n`);
|
|
38
|
+
} else {
|
|
39
|
+
for (const e of all) console.log(`${e.id.padEnd(10)} ${e.title}: ${e.description}`);
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const NEW_USAGE = "usage: bevy-ts new <dir> [--example <id>] [--no-install] [--json]";
|
|
45
|
+
|
|
46
|
+
export function create(args) {
|
|
47
|
+
let dir;
|
|
48
|
+
let id = "cube";
|
|
49
|
+
let install = true;
|
|
50
|
+
let json = false;
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const a = args[i];
|
|
53
|
+
if (a === "--example") {
|
|
54
|
+
id = args[++i];
|
|
55
|
+
if (!id || id.startsWith("--")) return refuse(`--example takes an id\n${NEW_USAGE}`, 2);
|
|
56
|
+
} else if (a === "--no-install") install = false;
|
|
57
|
+
else if (a === "--json") json = true;
|
|
58
|
+
else if (a.startsWith("--")) return refuse(`unknown option ${a}\n${NEW_USAGE}`, 2);
|
|
59
|
+
else if (dir === undefined) dir = a;
|
|
60
|
+
else return refuse(`unexpected argument ${a}\n${NEW_USAGE}`, 2);
|
|
61
|
+
}
|
|
62
|
+
if (dir === undefined) return refuse(`missing <dir>\n${NEW_USAGE}`, 2);
|
|
63
|
+
|
|
64
|
+
// Everything is checked before the first write, so a refusal writes nothing.
|
|
65
|
+
const all = list();
|
|
66
|
+
const example = all.find((e) => e.id === id);
|
|
67
|
+
if (!example) return refuse(`no example "${id}"; there are: ${all.map((e) => e.id).join(", ")}`);
|
|
68
|
+
const target = resolve(dir);
|
|
69
|
+
if (existsSync(target)) {
|
|
70
|
+
if (!statSync(target).isDirectory()) return refuse(`${target} is a file`);
|
|
71
|
+
if (readdirSync(target).length) return refuse(`${target} is not empty; bevy-ts new makes a project only in an empty or missing directory`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(target, { recursive: true });
|
|
76
|
+
for (const f of readdirSync(example.dir)) {
|
|
77
|
+
if (!META.includes(f)) cpSync(`${example.dir}/${f}`, `${target}/${f}`, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
const name = basename(target).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+/, "") || "game";
|
|
80
|
+
const pkg = {
|
|
81
|
+
name,
|
|
82
|
+
private: true,
|
|
83
|
+
type: "module",
|
|
84
|
+
scripts: { dev: `bevy-ts dev ${example.entry}` },
|
|
85
|
+
devDependencies: { "bevy-ts": version },
|
|
86
|
+
};
|
|
87
|
+
// The examples' compiler options, resolving bevy-ts from node_modules instead of the checkout.
|
|
88
|
+
const tsconfig = {
|
|
89
|
+
compilerOptions: {
|
|
90
|
+
target: "es2023",
|
|
91
|
+
module: "esnext",
|
|
92
|
+
moduleResolution: "bundler",
|
|
93
|
+
strict: true,
|
|
94
|
+
noEmit: true,
|
|
95
|
+
verbatimModuleSyntax: true,
|
|
96
|
+
jsx: "react-jsx",
|
|
97
|
+
jsxImportSource: "bevy-ts",
|
|
98
|
+
allowImportingTsExtensions: true,
|
|
99
|
+
},
|
|
100
|
+
include: ["src", ".bevy-ts/types"],
|
|
101
|
+
};
|
|
102
|
+
writeFileSync(`${target}/package.json`, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
103
|
+
writeFileSync(`${target}/tsconfig.json`, `${JSON.stringify(tsconfig, null, 2)}\n`);
|
|
104
|
+
writeFileSync(`${target}/.gitignore`, "node_modules/\n.bevy-ts/\n");
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return refuse(e.message);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const pm = packageManager();
|
|
110
|
+
const installed = install ? runInstall(pm, target) : false;
|
|
111
|
+
if (json) {
|
|
112
|
+
process.stdout.write(`${JSON.stringify({ dir: target, entry: example.entry, example: id, installed })}\n`);
|
|
113
|
+
} else {
|
|
114
|
+
console.log(`Made ${target} from ${id}${install && !installed ? "; the install failed" : ""}.`);
|
|
115
|
+
const steps = [`cd ${shellQuote(dir)}`, ...(installed ? [] : [`${pm} install`]), `npx bevy-ts dev ${example.entry}`];
|
|
116
|
+
console.log(` ${steps.join(" && ")}`);
|
|
117
|
+
}
|
|
118
|
+
return install && !installed ? 1 : 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The package manager that ran bevy-ts (npm, pnpm, yarn or bun), npm otherwise. */
|
|
122
|
+
function packageManager() {
|
|
123
|
+
const agent = (process.env.npm_config_user_agent ?? "").split("/")[0];
|
|
124
|
+
return ["npm", "pnpm", "yarn", "bun"].includes(agent) ? agent : "npm";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The path as it pastes into a POSIX shell: single-quoted when it holds anything but safe characters. */
|
|
128
|
+
function shellQuote(p) {
|
|
129
|
+
return /^[\w@%+=:,./-]+$/.test(p) ? p : `'${p.replaceAll("'", "'\\''")}'`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Installs with `pm` in `dir`, its output on stderr. */
|
|
133
|
+
function runInstall(pm, dir) {
|
|
134
|
+
console.error(`bevy-ts: ${pm} install in ${dir}`);
|
|
135
|
+
const r = spawnSync(pm, ["install"], { cwd: dir, stdio: ["ignore", 2, 2], shell: process.platform === "win32" });
|
|
136
|
+
if (r.error || r.status !== 0) {
|
|
137
|
+
console.error(`bevy-ts: ${pm} install failed${r.error ? `: ${r.error.message}` : ""}; the project is in place, run it again there`);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The arena: where things are. What an enemy is lives in src/enemy.tsx.
|
|
2
|
+
|
|
3
|
+
#EnemyA
|
|
4
|
+
@enemy::Enemy
|
|
5
|
+
Transform { translation: Vec3 { x: -3.0, z: -4.0 } }
|
|
6
|
+
--
|
|
7
|
+
#EnemyB
|
|
8
|
+
@enemy::Enemy { @hp: 250.0 }
|
|
9
|
+
Transform { translation: Vec3 { x: 3.0, z: -4.0 } }
|
|
10
|
+
--
|
|
11
|
+
// Two copies of one room, each with its guard.
|
|
12
|
+
#RoomA
|
|
13
|
+
:"levels/room.bsn"
|
|
14
|
+
Transform { translation: Vec3 { x: -12.0, z: -12.0 } }
|
|
15
|
+
--
|
|
16
|
+
#RoomB
|
|
17
|
+
:"levels/room.bsn"
|
|
18
|
+
Transform { translation: Vec3 { x: 12.0, z: -12.0 } }
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"accessors": [
|
|
3
|
+
{
|
|
4
|
+
"bufferView": 0,
|
|
5
|
+
"componentType": 5126,
|
|
6
|
+
"count": 24,
|
|
7
|
+
"max": [
|
|
8
|
+
0.5,
|
|
9
|
+
0.5,
|
|
10
|
+
0.5
|
|
11
|
+
],
|
|
12
|
+
"min": [
|
|
13
|
+
-0.5,
|
|
14
|
+
-0.5,
|
|
15
|
+
-0.5
|
|
16
|
+
],
|
|
17
|
+
"type": "VEC3"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"bufferView": 1,
|
|
21
|
+
"componentType": 5126,
|
|
22
|
+
"count": 24,
|
|
23
|
+
"type": "VEC3"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"bufferView": 2,
|
|
27
|
+
"componentType": 5123,
|
|
28
|
+
"count": 36,
|
|
29
|
+
"type": "SCALAR"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"asset": {
|
|
33
|
+
"generator": "bevy-ts-viewport tests/assets.rs",
|
|
34
|
+
"version": "2.0"
|
|
35
|
+
},
|
|
36
|
+
"bufferViews": [
|
|
37
|
+
{
|
|
38
|
+
"buffer": 0,
|
|
39
|
+
"byteLength": 288,
|
|
40
|
+
"byteOffset": 0
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"buffer": 0,
|
|
44
|
+
"byteLength": 288,
|
|
45
|
+
"byteOffset": 288
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"buffer": 0,
|
|
49
|
+
"byteLength": 72,
|
|
50
|
+
"byteOffset": 576
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"buffers": [
|
|
54
|
+
{
|
|
55
|
+
"byteLength": 648,
|
|
56
|
+
"uri": "data:application/octet-stream;base64,AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"materials": [
|
|
60
|
+
{
|
|
61
|
+
"name": "wood",
|
|
62
|
+
"pbrMetallicRoughness": {
|
|
63
|
+
"baseColorFactor": [
|
|
64
|
+
0.55,
|
|
65
|
+
0.36,
|
|
66
|
+
0.18,
|
|
67
|
+
1.0
|
|
68
|
+
],
|
|
69
|
+
"metallicFactor": 0.0,
|
|
70
|
+
"roughnessFactor": 0.8
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"meshes": [
|
|
75
|
+
{
|
|
76
|
+
"name": "crate",
|
|
77
|
+
"primitives": [
|
|
78
|
+
{
|
|
79
|
+
"attributes": {
|
|
80
|
+
"NORMAL": 1,
|
|
81
|
+
"POSITION": 0
|
|
82
|
+
},
|
|
83
|
+
"indices": 2,
|
|
84
|
+
"material": 0
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"nodes": [
|
|
90
|
+
{
|
|
91
|
+
"mesh": 0,
|
|
92
|
+
"name": "crate"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"scene": 0,
|
|
96
|
+
"scenes": [
|
|
97
|
+
{
|
|
98
|
+
"nodes": [
|
|
99
|
+
0
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// What an enemy is: a scene component. Levels place it (assets/levels/arena.bsn writes
|
|
2
|
+
// `@enemy::Enemy { @hp: 250.0 }`), and TSX can mount it too (`<Enemy at={[0, 0, -6]} />`).
|
|
3
|
+
import { Component, SceneComponent, type f32, type Vec3Like } from "bevy-ts";
|
|
4
|
+
import { Transform } from "bevy-ts/bevy";
|
|
5
|
+
|
|
6
|
+
export class Health extends Component {
|
|
7
|
+
hp: f32 = 100;
|
|
8
|
+
max: f32 = 100;
|
|
9
|
+
}
|
|
10
|
+
/** Marks enemies, for the systems that move and cull them. */
|
|
11
|
+
export class Foe extends Component {}
|
|
12
|
+
|
|
13
|
+
export class Enemy extends SceneComponent {
|
|
14
|
+
static scene({ hp = 100, at = [0, 0, 0] }: { hp?: f32; at?: Vec3Like }) {
|
|
15
|
+
return (
|
|
16
|
+
<entity name="enemy">
|
|
17
|
+
<Transform translation={at} />
|
|
18
|
+
<Health initialHp={hp} max={hp} />
|
|
19
|
+
<Foe />
|
|
20
|
+
<mesh shape="cuboid" size={[1, 1, 1]} color="#cc3333" />
|
|
21
|
+
<entity key="eye">
|
|
22
|
+
<Transform translation={[0, 0.6, 0.5]} />
|
|
23
|
+
<mesh shape="sphere" radius={0.15} color="#ffffff" />
|
|
24
|
+
</entity>
|
|
25
|
+
</entity>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// The arena: a player and a HUD in TSX, the enemies placed by assets/levels/arena.bsn, a camera, and
|
|
2
|
+
// three systems that play. `bevy-ts dev src/game.tsx`.
|
|
3
|
+
import { app, type Commands, type Entity, type Local, type Mut, type Query, ref, type Res, Startup, Time, Transform, type u32, Update, type With, type Without } from "bevy-ts";
|
|
4
|
+
import { Foe, Health } from "./enemy.tsx";
|
|
5
|
+
import { Hud } from "./hud.tsx";
|
|
6
|
+
import { Player, PlayerUnit } from "./player.tsx";
|
|
7
|
+
|
|
8
|
+
/** The game's camera and light. The camera sits where the editor's first view does, so edit mode
|
|
9
|
+
* opens on the same view. */
|
|
10
|
+
export function stage(commands: Commands) {
|
|
11
|
+
commands.spawnNative("camera3d", { position: [-6, 5, 10], lookAt: [0, 0, 0] });
|
|
12
|
+
commands.spawnNative("directionalLight", { position: [3, 6, 2], lookAt: [0, 0, 0] });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Every 30 frames, each enemy within 10 units hurts the player by 5. */
|
|
16
|
+
export function damage(
|
|
17
|
+
clock: Local<{ frames: u32 }>,
|
|
18
|
+
enemies: Query<[Transform], With<Foe>>,
|
|
19
|
+
players: Query<[Transform, Mut<Health>], With<Player>>,
|
|
20
|
+
) {
|
|
21
|
+
clock.frames += 1;
|
|
22
|
+
if (clock.frames % 30 !== 0) return;
|
|
23
|
+
players.each((p, h) => {
|
|
24
|
+
enemies.each((e) => {
|
|
25
|
+
if (e.translation.distance(p.translation) < 10) h.hp = Math.max(0, h.hp - 5);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Enemies bob up and down. */
|
|
31
|
+
export function bob(time: Res<Time>, enemies: Query<[Mut<Transform>], [With<Foe>, Without<Player>]>) {
|
|
32
|
+
enemies.each((t) => {
|
|
33
|
+
t.translation.y = Math.sin(time.elapsed_secs * 2) * 0.5;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Enemies at no hp are despawned: a reload does not bring them back. */
|
|
38
|
+
export function cull(commands: Commands, enemies: Query<[Entity, Health], With<Foe>>) {
|
|
39
|
+
enemies.each((e, h) => {
|
|
40
|
+
if (h.hp <= 0) commands.entity(e).despawn();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const player = ref();
|
|
45
|
+
app.scene("arena", () => (
|
|
46
|
+
<>
|
|
47
|
+
<PlayerUnit handle={player} />
|
|
48
|
+
<Hud player={player} />
|
|
49
|
+
</>
|
|
50
|
+
));
|
|
51
|
+
app.level("levels/arena.bsn");
|
|
52
|
+
app.addSystems(Startup, stage).addSystems(Update, damage, bob, cull);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The HUD: no hooks. The text binds the player's Health; the button mounts an enemy.
|
|
2
|
+
import { type NodeRef, px } from "bevy-ts";
|
|
3
|
+
import { Enemy, Health } from "./enemy.tsx";
|
|
4
|
+
|
|
5
|
+
export const Hud = ({ player }: { player: NodeRef }) => (
|
|
6
|
+
<node style={{ position: "absolute", top: px(12), left: px(12), padding: px(8), flexDirection: "column", rowGap: px(6) }} background="#00000088">
|
|
7
|
+
<text key="hp" size={20}>{() => `HP ${Math.ceil(player.get(Health).hp)} / ${player.get(Health).max}`}</text>
|
|
8
|
+
<button key="spawn" style={{ padding: px(4) }} background="#3366cc" onPointerClick={({ commands, frame }) => commands.mount(<Enemy key={`e${frame}`} at={[0, 0, -6]} />)}>
|
|
9
|
+
<text>Spawn</text>
|
|
10
|
+
</button>
|
|
11
|
+
</node>
|
|
12
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Component, type NodeRef } from "bevy-ts";
|
|
2
|
+
import { Transform } from "bevy-ts/bevy";
|
|
3
|
+
import { Health } from "./enemy.tsx";
|
|
4
|
+
|
|
5
|
+
export class Player extends Component {}
|
|
6
|
+
|
|
7
|
+
export const PlayerUnit = ({ handle }: { handle: NodeRef }) => (
|
|
8
|
+
<entity name="player" ref={handle}>
|
|
9
|
+
<Transform translation={[0, 0, 0]} />
|
|
10
|
+
<Health initialHp={100} max={100} />
|
|
11
|
+
<Player />
|
|
12
|
+
<mesh shape="sphere" radius={0.5} color="#3366cc" />
|
|
13
|
+
</entity>
|
|
14
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { app, Component, type f32, Startup, Time, Transform, Update } from "bevy-ts";
|
|
2
|
+
import type { Commands, Mut, Query, Res } from "bevy-ts";
|
|
3
|
+
|
|
4
|
+
export class Mover extends Component {
|
|
5
|
+
speed: f32 = 1;
|
|
6
|
+
amplitude: f32 = 2;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function scene(commands: Commands) {
|
|
10
|
+
commands.spawnNative("camera3d", { position: [-3, 3, 6], lookAt: [0, 0, 0] });
|
|
11
|
+
commands.spawnNative("directionalLight", { position: [3, 6, 2], lookAt: [0, 0, 0] });
|
|
12
|
+
const cube = commands.spawnNative("cuboid", { size: [1, 1, 1], color: "#f80" });
|
|
13
|
+
commands.entity(cube).insert(Mover.of());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function bob(time: Res<Time>, q: Query<[Mover, Mut<Transform>]>) {
|
|
17
|
+
q.each((mover, t) => {
|
|
18
|
+
t.translation.y = Math.sin(time.elapsed_secs * mover.speed) * mover.amplitude;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
app.addSystems(Startup, scene).addSystems(Update, bob);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The cube example with a script material (bevy-ts/material): its params in TS, its shader in
|
|
2
|
+
// glow.wgsl, no Rust. Run this file as the entry: `bevy-ts dev examples/cube/src/glow.tsx`.
|
|
3
|
+
// Save glow.wgsl and the cube redraws with no reload; change the params and it re-encodes.
|
|
4
|
+
import "./game.ts";
|
|
5
|
+
import { app } from "bevy-ts";
|
|
6
|
+
import { Transform } from "bevy-ts/bevy";
|
|
7
|
+
import { color, f32, material } from "bevy-ts/material";
|
|
8
|
+
import glow from "./glow.wgsl";
|
|
9
|
+
|
|
10
|
+
export const Glow = material("game::Glow", { params: { color: color, pulse: f32 }, fragment: glow });
|
|
11
|
+
|
|
12
|
+
app.scene("glow", () => (
|
|
13
|
+
<entity name="glow">
|
|
14
|
+
<Transform translation={[2.5, 0, 0]} />
|
|
15
|
+
<mesh shape="cuboid" size={[1, 1, 1]} />
|
|
16
|
+
<material kind={Glow} color="#ff8800" pulse={2} />
|
|
17
|
+
</entity>
|
|
18
|
+
));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// game::Glow's fragment shader. bevy-ts appends `params` (a GlowParams with color and pulse)
|
|
2
|
+
// from the schema in glow.tsx, so this file does not declare it.
|
|
3
|
+
import bevy_pbr::render::forward_io::VertexOutput;
|
|
4
|
+
import bevy_pbr::render::mesh_view_bindings::globals;
|
|
5
|
+
|
|
6
|
+
const FLOOR: f32 = 0.25;
|
|
7
|
+
|
|
8
|
+
@fragment
|
|
9
|
+
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
10
|
+
let wave = 0.5 + 0.5 * sin(globals.time * params.pulse);
|
|
11
|
+
let glow = FLOOR + (1.0 - FLOOR) * wave;
|
|
12
|
+
return vec4<f32>(params.color.rgb * glow, params.color.a);
|
|
13
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference path="./wgsl.d.ts" />
|
|
1
2
|
// Types for `bevy-ts`: the ruled surface (operator, 2026-10-20; docs/research/dx.md). The runtime
|
|
2
3
|
// is built into the engine binary.
|
|
3
4
|
//
|
|
@@ -427,6 +428,8 @@ export interface App {
|
|
|
427
428
|
|
|
428
429
|
/** Writes a manifest section for a Rust plugin. */
|
|
429
430
|
export declare function section(name: string, value: Uint8Array | string | object): void;
|
|
431
|
+
/** The loading generation's `{ items }` list for a JSON section, for a built-in module's per-generation declarations. */
|
|
432
|
+
export declare function sectionItems(name: string): unknown[];
|
|
430
433
|
/** Decodes UTF-8 bytes. */
|
|
431
434
|
export declare function text(bytes: Uint8Array): string;
|
|
432
435
|
/** A plugin: an exported function taking the app, as Bevy's `fn build(&self, app: &mut App)`. */
|
package/jsx-runtime.d.ts
CHANGED
|
@@ -132,6 +132,17 @@ export interface MaterialProps {
|
|
|
132
132
|
roughness?: number;
|
|
133
133
|
emissive?: string;
|
|
134
134
|
unlit?: boolean;
|
|
135
|
+
kind?: never;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `<material kind={Glow} color="#f80" pulse={2} />`: a script material from `material()` in
|
|
140
|
+
* `bevy-ts/material`, with its params by name and its textures as asset paths. Checked against
|
|
141
|
+
* the kind's schema when the generation loads.
|
|
142
|
+
*/
|
|
143
|
+
export interface ScriptMaterialProps {
|
|
144
|
+
kind: { readonly name: string };
|
|
145
|
+
[param: string]: unknown;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
type DescElement = Element;
|
|
@@ -161,7 +172,7 @@ export declare namespace JSX {
|
|
|
161
172
|
interface IntrinsicElements {
|
|
162
173
|
entity: EntityProps & KeyAttribute;
|
|
163
174
|
mesh: MeshProps;
|
|
164
|
-
material: MaterialProps;
|
|
175
|
+
material: MaterialProps | ScriptMaterialProps;
|
|
165
176
|
node: NodeProps & KeyAttribute;
|
|
166
177
|
button: NodeProps & KeyAttribute;
|
|
167
178
|
text: TextProps & KeyAttribute;
|
package/material.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @generated from crates/bevy-ts-material/ts/material.d.ts by scripts/sync-runtime.mjs. Do not edit.
|
|
2
|
+
/** The value a `.wgsl` import gives: the file, by path. Its WGSL stays in the engine. */
|
|
3
|
+
export interface ShaderFile {
|
|
4
|
+
readonly kind: "shaderFile";
|
|
5
|
+
readonly path: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Base = "f32" | "i32" | "u32" | "vec2f" | "vec3f" | "vec4f" | "mat4x4f" | "color";
|
|
9
|
+
export type ParamType = Base | { readonly array: Base; readonly len: number };
|
|
10
|
+
|
|
11
|
+
export declare const f32: "f32";
|
|
12
|
+
export declare const i32: "i32";
|
|
13
|
+
export declare const u32: "u32";
|
|
14
|
+
export declare const vec2f: "vec2f";
|
|
15
|
+
export declare const vec3f: "vec3f";
|
|
16
|
+
export declare const vec4f: "vec4f";
|
|
17
|
+
export declare const mat4x4f: "mat4x4f";
|
|
18
|
+
/** A `vec4<f32>` in linear RGBA, written as `"#f80"` or sRGB `[r, g, b, a?]`. */
|
|
19
|
+
export declare const color: "color";
|
|
20
|
+
/** A 2D texture with a filtering sampler: `name` and `name_sampler` in WGSL. */
|
|
21
|
+
export declare const texture2d: "texture2d";
|
|
22
|
+
|
|
23
|
+
/** A fixed array, strided to 16 bytes; scalar and `vec2f` elements read as `.x`/`.xy` of a `vec4`. */
|
|
24
|
+
export declare function array<B extends Base, N extends number>(of: B, len: N): { readonly array: B; readonly len: N };
|
|
25
|
+
|
|
26
|
+
export type AlphaMode = "opaque" | "blend" | "premultiplied" | "add" | "multiply" | "alphaToCoverage" | { mask: number };
|
|
27
|
+
|
|
28
|
+
/** A param's value in TS: numbers, number tuples, or a color. */
|
|
29
|
+
export type ParamValue<T extends ParamType> = T extends "f32" | "i32" | "u32"
|
|
30
|
+
? number
|
|
31
|
+
: T extends "vec2f"
|
|
32
|
+
? [number, number]
|
|
33
|
+
: T extends "vec3f"
|
|
34
|
+
? [number, number, number]
|
|
35
|
+
: T extends "vec4f"
|
|
36
|
+
? [number, number, number, number]
|
|
37
|
+
: T extends "mat4x4f"
|
|
38
|
+
? number[]
|
|
39
|
+
: T extends "color"
|
|
40
|
+
? string | [number, number, number] | [number, number, number, number]
|
|
41
|
+
: T extends { array: infer E extends Base }
|
|
42
|
+
? ParamValue<E>[]
|
|
43
|
+
: never;
|
|
44
|
+
|
|
45
|
+
/** A material kind, what `<material kind={...} />` names. */
|
|
46
|
+
export interface MaterialKind<P extends Record<string, ParamType> = Record<string, ParamType>, T extends Record<string, "texture2d"> = {}> {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
/** Types only: the params and textures `<material>` takes for this kind. */
|
|
49
|
+
readonly __props?: { [K in keyof P]?: ParamValue<P[K]> } & { [K in keyof T]?: string };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Declares a script material: its params, laid out by WGSL's uniform rules in Rust; its textures;
|
|
54
|
+
* and its shaders, `.wgsl` files imported like modules (`import glow from "./glow.wgsl"`). The WGSL
|
|
55
|
+
* reads the params as `params.<name>` and each texture as `<name>` with `<name>_sampler`; bevy-ts
|
|
56
|
+
* appends those declarations, so the file does not declare them.
|
|
57
|
+
*/
|
|
58
|
+
export declare function material<P extends Record<string, ParamType>, T extends Record<string, "texture2d"> = {}>(
|
|
59
|
+
name: string,
|
|
60
|
+
decl: { params: P; textures?: T; fragment: ShaderFile; vertex?: ShaderFile; alphaMode?: AlphaMode },
|
|
61
|
+
): MaterialKind<P, T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bevy-ts",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "TypeScript for Bevy: components, systems and hot reload without recompiling Rust",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"types": "./jsx-dev-runtime.d.ts",
|
|
31
31
|
"default": "./runtime/jsx-dev-runtime.js"
|
|
32
32
|
},
|
|
33
|
+
"./material": {
|
|
34
|
+
"types": "./material.d.ts",
|
|
35
|
+
"default": "./runtime/material.js"
|
|
36
|
+
},
|
|
33
37
|
"./protocol": {
|
|
34
38
|
"types": "./protocol.d.ts"
|
|
35
39
|
},
|
|
@@ -51,14 +55,11 @@
|
|
|
51
55
|
"*.js",
|
|
52
56
|
"bin",
|
|
53
57
|
"runtime",
|
|
54
|
-
"plugins"
|
|
58
|
+
"plugins",
|
|
59
|
+
"examples"
|
|
55
60
|
],
|
|
56
61
|
"optionalDependencies": {
|
|
57
|
-
"@bevy-ts/
|
|
58
|
-
"@bevy-ts/darwin-x64": "0.1.0-alpha.2",
|
|
59
|
-
"@bevy-ts/linux-arm64": "0.1.0-alpha.2",
|
|
60
|
-
"@bevy-ts/linux-x64": "0.1.0-alpha.2",
|
|
61
|
-
"@bevy-ts/win32-x64": "0.1.0-alpha.2"
|
|
62
|
+
"@bevy-ts/linux-arm64": "0.1.0-alpha.4"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"json-schema-to-typescript": "^15.0.4",
|
|
@@ -67,7 +68,8 @@
|
|
|
67
68
|
},
|
|
68
69
|
"scripts": {
|
|
69
70
|
"check": "node scripts/sync-runtime.mjs --check && tsc -p ../../examples/cube && tsc -p test",
|
|
70
|
-
"
|
|
71
|
+
"prepack": "node scripts/sync-examples.mjs",
|
|
72
|
+
"test": "node scripts/sync-examples.mjs && vitest run --config test/vitest/vitest.config.mjs"
|
|
71
73
|
},
|
|
72
74
|
"publishConfig": {
|
|
73
75
|
"access": "public",
|
package/plugins/esbuild.js
CHANGED
|
@@ -5,6 +5,11 @@ export default function bevyTs() {
|
|
|
5
5
|
return {
|
|
6
6
|
name: "bevy-ts",
|
|
7
7
|
setup(build) {
|
|
8
|
+
// A `.wgsl` import is a shader-file value, as in the engine; the WGSL is not loaded.
|
|
9
|
+
build.onLoad({ filter: /\.wgsl$/ }, (args) => ({
|
|
10
|
+
contents: `export default Object.freeze({ kind: "shaderFile", path: ${JSON.stringify(args.path)} });\n`,
|
|
11
|
+
loader: "js",
|
|
12
|
+
}));
|
|
8
13
|
build.onLoad({ filter: /\.(c|m)?tsx?$/ }, (args) => {
|
|
9
14
|
if (args.path.includes("/node_modules/")) return undefined;
|
|
10
15
|
return { contents: transform(args.path), loader: "js" };
|
package/plugins/vite.js
CHANGED
|
@@ -6,6 +6,12 @@ export default function bevyTs() {
|
|
|
6
6
|
return {
|
|
7
7
|
name: "bevy-ts",
|
|
8
8
|
enforce: "pre",
|
|
9
|
+
// A `.wgsl` import is a shader-file value, as in the engine; the WGSL is not loaded.
|
|
10
|
+
load(id) {
|
|
11
|
+
// `./x.wgsl?raw` and `?url` keep Vite's own meaning.
|
|
12
|
+
if (!id.endsWith(".wgsl")) return null;
|
|
13
|
+
return `export default Object.freeze({ kind: "shaderFile", path: ${JSON.stringify(id)} });\n`;
|
|
14
|
+
},
|
|
9
15
|
transform(code, id) {
|
|
10
16
|
const file = id.split("?")[0];
|
|
11
17
|
if (!TS_FILE.test(file) || file.includes("/node_modules/")) return null;
|
package/protocol.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @generated by packages/bevy-ts/scripts/gen-protocol.mjs from protocol/bevy-ts.openrpc.json. Do not edit.
|
|
2
2
|
|
|
3
3
|
/** The protocol version these types describe. */
|
|
4
|
-
export const PROTOCOL_VERSION: "1.
|
|
4
|
+
export const PROTOCOL_VERSION: "1.17.0";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* An entity id: Entity::to_bits.
|
|
@@ -101,6 +101,16 @@ export type EditRouteKind = "bsn" | "tsx" | "locked" | "transient";
|
|
|
101
101
|
* via the `definition` "SelectionOrigin".
|
|
102
102
|
*/
|
|
103
103
|
export type SelectionOrigin = "viewport" | "editor" | "engine";
|
|
104
|
+
/**
|
|
105
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
106
|
+
* via the `definition` "GizmoMode".
|
|
107
|
+
*/
|
|
108
|
+
export type GizmoMode = "translate" | "rotate" | "scale";
|
|
109
|
+
/**
|
|
110
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
111
|
+
* via the `definition` "GizmoSpace".
|
|
112
|
+
*/
|
|
113
|
+
export type GizmoSpace = "world" | "local";
|
|
104
114
|
/**
|
|
105
115
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
106
116
|
* via the `definition` "EditStatus".
|
|
@@ -203,7 +213,10 @@ export type LevelOp =
|
|
|
203
213
|
* A name path from the file's top; `#n` is the n-th unnamed sibling.
|
|
204
214
|
*/
|
|
205
215
|
parent: string[];
|
|
206
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Optional (1.12): without it the writer picks the first free sibling name.
|
|
218
|
+
*/
|
|
219
|
+
name?: string;
|
|
207
220
|
/**
|
|
208
221
|
* add_instance: the scene component.
|
|
209
222
|
*/
|
|
@@ -224,6 +237,10 @@ export type LevelOp =
|
|
|
224
237
|
[k: string]: LevelValue;
|
|
225
238
|
};
|
|
226
239
|
}[];
|
|
240
|
+
/**
|
|
241
|
+
* add_entity (1.12): a .glb or .gltf path, written as WorldAssetRoot("path#Scene0"), or a .bsn to include.
|
|
242
|
+
*/
|
|
243
|
+
asset?: string;
|
|
227
244
|
}
|
|
228
245
|
| {
|
|
229
246
|
op: "delete";
|
|
@@ -250,6 +267,10 @@ export type LevelOp =
|
|
|
250
267
|
* A name path from the file's top; `#n` is the n-th unnamed sibling.
|
|
251
268
|
*/
|
|
252
269
|
parent: string[];
|
|
270
|
+
/**
|
|
271
|
+
* (1.12) The place among the parent's written children; last when absent.
|
|
272
|
+
*/
|
|
273
|
+
index?: number;
|
|
253
274
|
}
|
|
254
275
|
| {
|
|
255
276
|
op: "restore";
|
|
@@ -259,6 +280,32 @@ export type LevelOp =
|
|
|
259
280
|
parent: string[];
|
|
260
281
|
index: number;
|
|
261
282
|
text: string;
|
|
283
|
+
}
|
|
284
|
+
| {
|
|
285
|
+
op: "add_component";
|
|
286
|
+
/**
|
|
287
|
+
* A name path from the file's top; `#n` is the n-th unnamed sibling.
|
|
288
|
+
*/
|
|
289
|
+
path: string[];
|
|
290
|
+
/**
|
|
291
|
+
* As LevelOverride.component names it, or any path a .bsn patch resolves.
|
|
292
|
+
*/
|
|
293
|
+
component: string;
|
|
294
|
+
fields?: {
|
|
295
|
+
[k: string]: LevelValue;
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* The patch as written; the inverse of a remove_component carries it.
|
|
299
|
+
*/
|
|
300
|
+
text?: string;
|
|
301
|
+
}
|
|
302
|
+
| {
|
|
303
|
+
op: "remove_component";
|
|
304
|
+
/**
|
|
305
|
+
* A name path from the file's top; `#n` is the n-th unnamed sibling.
|
|
306
|
+
*/
|
|
307
|
+
path: string[];
|
|
308
|
+
component: string;
|
|
262
309
|
};
|
|
263
310
|
|
|
264
311
|
/**
|
|
@@ -503,7 +550,19 @@ export interface TsSchemaExtension {
|
|
|
503
550
|
};
|
|
504
551
|
}
|
|
505
552
|
/**
|
|
506
|
-
*
|
|
553
|
+
* How an editor treats a component: whose it is, whether the engine maintains it, whether Add component may offer it, and its icon category.
|
|
554
|
+
*
|
|
555
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
556
|
+
* via the `definition` "EditorInfo".
|
|
557
|
+
*/
|
|
558
|
+
export interface EditorInfo {
|
|
559
|
+
origin: "engine" | "game" | "plugin";
|
|
560
|
+
internal: boolean;
|
|
561
|
+
addable: boolean;
|
|
562
|
+
category: "transform" | "render" | "light" | "camera" | "physics" | "audio" | "ui" | "animation" | "game" | "other";
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* A component's schema in the shape stock registry.schema uses, plus x-bevy-ts for TS components and x-bevy-ts-editor for every component.
|
|
507
566
|
*
|
|
508
567
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
509
568
|
* via the `definition` "ComponentSchema".
|
|
@@ -519,6 +578,7 @@ export interface ComponentSchema {
|
|
|
519
578
|
required?: string[];
|
|
520
579
|
reflectTypes?: string[];
|
|
521
580
|
"x-bevy-ts"?: TsSchemaExtension;
|
|
581
|
+
"x-bevy-ts-editor"?: EditorInfo;
|
|
522
582
|
[k: string]: unknown;
|
|
523
583
|
}
|
|
524
584
|
/**
|
|
@@ -758,6 +818,28 @@ export interface TsxTreeNode {
|
|
|
758
818
|
* Despawned by something other than tsx; not respawned until a restart or a key change.
|
|
759
819
|
*/
|
|
760
820
|
tombstoned: boolean;
|
|
821
|
+
/**
|
|
822
|
+
* (1.12) Set when a level placed the entity; absent for a TSX-only node.
|
|
823
|
+
*/
|
|
824
|
+
level?: {
|
|
825
|
+
/**
|
|
826
|
+
* The outermost level file (a sidecar's path for a scene overlay).
|
|
827
|
+
*/
|
|
828
|
+
file: string;
|
|
829
|
+
namePath: string[];
|
|
830
|
+
/**
|
|
831
|
+
* Written in that file, rather than coming from a prefab, an included level or the scene's TSX.
|
|
832
|
+
*/
|
|
833
|
+
written: boolean;
|
|
834
|
+
};
|
|
835
|
+
/**
|
|
836
|
+
* (1.15) The root the node is in.
|
|
837
|
+
*/
|
|
838
|
+
root?: string;
|
|
839
|
+
/**
|
|
840
|
+
* (1.15) The node id of the nearest ancestor row, or null at the root's top. Slot steps (`<Show>`, `<For>`) are in the key path but are not rows.
|
|
841
|
+
*/
|
|
842
|
+
parent?: TsxNodeId | null;
|
|
761
843
|
}
|
|
762
844
|
/**
|
|
763
845
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
@@ -767,6 +849,10 @@ export interface TsxRoot {
|
|
|
767
849
|
name: string;
|
|
768
850
|
location?: SourceLocation | null;
|
|
769
851
|
nodes: TsxTreeNode[];
|
|
852
|
+
/**
|
|
853
|
+
* (1.12) An app.scene root's sidecar level path, whether or not the file exists.
|
|
854
|
+
*/
|
|
855
|
+
sidecar?: string;
|
|
770
856
|
}
|
|
771
857
|
/**
|
|
772
858
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
@@ -867,7 +953,7 @@ export interface TsxMoved {
|
|
|
867
953
|
source: SourceLocation | null;
|
|
868
954
|
}
|
|
869
955
|
/**
|
|
870
|
-
* The first message carries `snapshot`; later ones carry one reconcile's changes: nodes added, removed, tombstoned, whose source moved,
|
|
956
|
+
* The first message carries `snapshot`; later ones carry one reconcile's changes: nodes added, removed, tombstoned, whose source moved, whose values tsx wrote, and sibling lists whose order changed. A client that applies `added` and `removed`, then replaces each `ordered` parent's children with `children`, holds the tree `tsx_tree` returns. A gap in `seq` means resubscribe.
|
|
871
957
|
*
|
|
872
958
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
873
959
|
* via the `definition` "TsxTreeWatchMessage".
|
|
@@ -876,10 +962,31 @@ export interface TsxTreeWatchMessage {
|
|
|
876
962
|
seq: number;
|
|
877
963
|
snapshot?: TsxRoot[];
|
|
878
964
|
added?: TsxTreeNode[];
|
|
965
|
+
/**
|
|
966
|
+
* Every node gone, its whole subtree included (1.15).
|
|
967
|
+
*/
|
|
879
968
|
removed?: TsxNodeId[];
|
|
880
969
|
tombstoned?: TsxNodeId[];
|
|
881
970
|
moved?: TsxMoved[];
|
|
882
971
|
written?: TsxNodeId[];
|
|
972
|
+
/**
|
|
973
|
+
* (1.15) Each sibling list that gained a child, lost one or changed order since the last message, with its full order as `tsx_tree` gives it. A list whose parent was removed is not sent.
|
|
974
|
+
*/
|
|
975
|
+
ordered?: TsxOrdered[];
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* One sibling list's full child order.
|
|
979
|
+
*
|
|
980
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
981
|
+
* via the `definition` "TsxOrdered".
|
|
982
|
+
*/
|
|
983
|
+
export interface TsxOrdered {
|
|
984
|
+
root: string;
|
|
985
|
+
/**
|
|
986
|
+
* The list's parent row, or null for the root's top.
|
|
987
|
+
*/
|
|
988
|
+
parent: TsxNodeId | null;
|
|
989
|
+
children: TsxNodeId[];
|
|
883
990
|
}
|
|
884
991
|
/**
|
|
885
992
|
* Modifier keys held; absent means up.
|
|
@@ -1356,6 +1463,11 @@ export interface PickHit {
|
|
|
1356
1463
|
entity: Entity;
|
|
1357
1464
|
depth: number;
|
|
1358
1465
|
selects: Entity;
|
|
1466
|
+
/**
|
|
1467
|
+
* @minItems 3
|
|
1468
|
+
* @maxItems 3
|
|
1469
|
+
*/
|
|
1470
|
+
point?: [number, number, number];
|
|
1359
1471
|
}
|
|
1360
1472
|
/**
|
|
1361
1473
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
@@ -1363,6 +1475,11 @@ export interface PickHit {
|
|
|
1363
1475
|
*/
|
|
1364
1476
|
export interface ViewportPickResult {
|
|
1365
1477
|
hits: PickHit[];
|
|
1478
|
+
/**
|
|
1479
|
+
* @minItems 3
|
|
1480
|
+
* @maxItems 3
|
|
1481
|
+
*/
|
|
1482
|
+
ground?: [number, number, number] | null;
|
|
1366
1483
|
}
|
|
1367
1484
|
/**
|
|
1368
1485
|
* Entities, or a source location resolved as bevy-ts/tsx_find resolves it.
|
|
@@ -1464,6 +1581,48 @@ export interface ViewportSelection {
|
|
|
1464
1581
|
origin: SelectionOrigin;
|
|
1465
1582
|
items: SelectionItem[];
|
|
1466
1583
|
}
|
|
1584
|
+
/**
|
|
1585
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
1586
|
+
* via the `definition` "ViewportToolsParams".
|
|
1587
|
+
*/
|
|
1588
|
+
export interface ViewportToolsParams {
|
|
1589
|
+
mode?: GizmoMode;
|
|
1590
|
+
space?: GizmoSpace;
|
|
1591
|
+
/**
|
|
1592
|
+
* Drags move, turn or scale in whole steps.
|
|
1593
|
+
*/
|
|
1594
|
+
snap?: boolean;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Fixed, as Bevy's editor prototype sets them.
|
|
1598
|
+
*
|
|
1599
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
1600
|
+
* via the `definition` "SnapSteps".
|
|
1601
|
+
*/
|
|
1602
|
+
export interface SnapSteps {
|
|
1603
|
+
/**
|
|
1604
|
+
* World units.
|
|
1605
|
+
*/
|
|
1606
|
+
translate: number;
|
|
1607
|
+
/**
|
|
1608
|
+
* Degrees.
|
|
1609
|
+
*/
|
|
1610
|
+
rotate: number;
|
|
1611
|
+
scale: number;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* `origin` is who made the last change: `editor` by the method, `viewport` by a key, `engine` before any.
|
|
1615
|
+
*
|
|
1616
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
1617
|
+
* via the `definition` "ViewportTools".
|
|
1618
|
+
*/
|
|
1619
|
+
export interface ViewportTools {
|
|
1620
|
+
mode: GizmoMode;
|
|
1621
|
+
space: GizmoSpace;
|
|
1622
|
+
snap: boolean;
|
|
1623
|
+
steps: SnapSteps;
|
|
1624
|
+
origin: SelectionOrigin;
|
|
1625
|
+
}
|
|
1467
1626
|
/**
|
|
1468
1627
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
1469
1628
|
* via the `definition` "ViewportEdit".
|
|
@@ -1571,6 +1730,7 @@ export interface AssetInfo {
|
|
|
1571
1730
|
loadState: AssetLoadState;
|
|
1572
1731
|
error?: string | null;
|
|
1573
1732
|
labels?: string[];
|
|
1733
|
+
thumbnail?: boolean;
|
|
1574
1734
|
}
|
|
1575
1735
|
/**
|
|
1576
1736
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
@@ -1807,6 +1967,10 @@ export interface LevelNodeResult {
|
|
|
1807
1967
|
* A scene component instance's @props, beside its fields; empty for anything else. (1.7)
|
|
1808
1968
|
*/
|
|
1809
1969
|
props?: LevelProp[];
|
|
1970
|
+
/**
|
|
1971
|
+
* (1.12) Components the file adds that the entity's prefab does not give, named as LevelOverride.component names them.
|
|
1972
|
+
*/
|
|
1973
|
+
patches?: string[];
|
|
1810
1974
|
}
|
|
1811
1975
|
/**
|
|
1812
1976
|
* One @prop of a scene component instance: its type, the value it takes, and whether the level sets it.
|
|
@@ -1848,7 +2012,10 @@ export interface SourceLocation1 {
|
|
|
1848
2012
|
export interface LevelWriteParams {
|
|
1849
2013
|
id?: number;
|
|
1850
2014
|
file: string;
|
|
1851
|
-
|
|
2015
|
+
/**
|
|
2016
|
+
* null (1.12): the file does not exist yet.
|
|
2017
|
+
*/
|
|
2018
|
+
base: LevelHash | null;
|
|
1852
2019
|
ops: LevelOp[];
|
|
1853
2020
|
}
|
|
1854
2021
|
/**
|
|
@@ -1877,6 +2044,13 @@ export interface LevelWriteResult {
|
|
|
1877
2044
|
* The batch that undoes this one.
|
|
1878
2045
|
*/
|
|
1879
2046
|
inverse: LevelOp[];
|
|
2047
|
+
/**
|
|
2048
|
+
* (1.12) One per entity the batch added, in order; entity is null when the level is not open.
|
|
2049
|
+
*/
|
|
2050
|
+
created?: {
|
|
2051
|
+
namePath: string[];
|
|
2052
|
+
entity: Entity | null;
|
|
2053
|
+
}[];
|
|
1880
2054
|
}
|
|
1881
2055
|
/**
|
|
1882
2056
|
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
@@ -2031,6 +2205,43 @@ export interface AssetPreviewCloseParams {
|
|
|
2031
2205
|
export interface AssetPreviewCloseResult {
|
|
2032
2206
|
closed: boolean;
|
|
2033
2207
|
}
|
|
2208
|
+
/**
|
|
2209
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
2210
|
+
* via the `definition` "LevelCatalogResult".
|
|
2211
|
+
*/
|
|
2212
|
+
export interface LevelCatalogResult {
|
|
2213
|
+
scenes: LevelCatalogScene[];
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
2217
|
+
* via the `definition` "LevelCatalogScene".
|
|
2218
|
+
*/
|
|
2219
|
+
export interface LevelCatalogScene {
|
|
2220
|
+
name: string;
|
|
2221
|
+
file?: string | null;
|
|
2222
|
+
line?: number | null;
|
|
2223
|
+
props: LevelCatalogProp[];
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* This interface was referenced by `ProtocolTypes`'s JSON-Schema
|
|
2227
|
+
* via the `definition` "LevelCatalogProp".
|
|
2228
|
+
*/
|
|
2229
|
+
export interface LevelCatalogProp {
|
|
2230
|
+
name: string;
|
|
2231
|
+
/**
|
|
2232
|
+
* The prop's type as the manifest writes it ({ "kind": "f32" }).
|
|
2233
|
+
*/
|
|
2234
|
+
type: {
|
|
2235
|
+
[k: string]: unknown;
|
|
2236
|
+
};
|
|
2237
|
+
/**
|
|
2238
|
+
* The literal default, or null.
|
|
2239
|
+
*/
|
|
2240
|
+
default: {
|
|
2241
|
+
[k: string]: unknown;
|
|
2242
|
+
};
|
|
2243
|
+
required: boolean;
|
|
2244
|
+
}
|
|
2034
2245
|
|
|
2035
2246
|
/** Every method: params, result, owning crate, and whether it streams. */
|
|
2036
2247
|
export interface Methods {
|
|
@@ -2061,6 +2272,8 @@ export interface Methods {
|
|
|
2061
2272
|
"bevy-ts/viewport_pick": { params: ViewportPickParams; result: ViewportPickResult; crate: "bevy-ts-viewport"; watch: false };
|
|
2062
2273
|
"bevy-ts/viewport_select": { params: ViewportSelectParams; result: ViewportSelection; crate: "bevy-ts-viewport"; watch: false };
|
|
2063
2274
|
"bevy-ts/viewport_selection+watch": { params: undefined; result: ViewportSelection; crate: "bevy-ts-viewport"; watch: true };
|
|
2275
|
+
"bevy-ts/viewport_tools": { params: ViewportToolsParams | undefined; result: ViewportTools; crate: "bevy-ts-viewport"; watch: false };
|
|
2276
|
+
"bevy-ts/viewport_tools+watch": { params: undefined; result: ViewportTools; crate: "bevy-ts-viewport"; watch: true };
|
|
2064
2277
|
"bevy-ts/viewport_edits+watch": { params: undefined; result: ViewportEditsMessage; crate: "bevy-ts-viewport"; watch: true };
|
|
2065
2278
|
"bevy-ts/viewport_edit_result": { params: ViewportEditResultParams; result: ViewportEdit; crate: "bevy-ts-viewport"; watch: false };
|
|
2066
2279
|
"bevy-ts/viewport_revert": { params: ViewportRevertParams; result: ViewportEdit; crate: "bevy-ts-viewport"; watch: false };
|
|
@@ -2073,6 +2286,7 @@ export interface Methods {
|
|
|
2073
2286
|
"bevy-ts/asset_preview_close": { params: AssetPreviewCloseParams; result: AssetPreviewCloseResult; crate: "bevy-ts-viewport"; watch: false };
|
|
2074
2287
|
"bevy-ts/level_list": { params: undefined; result: LevelListResult; crate: "bevy-ts-levels"; watch: false };
|
|
2075
2288
|
"bevy-ts/level_node": { params: LevelNodeParams; result: LevelNodeResult; crate: "bevy-ts-levels"; watch: false };
|
|
2289
|
+
"bevy-ts/level_catalog": { params: undefined; result: LevelCatalogResult; crate: "bevy-ts-levels"; watch: false };
|
|
2076
2290
|
"bevy-ts/level_write": { params: LevelWriteParams; result: LevelWriteResult; crate: "bevy-ts-levels"; watch: false };
|
|
2077
2291
|
"bevy-ts/level_apply": { params: LevelApplyParams; result: LevelApplyResult; crate: "bevy-ts-levels"; watch: false };
|
|
2078
2292
|
"bevy-ts/level+watch": { params: undefined; result: LevelWatchMessage; crate: "bevy-ts-levels"; watch: true };
|
package/runtime/core.js
CHANGED
|
@@ -849,6 +849,21 @@ export function section(name, value) {
|
|
|
849
849
|
g.sections.set(name, { bytes, location: at });
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
/**
|
|
853
|
+
* The loading generation's list for a JSON section, `{ items: [...] }`, created on first use. For
|
|
854
|
+
* a built-in module, which is evaluated once, whose declarations come from each generation's code
|
|
855
|
+
* (`material()` in `bevy-ts/material`). The section's location is the first caller's.
|
|
856
|
+
*/
|
|
857
|
+
export function sectionItems(name) {
|
|
858
|
+
const g = loadingOrThrow(`sectionItems(${name})`);
|
|
859
|
+
let s = g.sections.get(name);
|
|
860
|
+
if (!s || !s.items) {
|
|
861
|
+
s = { items: [], location: callerLocation() };
|
|
862
|
+
g.sections.set(name, s);
|
|
863
|
+
}
|
|
864
|
+
return s.items;
|
|
865
|
+
}
|
|
866
|
+
|
|
852
867
|
/** Decodes UTF-8 bytes, for entries that take text or JSON payloads. */
|
|
853
868
|
export function text(bytes) {
|
|
854
869
|
return decoder.decode(bytes);
|
|
@@ -1093,7 +1108,7 @@ function end() {
|
|
|
1093
1108
|
})),
|
|
1094
1109
|
entries: g.entries,
|
|
1095
1110
|
};
|
|
1096
|
-
const sections = [...g.sections].map(([name, s]) => [name, s.bytes, s.location || null]);
|
|
1111
|
+
const sections = [...g.sections].map(([name, s]) => [name, s.items ? encoder.encode(JSON.stringify({ items: s.items })) : s.bytes, s.location || null]);
|
|
1097
1112
|
return [JSON.stringify(manifest), sections];
|
|
1098
1113
|
}
|
|
1099
1114
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @generated from crates/bevy-ts-material/ts/material.js by scripts/sync-runtime.mjs. Do not edit.
|
|
2
|
+
// `bevy-ts/material`: script materials. A material is a name, a params schema in WGSL types and
|
|
3
|
+
// `.wgsl` files imported like modules; Rust lays out the params, generates their declarations and
|
|
4
|
+
// draws it with one erased material. Served by the engine; evaluated once, so each declaration
|
|
5
|
+
// goes into the loading generation's `materials` section.
|
|
6
|
+
import { sectionItems } from "bevy-ts";
|
|
7
|
+
|
|
8
|
+
export const f32 = "f32";
|
|
9
|
+
export const i32 = "i32";
|
|
10
|
+
export const u32 = "u32";
|
|
11
|
+
export const vec2f = "vec2f";
|
|
12
|
+
export const vec3f = "vec3f";
|
|
13
|
+
export const vec4f = "vec4f";
|
|
14
|
+
export const mat4x4f = "mat4x4f";
|
|
15
|
+
/** A `vec4<f32>` in linear RGBA, written as `"#f80"` or sRGB `[r, g, b, a?]`. */
|
|
16
|
+
export const color = "color";
|
|
17
|
+
/** A 2D texture with a filtering sampler, `<name>` and `<name>_sampler` in WGSL. */
|
|
18
|
+
export const texture2d = "texture2d";
|
|
19
|
+
|
|
20
|
+
const BASES = new Set([f32, i32, u32, vec2f, vec3f, vec4f, mat4x4f, color]);
|
|
21
|
+
const ALPHA = new Set(["opaque", "blend", "premultiplied", "add", "multiply", "alphaToCoverage"]);
|
|
22
|
+
|
|
23
|
+
/** A fixed array, strided to 16 bytes; scalar and `vec2f` elements read as `.x`/`.xy` of a `vec4`. */
|
|
24
|
+
export function array(of, len) {
|
|
25
|
+
if (!BASES.has(of)) throw new Error(`array(${JSON.stringify(of)}, ${len}): elements are f32, i32, u32, vec2f, vec3f, vec4f, mat4x4f or color`);
|
|
26
|
+
if (!Number.isInteger(len) || len < 1) throw new Error(`array(${of}, ${len}): the length is a positive integer`);
|
|
27
|
+
return Object.freeze({ array: of, len });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const FRAME = /\(?((?:file|bevy-ts):[^\s()]+?):(\d+):(\d+)\)?\s*$/;
|
|
31
|
+
|
|
32
|
+
function callerLocation() {
|
|
33
|
+
for (const line of String(new Error().stack).split("\n").slice(1)) {
|
|
34
|
+
if (/bevy-ts:/.test(line)) continue;
|
|
35
|
+
const m = FRAME.exec(line);
|
|
36
|
+
if (m) return { file: m[1].replace(/\?gen=\d+$/, ""), line: +m[2], column: +m[3] };
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function located(message, location) {
|
|
42
|
+
const e = new Error(message);
|
|
43
|
+
e.bevyTsLocation = location;
|
|
44
|
+
return e;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isShaderFile(v) {
|
|
48
|
+
return v !== null && typeof v === "object" && v.kind === "shaderFile" && typeof v.path === "string";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A material kind: what `<material kind={...} />` names. Only `name` crosses into a scene. */
|
|
52
|
+
class MaterialKind {
|
|
53
|
+
constructor(name) {
|
|
54
|
+
this.name = name;
|
|
55
|
+
Object.freeze(this);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `material("game::Glow", { params: { color: color, pulse: f32 }, fragment: glow })`, where
|
|
61
|
+
* `glow` is `import glow from "./glow.wgsl"`.
|
|
62
|
+
*/
|
|
63
|
+
export function material(name, decl) {
|
|
64
|
+
const at = callerLocation();
|
|
65
|
+
const fail = (m) => {
|
|
66
|
+
throw located(`material ${name}: ${m}`, at);
|
|
67
|
+
};
|
|
68
|
+
if (typeof name !== "string" || !name) throw located("material(name, ...): the name is a string (\"game::Glow\")", at);
|
|
69
|
+
if (!decl || typeof decl !== "object") fail("takes { params, fragment }");
|
|
70
|
+
for (const k of Object.keys(decl)) {
|
|
71
|
+
if (!["params", "textures", "fragment", "vertex", "alphaMode"].includes(k)) fail(`unknown option ${k}`);
|
|
72
|
+
}
|
|
73
|
+
const shader = (k) => {
|
|
74
|
+
const v = decl[k];
|
|
75
|
+
if (v === undefined) return null;
|
|
76
|
+
if (typeof v === "string") fail(`${k} is a WGSL file, import it: import shader from "./${k}.wgsl"; then ${k}: shader`);
|
|
77
|
+
if (!isShaderFile(v)) fail(`${k} is a shader file from a .wgsl import`);
|
|
78
|
+
return v.path;
|
|
79
|
+
};
|
|
80
|
+
const fragment = shader("fragment");
|
|
81
|
+
if (fragment === null) fail("needs a fragment shader");
|
|
82
|
+
const params = [];
|
|
83
|
+
for (const [p, type] of Object.entries(decl.params || {})) {
|
|
84
|
+
const ok = BASES.has(type) || (type && typeof type === "object" && BASES.has(type.array) && Number.isInteger(type.len));
|
|
85
|
+
if (!ok) fail(`param ${p} has type ${JSON.stringify(type)}; use f32, i32, u32, vec2f, vec3f, vec4f, mat4x4f, color or array(...)`);
|
|
86
|
+
params.push({ name: p, type });
|
|
87
|
+
}
|
|
88
|
+
const textures = [];
|
|
89
|
+
for (const [t, type] of Object.entries(decl.textures || {})) {
|
|
90
|
+
if (type !== texture2d) fail(`texture ${t} has type ${JSON.stringify(type)}; use texture2d`);
|
|
91
|
+
if (params.some((p) => p.name === t)) fail(`${t} is both a param and a texture`);
|
|
92
|
+
textures.push(t);
|
|
93
|
+
}
|
|
94
|
+
let alphaMode = decl.alphaMode === undefined ? "opaque" : decl.alphaMode;
|
|
95
|
+
if (typeof alphaMode === "object" && alphaMode !== null && typeof alphaMode.mask === "number") alphaMode = { mask: alphaMode.mask };
|
|
96
|
+
else if (!ALPHA.has(alphaMode)) fail(`alphaMode ${JSON.stringify(alphaMode)}; use ${[...ALPHA].join(", ")} or { mask: 0.5 }`);
|
|
97
|
+
const items = sectionItems("materials");
|
|
98
|
+
if (items.some((m) => m.name === name)) fail("is declared twice");
|
|
99
|
+
items.push({ name, params, textures, fragment, vertex: shader("vertex"), alphaMode, location: at || null });
|
|
100
|
+
return new MaterialKind(name);
|
|
101
|
+
}
|
package/wgsl.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// A `.wgsl` import is a shader file, for `material()` in `bevy-ts/material`. The engine resolves
|
|
2
|
+
// it relative to the importing file and watches it; its WGSL never runs as JS.
|
|
3
|
+
declare module "*.wgsl" {
|
|
4
|
+
const shader: { readonly kind: "shaderFile"; readonly path: string };
|
|
5
|
+
export default shader;
|
|
6
|
+
}
|