dotframe 0.1.4 → 0.1.6
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/CHANGELOG.md +12 -0
- package/cli/native.ts +96 -37
- package/native/df_native.c +2 -0
- package/package.json +1 -1
- package/skill-data/macos/SKILL.md +28 -1
- package/skill-data/netplay/SKILL.md +2 -0
- package/templates/_base/src/setup.ts +27 -5
- package/templates/blank/game.ts +2 -0
- package/templates/fighter/game.ts +2 -0
- package/templates/platformer/game.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
From the Craft Ones native port.
|
|
6
|
+
|
|
7
|
+
- Native: mouse buttons read 0 while the window lacks input focus, so clicks in other apps no longer reach the game. The pointer position stays global, so aiming is unchanged.
|
|
8
|
+
- Templates: `src/setup.ts` reads the pointer and touches with a hold-to-charge, release-to-act pattern (a charge meter while holding, a tone on release), on web and native. Each `game.ts` declares the `POINTER_BIT` a release sets.
|
|
9
|
+
- Skills: the macos guide gains "Porting existing TypeScript" (what scriptc rejects and the fix for each, plus runtime traps such as structural coercion copying and out-of-range reads) and "Debugging a native crash" with lldb. Netplay warns against checksums built on `JSON.stringify`.
|
|
10
|
+
|
|
11
|
+
## 0.1.5
|
|
12
|
+
|
|
13
|
+
- Native builds follow the entry's import graph instead of copying the game folder: relative imports that leave the game root and workspace packages that resolve to source are staged with their relative layout and rewritten to relative paths, so ports inside monorepos compile.
|
|
14
|
+
|
|
3
15
|
## 0.1.4
|
|
4
16
|
|
|
5
17
|
Fixes from rounds 2 and 3 of the Craft Ones dogfood.
|
package/cli/native.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { CliError, type Ctx, exec, home, print, type RunResult, which } from "./lib";
|
|
5
5
|
|
|
6
6
|
// The engine this CLI belongs to: a git checkout or node_modules/dotframe.
|
|
@@ -44,42 +44,104 @@ export function vendorFix(platform: NativePlatform): string {
|
|
|
44
44
|
return `dotframe vendor ${platform}`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
const SPECIFIER = /(?:from\s+|import\s*\(\s*|import\s+)(["'])([^"']+)\1/g;
|
|
48
|
+
const EXTENSIONS = ["", ".ts", ".tsx", ".json", "/index.ts", "/index.tsx"];
|
|
49
|
+
|
|
50
|
+
function resolveFile(base: string): string | null {
|
|
51
|
+
const candidates = [base.replace(/\.js$/, ".ts"), base];
|
|
52
|
+
for (const c of candidates) for (const ext of EXTENSIONS) if (existsSync(c + ext) && statSync(c + ext).isFile()) return realpathSync(c + ext);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A bare specifier that resolves, through node_modules, to TypeScript source outside node_modules: a workspace
|
|
57
|
+
// package (bun and npm link those). Published packages stay packages.
|
|
58
|
+
function resolveWorkspace(specifier: string, fromDir: string): string | null {
|
|
59
|
+
const parts = specifier.split("/");
|
|
60
|
+
const name = specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
61
|
+
const sub = parts.slice(name.split("/").length).join("/");
|
|
62
|
+
for (let dir = fromDir; ; dir = dirname(dir)) {
|
|
63
|
+
const pkgDir = join(dir, "node_modules", name);
|
|
64
|
+
if (existsSync(pkgDir)) {
|
|
65
|
+
const real = realpathSync(pkgDir);
|
|
66
|
+
if (real.split(sep).includes("node_modules")) return null;
|
|
67
|
+
if (sub) return resolveFile(join(real, sub));
|
|
68
|
+
const pkg = JSON.parse(readFileSync(join(real, "package.json"), "utf8")) as Record<string, unknown>;
|
|
69
|
+
const exp = pkg.exports as Record<string, unknown> | string | undefined;
|
|
70
|
+
const dot = typeof exp === "string" ? exp : (exp?.["."] as Record<string, string> | string | undefined);
|
|
71
|
+
const entry = typeof dot === "string" ? dot : (dot?.import ?? dot?.default ?? dot?.types ?? (pkg.module as string) ?? (pkg.main as string) ?? "index.ts");
|
|
72
|
+
return resolveFile(join(real, entry));
|
|
59
73
|
}
|
|
74
|
+
if (dirname(dir) === dir) return null;
|
|
60
75
|
}
|
|
61
|
-
return n;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
interface Graph {
|
|
79
|
+
files: string[];
|
|
80
|
+
// Per file, the specifiers to rewrite and the absolute file each one points at.
|
|
81
|
+
links: Map<string, Map<string, string>>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Every source file the entry reaches: relative imports (including ones that leave the game root) and workspace
|
|
85
|
+
// packages. "dotframe/..." is the engine, staged separately.
|
|
86
|
+
function importGraph(entry: string): Graph {
|
|
87
|
+
const files: string[] = [];
|
|
88
|
+
const links = new Map<string, Map<string, string>>();
|
|
89
|
+
const queue = [realpathSync(entry)];
|
|
90
|
+
const seen = new Set(queue);
|
|
91
|
+
while (queue.length > 0) {
|
|
92
|
+
const file = queue.shift() as string;
|
|
93
|
+
files.push(file);
|
|
94
|
+
if (!/\.tsx?$/.test(file)) continue;
|
|
95
|
+
const map = new Map<string, string>();
|
|
96
|
+
for (const m of readFileSync(file, "utf8").matchAll(SPECIFIER)) {
|
|
97
|
+
const spec = m[2];
|
|
98
|
+
if (spec.startsWith("dotframe/") || spec.startsWith("node:")) continue;
|
|
99
|
+
const target = spec.startsWith(".") ? resolveFile(resolve(dirname(file), spec)) : resolveWorkspace(spec, dirname(file));
|
|
100
|
+
if (!target) continue;
|
|
101
|
+
if (!spec.startsWith(".")) map.set(spec, target);
|
|
102
|
+
if (!seen.has(target)) {
|
|
103
|
+
seen.add(target);
|
|
104
|
+
queue.push(target);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
links.set(file, map);
|
|
108
|
+
}
|
|
109
|
+
return { files, links };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function commonDir(paths: string[]): string {
|
|
113
|
+
let base = dirname(paths[0]);
|
|
114
|
+
for (const p of paths) while (!(p + sep).startsWith(base === sep ? sep : base + sep)) base = dirname(base);
|
|
115
|
+
return base;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function rel(fromFile: string, to: string): string {
|
|
119
|
+
const r = relative(dirname(fromFile), to);
|
|
120
|
+
return r.startsWith(".") ? r : `./${r}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// scriptc's static build takes relative imports only, and treats anything under node_modules as package code for
|
|
124
|
+
// its dynamic engine. Staging copies the engine and every reached game file side by side, keeping their relative
|
|
125
|
+
// layout, and rewrites "dotframe/..." and workspace imports to relative paths.
|
|
126
|
+
function stageGame(root: string, entry: string, tree: string, engineStage: string): string {
|
|
127
|
+
const graph = importGraph(join(root, entry));
|
|
128
|
+
const base = commonDir([realpathSync(root) + sep + "x", ...graph.files]);
|
|
129
|
+
const staged = (file: string): string => join(tree, relative(base, file));
|
|
130
|
+
for (const file of graph.files) {
|
|
131
|
+
const to = staged(file);
|
|
132
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
133
|
+
let text = readFileSync(file, "utf8");
|
|
134
|
+
if (/\.tsx?$/.test(file)) {
|
|
135
|
+
const map = graph.links.get(file) ?? new Map<string, string>();
|
|
136
|
+
text = text.replace(SPECIFIER, (whole: string, q: string, spec: string): string => {
|
|
137
|
+
if (spec.startsWith("dotframe/")) return whole.replace(`${q}${spec}${q}`, `${q}${rel(to, join(engineStage, spec.slice("dotframe/".length)))}${q}`);
|
|
138
|
+
const target = map.get(spec);
|
|
139
|
+
return target ? whole.replace(`${q}${spec}${q}`, `${q}${rel(to, staged(target)).replace(/\.tsx?$/, "")}${q}`) : whole;
|
|
140
|
+
});
|
|
73
141
|
}
|
|
74
|
-
|
|
75
|
-
const text = readFileSync(path, "utf8");
|
|
76
|
-
const next = text.replace(/(from\s+|import\s*\(\s*)(["'])dotframe\/([^"']+)\2/g, (_m: string, head: string, q: string, rest: string): string => {
|
|
77
|
-
let rel = relative(dirname(path), join(engineRoot, rest));
|
|
78
|
-
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
79
|
-
return `${head}${q}${rel}${q}`;
|
|
80
|
-
});
|
|
81
|
-
if (next !== text) writeFileSync(path, next);
|
|
142
|
+
writeFileSync(to, text);
|
|
82
143
|
}
|
|
144
|
+
return join(tree, relative(base, realpathSync(root)));
|
|
83
145
|
}
|
|
84
146
|
|
|
85
147
|
export async function buildNative(ctx: Ctx, root: string, gameName: string, targetName: string, t: NativeTarget): Promise<{ binary: string; log: string; steps: RunResult[] }> {
|
|
@@ -95,6 +157,7 @@ export async function buildNative(ctx: Ctx, root: string, gameName: string, targ
|
|
|
95
157
|
const log = join(logDir, `build-${targetName}.log`);
|
|
96
158
|
const out = join(root, "dist", targetName);
|
|
97
159
|
const binary = join(out, `${t.name ?? gameName}${platform === "windows" ? ".exe" : ""}`);
|
|
160
|
+
if (!existsSync(join(root, t.entry))) throw new CliError("ENTRY_MISSING", `native entry ${t.entry} does not exist`, `add ${t.entry} (see the macos skill) or fix targets.${targetName}.native.entry`, skill);
|
|
98
161
|
if (ctx.dryRun) {
|
|
99
162
|
print(ctx, { dryRun: true, platform, stage, vendor: vendor.dir, binary }, (): string => `would stage the engine and game in ${stage} and build ${binary}`);
|
|
100
163
|
return { binary, log, steps: [] };
|
|
@@ -104,9 +167,7 @@ export async function buildNative(ctx: Ctx, root: string, gameName: string, targ
|
|
|
104
167
|
mkdirSync(out, { recursive: true });
|
|
105
168
|
const engineStage = join(stage, "dotframe");
|
|
106
169
|
cpSync(join(ENGINE, "src"), join(engineStage, "src"), { recursive: true });
|
|
107
|
-
const gameStage = join(stage, "game");
|
|
108
|
-
copyTs(root, gameStage);
|
|
109
|
-
rewriteImports(gameStage, engineStage);
|
|
170
|
+
const gameStage = stageGame(root, t.entry, join(stage, "game"), engineStage);
|
|
110
171
|
|
|
111
172
|
const inc = [`-I${join(vendor.dir, vendor.sdl ?? "", "include")}`, `-I${join(vendor.dir, "wgpu", platform, "include")}`];
|
|
112
173
|
const lib = join(stage, "lib");
|
|
@@ -120,8 +181,6 @@ export async function buildNative(ctx: Ctx, root: string, gameName: string, targ
|
|
|
120
181
|
const ffi = JSON.parse(readFileSync(join(ENGINE, "native", `ffi.${platform}.json`), "utf8")) as { libraries: string[] };
|
|
121
182
|
ffi.libraries = [join(lib, "libdf_native.a"), join(vendor.dir, "build", `sdl-${platform}`, "libSDL3.a"), join(vendor.dir, "wgpu", platform, "lib", "libwgpu_native.a")];
|
|
122
183
|
writeFileSync(join(stage, "ffi.json"), JSON.stringify(ffi, null, 2));
|
|
123
|
-
const entry = join(gameStage, t.entry);
|
|
124
|
-
if (!existsSync(entry)) throw new CliError("ENTRY_MISSING", `native entry ${t.entry} does not exist`, `add ${t.entry} (see the macos skill) or fix targets.${targetName}.native.entry`, skill);
|
|
125
184
|
let env: Record<string, string> = {};
|
|
126
185
|
if (platform === "windows") {
|
|
127
186
|
// A devDependency of dotframe, so an npm install of dotframe does not bring it; the game can.
|
package/native/df_native.c
CHANGED
|
@@ -227,6 +227,8 @@ static void mouse_relative(double *nx, double *ny) {
|
|
|
227
227
|
// Field 0 x, 1 y (normalized), 2 buttons (bit 0 left, bit 1 middle, bit 2 right).
|
|
228
228
|
double df_mouse(int32_t field) {
|
|
229
229
|
if (field == 2) {
|
|
230
|
+
// Clicks in other apps must not reach the game: buttons count only while the window has input focus.
|
|
231
|
+
if (!(SDL_GetWindowFlags(g_window) & SDL_WINDOW_INPUT_FOCUS)) return 0;
|
|
230
232
|
SDL_MouseButtonFlags flags = SDL_GetGlobalMouseState(NULL, NULL);
|
|
231
233
|
return (flags & SDL_BUTTON_LMASK ? 1 : 0) | (flags & SDL_BUTTON_MMASK ? 2 : 0) | (flags & SDL_BUTTON_RMASK ? 4 : 0);
|
|
232
234
|
}
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ A target with `"native"` is built by the CLI itself:
|
|
|
20
20
|
"windows": { "native": { "platform": "windows", "entry": "main.native.ts" } }
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
The CLI copies the engine (`src/`) and
|
|
23
|
+
The CLI copies the engine (`src/`) and every file the entry reaches into `.dotframe/native/<target>/`: relative imports (including ones that leave the game folder, as in a monorepo port importing `../../packages/shared/src`) and workspace packages that resolve to source (`@my/shared` linked by bun or npm workspaces). It keeps their relative layout, rewrites `dotframe/...` and workspace imports to relative paths, compiles the C shim against the vendored SDL3 and wgpu-native, and runs scriptc. This is why it works from `node_modules`: scriptc treats code under `node_modules` as package code for its dynamic engine and takes only relative imports in a static build, so building in place fails.
|
|
24
24
|
|
|
25
25
|
- Vendor lives in `DOTFRAME_VENDOR`, else a dotframe git checkout's `vendor/` when populated, else `~/.dotframe/vendor`. It survives reinstalling dotframe and is shared by every game.
|
|
26
26
|
- Output goes to the game's `dist/<target>/`, never into `node_modules`.
|
|
@@ -39,6 +39,33 @@ scriptc compiles a subset of TypeScript. What tripped the templates:
|
|
|
39
39
|
|
|
40
40
|
Run `dotframe build macos` early and after each feature: one scriptc error is a quick fix, ninety are a port.
|
|
41
41
|
|
|
42
|
+
## Porting existing TypeScript
|
|
43
|
+
|
|
44
|
+
What a real port (Craft Ones, 66 errors) hit, and the fix for each:
|
|
45
|
+
|
|
46
|
+
| scriptc rejects | Do instead |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `Object.assign(target, ...)` | Assign fields one by one |
|
|
49
|
+
| `ArrayLike<T>` / `Iterable<T>` parameters | Take `T[]` |
|
|
50
|
+
| `readonly T[]`, `.find`/`.filter` on readonly tuples, `for...of` over tuples | Plain arrays (`T[]`), indexed loops |
|
|
51
|
+
| Destructuring an `unknown` network payload | Narrow field by field with `typeof` checks |
|
|
52
|
+
| Non-literal index into a tuple | Make it an array |
|
|
53
|
+
| Dynamic keyed reads on a record whose entries have different shapes | Give every entry the same shape (optional fields), or a `switch` |
|
|
54
|
+
| `string[i]` | `s.charAt(i)` or `s.charCodeAt(i)` |
|
|
55
|
+
| `Number.parseInt` in a static build | `parseInt` or `Math.trunc(Number(s))` |
|
|
56
|
+
| `++`/`--` inside an expression | Its own statement |
|
|
57
|
+
| `satisfies Record<K, Record<string, T>>` | A plain type annotation |
|
|
58
|
+
|
|
59
|
+
Runtime traps (compile clean, fail when run):
|
|
60
|
+
|
|
61
|
+
- **Structural coercion copies.** Passing a class instance, or a wider record, to a parameter typed as a narrower structural type passes a copy: the callee's writes to scalar fields are lost (arrays inside still alias). In Craft Ones every projectile froze mid-flight. Pass the exact type, or return the updated record and copy it back. Reported to scriptc.
|
|
62
|
+
- **Out-of-range reads.** `rows[y - 1]?.[x]` typed as `string` but `undefined` at runtime crashes with "undefined is not representable in the target union". Bounds-check before reading.
|
|
63
|
+
- **`JSON.stringify` key order** differed from JavaScript in Craft Ones (not reproduced in a minimal case). Never build checksums or netplay comparisons from it.
|
|
64
|
+
|
|
65
|
+
## Debugging a native crash
|
|
66
|
+
|
|
67
|
+
`--optimization dev` builds failed to link in Craft Ones (undefined `_main`; a template game links fine). When that happens, debug the release binary with lldb: `lldb ./dist/macos/<name>`, `breakpoint set -n scr_error_new`, `run`, then `bt` shows the TypeScript call site of a runtime error.
|
|
68
|
+
|
|
42
69
|
## Other notes
|
|
43
70
|
|
|
44
71
|
- Benchmarks mean nothing while other processes load the machine. Check `top` first and say what else was running.
|
|
@@ -30,6 +30,8 @@ Run several seeds and a high latency (474 ms is what a transatlantic relay measu
|
|
|
30
30
|
|
|
31
31
|
## Rules
|
|
32
32
|
|
|
33
|
+
- Build checksums from numbers in a fixed order, never from `JSON.stringify`: native builds can order keys differently from JavaScript, so a web peer and a native peer would report false desyncs.
|
|
34
|
+
|
|
33
35
|
- The camera, effects, and HUD timers are simulation state if the simulation ever reads them. Update them once per step, never per drawn frame.
|
|
34
36
|
- Render must restore any random state it uses, or use a separate visual generator.
|
|
35
37
|
- Input delay (`--delay`, default 2) trades latency for fewer rollbacks.
|
|
@@ -1,27 +1,49 @@
|
|
|
1
|
-
// Shared by main.web.ts and main.native.ts: fixed 60 Hz steps
|
|
1
|
+
// Shared by main.web.ts and main.native.ts: keyboard and pointer to inputs, fixed 60 Hz steps, one render per frame.
|
|
2
2
|
import { createDraw2D } from "dotframe/src/draw2d";
|
|
3
3
|
import type { Frame, Setup } from "dotframe/src/gpu";
|
|
4
|
-
import { Key } from "dotframe/src/input";
|
|
4
|
+
import { Key, MouseButton } from "dotframe/src/input";
|
|
5
5
|
import type { Platform } from "dotframe/src/platform";
|
|
6
|
-
import { createGame, keyInput, PLAYERS, render, step, WINDOW } from "./game";
|
|
6
|
+
import { createGame, keyInput, PLAYERS, POINTER_BIT, render, step, WINDOW } from "./game";
|
|
7
7
|
|
|
8
8
|
const STEP = 1 / 60;
|
|
9
|
+
// A full charge takes one second of holding.
|
|
10
|
+
const FULL_CHARGE = 60;
|
|
9
11
|
|
|
10
12
|
export function createSetup(): Setup {
|
|
11
|
-
return ({ gpu, input }: Platform): Frame => {
|
|
13
|
+
return ({ gpu, input, audio }: Platform): Frame => {
|
|
12
14
|
const draw = createDraw2D(gpu, WINDOW.width, WINDOW.height);
|
|
13
15
|
const game = createGame(Math.floor(Math.random() * 1e9));
|
|
14
16
|
let simulated = -1;
|
|
17
|
+
// Hold to charge, release to act: the pattern most mouse and touch games need.
|
|
18
|
+
let charge = 0;
|
|
19
|
+
let wasDown = false;
|
|
15
20
|
return (time: number): boolean => {
|
|
16
21
|
if (simulated < 0) simulated = time;
|
|
17
|
-
// Fixed steps, so every target plays exactly what dotframe sim simulates.
|
|
18
22
|
for (let n = 0; simulated + STEP <= time && n < 5; n++) {
|
|
23
|
+
const pointer = input.pointer();
|
|
24
|
+
const inside = pointer.x >= 0 && pointer.x <= 1 && pointer.y >= 0 && pointer.y <= 1;
|
|
25
|
+
const down = inside && ((pointer.buttons & MouseButton.Left) !== 0 || input.touches().length > 0);
|
|
26
|
+
let released = false;
|
|
27
|
+
if (down) charge = Math.min(FULL_CHARGE, charge + 1);
|
|
28
|
+
else if (wasDown) {
|
|
29
|
+
released = true;
|
|
30
|
+
// Audio is presentation, never simulation state: the pitch says how charged the release was.
|
|
31
|
+
audio.tone(220 + (charge / FULL_CHARGE) * 660, 0.12, 0.25);
|
|
32
|
+
charge = 0;
|
|
33
|
+
}
|
|
34
|
+
wasDown = down;
|
|
19
35
|
const keys = [keyInput(input, [Key.A, Key.D, Key.W, Key.J]), keyInput(input, [Key.Left, Key.Right, Key.Up, Key.L])];
|
|
36
|
+
if (released) keys[0] = keys[0] | POINTER_BIT;
|
|
20
37
|
step(game, keys.slice(0, PLAYERS));
|
|
21
38
|
simulated += STEP;
|
|
22
39
|
}
|
|
23
40
|
draw.begin();
|
|
24
41
|
render(game, draw);
|
|
42
|
+
if (charge > 0) {
|
|
43
|
+
// Charge meter under the play area.
|
|
44
|
+
draw.setFillStyle("#ffffff");
|
|
45
|
+
draw.fillRect(20, WINDOW.height - 16, (WINDOW.width - 40) * (charge / FULL_CHARGE), 6);
|
|
46
|
+
}
|
|
25
47
|
draw.end({ r: 0, g: 0, b: 0 });
|
|
26
48
|
return true;
|
|
27
49
|
};
|
package/templates/blank/game.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { Draw2D } from "dotframe/src/draw2d";
|
|
|
4
4
|
import type { Input } from "dotframe/src/input";
|
|
5
5
|
|
|
6
6
|
export const WINDOW = { width: 960, height: 540, title: "__NAME__" };
|
|
7
|
+
// The input bit a released mouse or touch press sets for player 1 (fires the action bit); see src/setup.ts.
|
|
8
|
+
export const POINTER_BIT = 8;
|
|
7
9
|
export const PLAYERS = 2;
|
|
8
10
|
|
|
9
11
|
export interface Game {
|
|
@@ -11,6 +11,8 @@ const RIGHT = 2;
|
|
|
11
11
|
const JUMP = 4;
|
|
12
12
|
const ATTACK = 8;
|
|
13
13
|
const GROUND = 440;
|
|
14
|
+
// The input bit a released mouse or touch press sets for player 1 (attacks); see src/setup.ts.
|
|
15
|
+
export const POINTER_BIT = ATTACK;
|
|
14
16
|
const GRAVITY = 0.8;
|
|
15
17
|
const SPEED = 5;
|
|
16
18
|
const JUMP_SPEED = 15;
|
|
@@ -9,6 +9,8 @@ export const PLAYERS = 1;
|
|
|
9
9
|
const LEFT = 1;
|
|
10
10
|
const RIGHT = 2;
|
|
11
11
|
const JUMP = 4;
|
|
12
|
+
// The input bit a released mouse or touch press sets for player 1 (jumps); see src/setup.ts.
|
|
13
|
+
export const POINTER_BIT = JUMP;
|
|
12
14
|
const GRAVITY = 0.7;
|
|
13
15
|
const SPEED = 4.5;
|
|
14
16
|
const JUMP_SPEED = 13;
|