@origingame/create-game 0.2.0 → 0.3.0
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/README.md +2 -2
- package/archetypes/README.md +1 -0
- package/archetypes/arena/.origin/setup +14 -0
- package/archetypes/arena/LICENSES.md +8 -0
- package/archetypes/arena/README.md +67 -0
- package/archetypes/arena/assets/audio/coin.ogt +9 -0
- package/archetypes/arena/assets/audio/dash.ogt +9 -0
- package/archetypes/arena/assets/audio/win.ogt +9 -0
- package/archetypes/arena/assets/fx/coin.ogt +20 -0
- package/archetypes/arena/assets/looks/arena.ogt +10 -0
- package/archetypes/arena/gitattributes +22 -0
- package/archetypes/arena/gitignore +8 -0
- package/archetypes/arena/host.d.ts +46 -0
- package/archetypes/arena/package.json +18 -0
- package/archetypes/arena/project.ogt +14 -0
- package/archetypes/arena/src/game.ts +314 -0
- package/archetypes/arena/src/main.ts +100 -0
- package/archetypes/arena/tests/duel.og.ts +32 -0
- package/archetypes/arena/tests/solo.og.ts +22 -0
- package/archetypes/arena/tsconfig.json +17 -0
- package/archetypes/idle/LICENSES.md +1 -1
- package/archetypes/idle/README.md +3 -3
- package/archetypes/idle/assets/looks/idle.ogt +1 -1
- package/archetypes/idle/assets/{skins → styles}/sparkorb/LICENSES.md +4 -4
- package/archetypes/idle/assets/{skins/sparkorb/skin.ogt → styles/sparkorb/style.ogt} +47 -47
- package/archetypes/idle/package.json +1 -1
- package/archetypes/idle/project.ogt +1 -1
- package/archetypes/idle/references/metal/card.png +0 -0
- package/archetypes/idle/references/metal/orbit.png +0 -0
- package/archetypes/idle/references/metal/title.png +0 -0
- package/archetypes/idle/src/main.ts +2 -2
- package/archetypes/idle/src/ui.ts +11 -11
- package/archetypes/idle/tools/make-assets.py +4 -4
- package/archetypes/match3/package.json +1 -1
- package/archetypes/match3/project.ogt +1 -1
- package/archetypes/rhythm/LICENSES.md +1 -1
- package/archetypes/rhythm/README.md +3 -3
- package/archetypes/rhythm/assets/{skins → styles}/lanebeat/LICENSES.md +4 -4
- package/archetypes/rhythm/assets/{skins/lanebeat/skin.ogt → styles/lanebeat/style.ogt} +45 -45
- package/archetypes/rhythm/package.json +1 -1
- package/archetypes/rhythm/project.ogt +1 -1
- package/archetypes/rhythm/references/metal/results.png +0 -0
- package/archetypes/rhythm/references/metal/title.png +0 -0
- package/archetypes/rhythm/src/main.ts +2 -2
- package/archetypes/rhythm/src/ui.ts +11 -11
- package/archetypes/roguelike/LICENSES.md +1 -1
- package/archetypes/roguelike/README.md +3 -3
- package/archetypes/roguelike/assets/{skins → styles}/lampdive/LICENSES.md +4 -4
- package/archetypes/roguelike/assets/{skins/lampdive/skin.ogt → styles/lampdive/style.ogt} +41 -41
- package/archetypes/roguelike/package.json +1 -1
- package/archetypes/roguelike/project.ogt +1 -1
- package/archetypes/roguelike/references/metal/over.png +0 -0
- package/archetypes/roguelike/references/metal/title.png +0 -0
- package/archetypes/roguelike/src/main.ts +2 -2
- package/archetypes/roguelike/src/ui.ts +7 -7
- package/archetypes/runner/package.json +1 -1
- package/archetypes/runner/project.ogt +1 -1
- package/archetypes/third-person/LICENSES.md +1 -1
- package/archetypes/third-person/README.md +3 -3
- package/archetypes/third-person/assets/{skins → styles}/ember/LICENSES.md +2 -2
- package/archetypes/third-person/assets/{skins/ember/skin.ogt → styles/ember/style.ogt} +40 -41
- package/archetypes/third-person/package.json +1 -1
- package/archetypes/third-person/project.ogt +1 -1
- package/archetypes/third-person/references/metal/beacon.png +0 -0
- package/archetypes/third-person/references/metal/dawn.png +0 -0
- package/archetypes/third-person/references/metal/title.png +0 -0
- package/archetypes/third-person/references/webgl2/beacon.png +0 -0
- package/archetypes/third-person/src/main.ts +2 -2
- package/archetypes/third-person/src/ui.ts +10 -10
- package/archetypes/topdown/package.json +1 -1
- package/archetypes/topdown/project.ogt +1 -1
- package/archetypes/tower-defense/LICENSES.md +1 -1
- package/archetypes/tower-defense/README.md +5 -5
- package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LICENSES.md +4 -4
- package/archetypes/tower-defense/assets/styles/pathguard/atlas.png +0 -0
- package/archetypes/tower-defense/assets/styles/pathguard/style.ogt +111 -0
- package/archetypes/tower-defense/package.json +1 -1
- package/archetypes/tower-defense/project.ogt +1 -1
- package/archetypes/tower-defense/references/metal/end.png +0 -0
- package/archetypes/tower-defense/references/metal/title.png +0 -0
- package/archetypes/tower-defense/references/metal/wave.png +0 -0
- package/archetypes/tower-defense/src/main.ts +2 -2
- package/archetypes/tower-defense/src/ui.ts +19 -17
- package/bin/create.ts +11 -8
- package/looks/catalogue.json +40 -40
- package/package.json +1 -1
- package/template/README.md +2 -1
- package/template/assets/audio/sting.ogt +10 -0
- package/template/assets/timelines/intro.ogt +101 -0
- package/template/assets/timelines/narration.srt +11 -0
- package/template/host.d.ts +46 -0
- package/template/package.json +1 -1
- package/template/project.ogt +1 -1
- package/template/references/metal/intro-4.png +0 -0
- package/template/references/metal/intro-8.png +0 -0
- package/template/src/main.ts +1 -1
- package/template/tests/frames.ts +134 -0
- package/template/tests/intro.og.ts +65 -0
- package/template/tests/smoke.og.ts +30 -36
- package/template/tsconfig.json +1 -1
- package/template-2d/LICENSES.md +1 -0
- package/template-2d/README.md +5 -2
- package/template-2d/assets/rigs/keeper.ogt +292 -0
- package/template-2d/assets/rigs/keeper.png +0 -0
- package/template-2d/assets/rigs/keeper.png.ogt +7 -0
- package/template-2d/package.json +1 -1
- package/template-2d/project.ogt +1 -1
- package/template-2d/src/main.ts +159 -4
- package/template-2d/tests/keeper.og.ts +93 -0
- package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +0 -111
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/OFL.txt +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf.ogt +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf.ogt +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png.ogt +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png +0 -0
- /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png.ogt +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf.ogt +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf.ogt +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/OFL.txt +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf.ogt +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png.ogt +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png +0 -0
- /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png.ogt +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf.ogt +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/OFL.txt +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png.ogt +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png +0 -0
- /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png.ogt +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf.ogt +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf.ogt +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/OFL.txt +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png.ogt +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png +0 -0
- /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png.ogt +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf.ogt +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf.ogt +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/OFL.txt +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/atlas.png.ogt +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png +0 -0
- /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png.ogt +0 -0
package/template-2d/src/main.ts
CHANGED
|
@@ -11,11 +11,20 @@
|
|
|
11
11
|
// Everything the starter shows is in og-game §8 (sprites, flipbooks,
|
|
12
12
|
// tilemaps), §13 (the controller) and og-feel §12 (its tunables).
|
|
13
13
|
//
|
|
14
|
+
// Beside the spawn a lamp keeper stands on a ramp: a 2D skeletal rig
|
|
15
|
+
// (assets/rigs/keeper.ogt, og.Skeleton2D) whose cape is a weighted mesh the
|
|
16
|
+
// idle clip deforms, whose feet two-bone IK planted on the slope
|
|
17
|
+
// (og.IkTarget2D, the Plant system), and whose lantern lights the sprite
|
|
18
|
+
// layers through og.Lighting2D (og-game §8; og-anim's 2D skeletons). Sent
|
|
19
|
+
// along the ramp (`keeper:<x>`), it plays its walk clip there, each foot
|
|
20
|
+
// planted while it carries the keeper and lifted while it swings, and
|
|
21
|
+
// breathes again when it arrives (the Walk and Plant systems).
|
|
22
|
+
//
|
|
14
23
|
// The frame's look is data: GameDesc.look names a file under assets/looks/
|
|
15
24
|
// that extends a preset of the style catalogue, applied before start runs.
|
|
16
25
|
|
|
17
26
|
import { og, defineComponent, defineResource, defineSystem, bool, f32, u32, SHAPE_BOX } from "@origingame/engine";
|
|
18
|
-
import type { FlipbookInfo, Result, TileObject } from "@origingame/engine";
|
|
27
|
+
import type { FlipbookInfo, Result, Rig2DInfo, TileObject, World } from "@origingame/engine";
|
|
19
28
|
|
|
20
29
|
/** The courier's clips, in the sheet's file order (og-game §8): `start` checks them. */
|
|
21
30
|
const CLIP = { idle: 0, run: 1, jump: 2, fall: 3 } as const;
|
|
@@ -28,6 +37,16 @@ const BOX = { w: 0.9, h: 1.9 } as const;
|
|
|
28
37
|
/** Below this the courier fell into the pit. */
|
|
29
38
|
const FALL_Y = -3;
|
|
30
39
|
const LIVES = 3;
|
|
40
|
+
/** The keeper's ramp: a plank from (x0, y0) on the roof rising at `slope` (tan 12°) to x1. */
|
|
41
|
+
const RAMP = { x0: 4.5, y0: 2, x1: 7.6, slope: 0.21256, sinHalf: 0.104528, cosHalf: 0.994522 } as const;
|
|
42
|
+
/** The ramp's top at x. */
|
|
43
|
+
const rampY = (x: number): number => RAMP.y0 + RAMP.slope * (x - RAMP.x0);
|
|
44
|
+
/** The keeper's ankles and its root above the ramp (its legs reach 1.1 from a hip 1.1 up: a slight bend), the lantern from its feet, in world units. */
|
|
45
|
+
const ANKLE = 0.12;
|
|
46
|
+
const LIFT = 0.08;
|
|
47
|
+
const LANTERN = { x: 0.25, y: 1.0 } as const;
|
|
48
|
+
/** The keeper's gait: the walk clip's 0.8 s cycle carries it a stride of 0.4 m (a step per foot), each foot lifted at most `lift` mid-swing. */
|
|
49
|
+
const WALK = { speed: 0.5, stride: 0.4, lift: 0.08 } as const;
|
|
31
50
|
|
|
32
51
|
/** The courier. */
|
|
33
52
|
export const Hero = defineComponent("Hero", { spawnX: f32(0, { unit: "m" }), spawnY: f32(0, { unit: "m" }) });
|
|
@@ -35,6 +54,9 @@ export const Hero = defineComponent("Hero", { spawnX: f32(0, { unit: "m" }), spa
|
|
|
35
54
|
/** A coin of the map's `coin` objects; taken once. */
|
|
36
55
|
export const Coin = defineComponent("Coin", { taken: bool() });
|
|
37
56
|
|
|
57
|
+
/** One of the keeper's feet: its IK target stays on the ramp `dx` ahead of the keeper (behind when negative), `lag` of a gait cycle after the front foot. */
|
|
58
|
+
export const Plant = defineComponent("Plant", { dx: f32(0, { unit: "m" }), lag: f32(0, { min: 0, max: 1 }) });
|
|
59
|
+
|
|
38
60
|
/** A voice: the sound bit (Sound) it plays when Game.sfx asks for it. */
|
|
39
61
|
export const Voice = defineComponent("Voice", { bit: u32() });
|
|
40
62
|
|
|
@@ -58,6 +80,21 @@ export const Game = defineResource("Game", {
|
|
|
58
80
|
/** The HUD's touch controls this tick (a phone): the run and the held jump. */
|
|
59
81
|
touchX: f32(0, { min: -1, max: 1 }),
|
|
60
82
|
touchJump: bool(),
|
|
83
|
+
/** The keeper's breaths: og.Skeleton2DEvent "inhale" (the idle clip's event at 0). */
|
|
84
|
+
inhales: u32(),
|
|
85
|
+
/** The keeper's footfalls: og.Skeleton2DEvent "step" (the walk clip's, one per foot). */
|
|
86
|
+
steps: u32(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/** Where the keeper walks (`keeper:<x>`), how far it has walked since it set off (its gait's phase), and its clips. */
|
|
90
|
+
export const KeeperWalk = defineResource("KeeperWalk", {
|
|
91
|
+
goal: f32(6, { unit: "m" }),
|
|
92
|
+
walked: f32(0, { unit: "m" }),
|
|
93
|
+
walking: bool(),
|
|
94
|
+
/** -1 facing left (towards the spawn), 1 right. */
|
|
95
|
+
facing: f32(-1),
|
|
96
|
+
idle: u32(),
|
|
97
|
+
walk: u32(),
|
|
61
98
|
});
|
|
62
99
|
|
|
63
100
|
function must<T>(r: Result<T>): T {
|
|
@@ -161,6 +198,80 @@ export const Animate = defineSystem("Animate", { reads: [Hero, og.PlatformerCont
|
|
|
161
198
|
}
|
|
162
199
|
});
|
|
163
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Walks the keeper towards KeeperWalk.goal along the ramp at the gait's
|
|
203
|
+
* speed (by scaled time, as the clip plays: paused, both hold), facing the
|
|
204
|
+
* way it goes, with the walk clip; on arrival it faces the spawn again with
|
|
205
|
+
* the idle clip (which starts over, breathing in).
|
|
206
|
+
*/
|
|
207
|
+
export const Walk = defineSystem("Walk", { writes: [KeeperWalk, og.Skeleton2D, og.Transform], after: ["Control"] }, (_q, time, world) => {
|
|
208
|
+
const keeper = world.find("keeper");
|
|
209
|
+
if (keeper === null) return;
|
|
210
|
+
const w = must(world.resource(KeeperWalk));
|
|
211
|
+
const x = must(world.get(keeper, og.Transform)).position.x;
|
|
212
|
+
const left = w.goal - x;
|
|
213
|
+
if (left === 0 && !w.walking) return;
|
|
214
|
+
const step = WALK.speed * time.scaledDt;
|
|
215
|
+
const arrived = Math.abs(left) <= step;
|
|
216
|
+
const nx = arrived ? w.goal : x + Math.sign(left) * step;
|
|
217
|
+
w.walked = arrived ? 0 : (w.walking ? w.walked : 0) + Math.abs(nx - x);
|
|
218
|
+
w.walking = !arrived;
|
|
219
|
+
w.facing = arrived ? -1 : Math.sign(left);
|
|
220
|
+
must(world.set(keeper, og.Transform, { position: { x: nx, y: rampY(nx) + LIFT, z: 0 } }));
|
|
221
|
+
const s = must(world.get(keeper, og.Skeleton2D));
|
|
222
|
+
const clip = arrived ? w.idle : w.walk;
|
|
223
|
+
const flipX = w.facing < 0;
|
|
224
|
+
// Only what changed: a new clip starts at 0 (the idle clip's breath, the walk's first step).
|
|
225
|
+
if (s.clip !== clip || s.flipX !== flipX) must(world.set(keeper, og.Skeleton2D, { clip, flipX }));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Puts the keeper's feet on the ramp (og.IkTarget2D: the legs' two-bone IK
|
|
230
|
+
* reaches each target, the knees bend as the hip bobs). Standing, each foot
|
|
231
|
+
* is `dx` from the keeper; walking, the front foot's gait phase is the
|
|
232
|
+
* distance walked over the stride and the back foot's half a cycle later:
|
|
233
|
+
* for half a cycle a foot stays where it is on the plank while the keeper
|
|
234
|
+
* passes over it, for the other half it swings a stride forward, lifted on
|
|
235
|
+
* a parabola. Keeps the lantern's light at its hand, and counts the
|
|
236
|
+
* clips' breaths and footfalls.
|
|
237
|
+
*/
|
|
238
|
+
export const Plant2D = defineSystem("Plant", { reads: [Plant, KeeperWalk, og.Skeleton2DEvent], writes: [Game, og.IkTarget2D, og.Transform], after: ["Walk"] }, (q, _time, world) => {
|
|
239
|
+
const keeper = world.find("keeper");
|
|
240
|
+
if (keeper === null) return;
|
|
241
|
+
const at = must(world.get(keeper, og.Transform)).position;
|
|
242
|
+
const w = must(world.resource(KeeperWalk));
|
|
243
|
+
for (const a of q.archetypes()) {
|
|
244
|
+
const plant = a.column(Plant);
|
|
245
|
+
const target = a.column(og.IkTarget2D).target;
|
|
246
|
+
for (let i = 0; i < a.count; i++) {
|
|
247
|
+
let ahead = plant.dx[i]!;
|
|
248
|
+
let lift = 0;
|
|
249
|
+
if (w.walking) {
|
|
250
|
+
const phase = w.walked / WALK.stride + plant.lag[i]!;
|
|
251
|
+
const p = phase - Math.floor(phase);
|
|
252
|
+
if (p < 0.5) {
|
|
253
|
+
// Planted: it slides back under the keeper as fast as the keeper walks.
|
|
254
|
+
ahead += WALK.stride * (0.25 - p);
|
|
255
|
+
} else {
|
|
256
|
+
const s = (p - 0.5) * 2;
|
|
257
|
+
ahead += WALK.stride * (s - 0.5) * 0.5;
|
|
258
|
+
lift = WALK.lift * 4 * s * (1 - s);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const x = at.x + w.facing * ahead;
|
|
262
|
+
target[i * 2] = x;
|
|
263
|
+
target[i * 2 + 1] = rampY(x) + ANKLE + lift;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const lantern = world.find("lantern");
|
|
267
|
+
if (lantern !== null) must(world.set(lantern, og.Transform, { position: { x: at.x - w.facing * LANTERN.x, y: at.y + LANTERN.y, z: 0 } }));
|
|
268
|
+
const g = must(world.resource(Game));
|
|
269
|
+
for (const e of world.events(og.Skeleton2DEvent)) {
|
|
270
|
+
if (e.name === "inhale") g.inhales += 1;
|
|
271
|
+
else if (e.name === "step") g.steps += 1;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
164
275
|
/** Plays each sound asked for once: a voice still playing is stopped this tick and played again the next. */
|
|
165
276
|
export const Voices = defineSystem("Voices", { reads: [Voice], writes: [Game, og.AudioSource], after: ["Pickups"] }, (q, _time, world) => {
|
|
166
277
|
const g = must(world.resource(Game));
|
|
@@ -212,6 +323,42 @@ export const Hud = defineSystem("Hud", { writes: [Game] }, (_q, _time, world) =>
|
|
|
212
323
|
}
|
|
213
324
|
});
|
|
214
325
|
|
|
326
|
+
/**
|
|
327
|
+
* The lamp keeper: a plank ramp, the rig on it facing the spawn, a foot
|
|
328
|
+
* target per leg, and its lantern's light; og.Lighting2D lights layers 1
|
|
329
|
+
* and 2 (the keeper, the courier, the coins), the city stays as drawn.
|
|
330
|
+
*/
|
|
331
|
+
function spawnKeeper(world: World, rig: Rig2DInfo): void {
|
|
332
|
+
const idle = og.anim.clip(rig, "idle");
|
|
333
|
+
const walk = og.anim.clip(rig, "walk");
|
|
334
|
+
if (idle < 0 || walk < 0) throw new Error("rigs/keeper.ogt needs clips idle and walk");
|
|
335
|
+
const mid = (RAMP.x0 + RAMP.x1) / 2;
|
|
336
|
+
const plank = must(world.spawn({ name: "ramp", position: { x: mid, y: rampY(mid) - 0.2, z: 0 }, quaternion: { x: 0, y: 0, z: RAMP.sinHalf, w: RAMP.cosHalf } }));
|
|
337
|
+
// The plank is a region of the keeper's texture (pixels 24..64 x 32..40 of 64 x 64).
|
|
338
|
+
must(world.insert(plank, og.Sprite, { texture: rig.texture, width: RAMP.x1 - RAMP.x0, height: 0.4, uvMin: { x: 0.375, y: 0.5 }, uvMax: { x: 1, y: 0.625 }, layer: 1 }));
|
|
339
|
+
const x = 6;
|
|
340
|
+
const keeper = must(world.spawn({ name: "keeper", position: { x, y: rampY(x) + LIFT, z: 0 } }));
|
|
341
|
+
must(world.insert(keeper, og.Skeleton2D, { rig: rig.rig, clip: idle, flipX: true, layer: 1 }));
|
|
342
|
+
const w = must(world.resource(KeeperWalk));
|
|
343
|
+
Object.assign(w, { goal: x, walked: 0, walking: false, facing: -1, idle, walk });
|
|
344
|
+
// Facing left, the front foot is the left one; the back foot steps half a cycle later.
|
|
345
|
+
for (const [name, ahead, lag] of [["leg-f", 0.15, 0], ["leg-b", -0.15, 0.5]] as const) {
|
|
346
|
+
const constraint = rig.constraints.indexOf(name);
|
|
347
|
+
if (constraint < 0) throw new Error(`rigs/keeper.ogt has no ik ${name}`);
|
|
348
|
+
const fx = x - ahead;
|
|
349
|
+
const foot = must(world.spawn({ name: `keeper-${name}` }));
|
|
350
|
+
must(world.insert(foot, og.IkTarget2D, { skeleton: keeper, constraint, target: { x: fx, y: rampY(fx) + ANKLE }, tolerance: 0.02 }));
|
|
351
|
+
must(world.insert(foot, Plant, { dx: ahead, lag }));
|
|
352
|
+
}
|
|
353
|
+
const light = must(world.spawn({ name: "lantern", position: { x: x + LANTERN.x, y: rampY(x) + LIFT + LANTERN.y, z: 0 } }));
|
|
354
|
+
must(world.insert(light, og.Light2D, { color: { r: 1, g: 0.72, b: 0.35 }, intensity: 1.6, radius: 4, castShadows: false }));
|
|
355
|
+
const lighting = must(world.resource(og.Lighting2D));
|
|
356
|
+
lighting.enabled = true;
|
|
357
|
+
lighting.ambient = { r: 0.62, g: 0.64, b: 0.78 };
|
|
358
|
+
lighting.minLayer = 1;
|
|
359
|
+
lighting.maxLayer = 2;
|
|
360
|
+
}
|
|
361
|
+
|
|
215
362
|
function checkClips(info: FlipbookInfo): void {
|
|
216
363
|
for (const [name, index] of Object.entries(CLIP)) {
|
|
217
364
|
const at = og.anim.clip(info, name);
|
|
@@ -229,13 +376,14 @@ export default og.game({
|
|
|
229
376
|
look: "looks/origin-game-2d.ogt",
|
|
230
377
|
tick: 60,
|
|
231
378
|
seed: 1,
|
|
232
|
-
resources: [Game],
|
|
233
|
-
systems: [Hud, Control, Pickups, Animate, Voices],
|
|
379
|
+
resources: [Game, KeeperWalk],
|
|
380
|
+
systems: [Hud, Control, Pickups, Animate, Walk, Plant2D, Voices],
|
|
234
381
|
start(world) {
|
|
235
382
|
const map = must(og.load.tilemap(world, "maps/level.ogt"));
|
|
236
383
|
const courier = must(og.load.flipbook(world, "sprites/courier.ogt"));
|
|
237
384
|
checkClips(courier);
|
|
238
385
|
const coin = must(og.load.flipbook(world, "sprites/coin.ogt"));
|
|
386
|
+
spawnKeeper(world, must(og.load.rig2d(world, "rigs/keeper.ogt")));
|
|
239
387
|
const height = map.height * map.tileHeight;
|
|
240
388
|
|
|
241
389
|
const camera = must(world.resource(og.Camera));
|
|
@@ -293,13 +441,20 @@ export default og.game({
|
|
|
293
441
|
if (hero === null || !Number.isFinite(x) || !Number.isFinite(y)) throw new Error("write warp:<x>:<y>");
|
|
294
442
|
must(world.set(hero, og.Transform, { position: { x, y, z: 0 } }));
|
|
295
443
|
},
|
|
444
|
+
/** `keeper:<x>`: send the keeper walking along the ramp to x (the Walk system moves it; its feet step on the plank). */
|
|
445
|
+
keeper: (world, args) => {
|
|
446
|
+
const x = Math.max(RAMP.x0 + 0.4, Math.min(RAMP.x1 - 0.4, Number(args[0])));
|
|
447
|
+
if (world.find("keeper") === null || !Number.isFinite(x)) throw new Error("write keeper:<x>");
|
|
448
|
+
must(world.resource(KeeperWalk)).goal = x;
|
|
449
|
+
},
|
|
296
450
|
},
|
|
297
451
|
commandHelp: {
|
|
298
452
|
warp: { usage: "<x>:<y>", doc: "Put the courier's feet at a world point." },
|
|
453
|
+
keeper: { usage: "<x>", doc: "Send the lamp keeper walking along its ramp to x." },
|
|
299
454
|
},
|
|
300
455
|
facts: (world) => {
|
|
301
456
|
const g = world.resource(Game);
|
|
302
457
|
if (!g.ok) return {};
|
|
303
|
-
return { coins: g.value.coins, total: g.value.total, lives: g.value.lives, won: g.value.won, x: g.value.heroX, y: g.value.heroY };
|
|
458
|
+
return { coins: g.value.coins, total: g.value.total, lives: g.value.lives, won: g.value.won, inhales: g.value.inhales, steps: g.value.steps, x: g.value.heroX, y: g.value.heroY };
|
|
304
459
|
},
|
|
305
460
|
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// The lamp keeper's playtest (og-game §8, 2D skeletons): the rig plays its
|
|
2
|
+
// idle clip (its event counted as breaths) with both legs' IK targets on
|
|
3
|
+
// the ramp and reached; `keeper:7` sends it walking up the ramp with its
|
|
4
|
+
// walk clip, facing the way it goes, one foot planted on the plank while
|
|
5
|
+
// the other swings above it; it arrives, faces the spawn and breathes again
|
|
6
|
+
// with both feet planted, higher.
|
|
7
|
+
import { playtest } from "@origingame/engine/playtest";
|
|
8
|
+
|
|
9
|
+
interface Got<T> {
|
|
10
|
+
value: T;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Fields = Record<string, number | boolean | string | number[]>;
|
|
14
|
+
|
|
15
|
+
interface EntityDump {
|
|
16
|
+
components: Record<string, Fields>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The ramp's top at x (src/main.ts's RAMP), plus the ankle's height. */
|
|
20
|
+
const ankleY = (x: number): number => 2 + 0.21256 * (x - 4.5) + 0.12;
|
|
21
|
+
/** The plank's ends and the highest a swinging foot lifts (src/main.ts's RAMP and WALK). */
|
|
22
|
+
const PLANK = { x0: 4.5, x1: 7.6 } as const;
|
|
23
|
+
const LIFT = 0.08;
|
|
24
|
+
/** The rig's clips, in file order. */
|
|
25
|
+
const IDLE = 0;
|
|
26
|
+
const WALK = 1;
|
|
27
|
+
|
|
28
|
+
export default playtest({
|
|
29
|
+
name: "The lamp keeper breathes on its ramp with both feet planted, walks up the slope stepping on the plank, and stands planted again",
|
|
30
|
+
commands:
|
|
31
|
+
"1.0:screenshot:keeper.png;1.01:dump_entity:keeper:keeper.json;1.02:dump_entity:keeper-leg-f:front.json;1.03:dump_entity:keeper-leg-b:back.json;" +
|
|
32
|
+
"2.1:get:Game:game.json;2.2:keeper:7;" +
|
|
33
|
+
"2.6:screenshot:walk.png;2.61:dump_entity:keeper:walk.json;2.62:dump_entity:keeper-leg-f:walk-front.json;2.63:dump_entity:keeper-leg-b:walk-back.json;" +
|
|
34
|
+
"2.9:dump_entity:keeper:walk-later.json;2.91:dump_entity:keeper-leg-f:walk-front-later.json;2.92:dump_entity:keeper-leg-b:walk-back-later.json;" +
|
|
35
|
+
"4.6:dump_entity:keeper:keeper-up.json;4.61:dump_entity:keeper-leg-f:front-up.json;4.62:dump_entity:keeper-leg-b:back-up.json;4.7:get:Game:game-end.json;4.8:quit",
|
|
36
|
+
check(run, t) {
|
|
37
|
+
t.ok(run.quit, "the script reached quit");
|
|
38
|
+
for (const png of ["keeper.png", "walk.png"]) {
|
|
39
|
+
t.frame(png, () => {
|
|
40
|
+
const frame = run.png(png);
|
|
41
|
+
t.ok(frame.width > 0 && frame.height > 0, `a ${frame.width}x${frame.height} frame was captured`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const skeleton = (file: string) => run.json<EntityDump>(file).components["og.Skeleton2D"]!;
|
|
45
|
+
const at = (file: string) => (run.json<EntityDump>(file).components["og.Transform"]!["position"] as number[])[0]!;
|
|
46
|
+
const foot = (file: string) => run.json<EntityDump>(file).components["og.IkTarget2D"]!;
|
|
47
|
+
|
|
48
|
+
// Standing: the idle clip, both feet on the ramp under the keeper.
|
|
49
|
+
const idle = skeleton("keeper.json");
|
|
50
|
+
t.ok(idle["clip"] === IDLE && (idle["time"] as number) > 0.9 && (idle["time"] as number) < 1.1, `the idle clip plays (clip ${idle["clip"]}, time ${idle["time"]})`);
|
|
51
|
+
for (const [file, label, dx, x0] of [["front.json", "front", -0.15, 6], ["back.json", "back", 0.15, 6], ["front-up.json", "front after the walk", -0.15, 7], ["back-up.json", "back after the walk", 0.15, 7]] as const) {
|
|
52
|
+
const ik = foot(file);
|
|
53
|
+
const target = ik["target"] as number[];
|
|
54
|
+
const x = x0 + dx;
|
|
55
|
+
t.ok(Math.abs(target[0]! - x) < 1e-4 && Math.abs(target[1]! - ankleY(x)) < 1e-4, `the ${label} foot's target is on the ramp (${target.join(", ")})`);
|
|
56
|
+
t.ok(ik["reached"] === true, `the ${label} foot reached it (error ${ik["error"]})`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Walking: the walk clip, facing up the ramp, the keeper between where it was and where it goes.
|
|
60
|
+
let planted = 0;
|
|
61
|
+
for (const [keeper, front, back] of [["walk.json", "walk-front.json", "walk-back.json"], ["walk-later.json", "walk-front-later.json", "walk-back-later.json"]] as const) {
|
|
62
|
+
const s = skeleton(keeper);
|
|
63
|
+
const x = at(keeper);
|
|
64
|
+
t.ok(s["clip"] === WALK && s["flipX"] === false, `walking, the keeper plays its walk clip facing right (clip ${s["clip"]}, flipX ${s["flipX"]})`);
|
|
65
|
+
t.ok(x > 6 && x < 7, `walking, it is part of the way along the ramp (x ${x})`);
|
|
66
|
+
for (const [file, label] of [[front, "front"], [back, "back"]] as const) {
|
|
67
|
+
const ik = foot(file);
|
|
68
|
+
const [fx, fy] = ik["target"] as [number, number];
|
|
69
|
+
const lift = fy - ankleY(fx);
|
|
70
|
+
t.ok(fx > PLANK.x0 && fx < PLANK.x1 && lift > -1e-4 && lift <= LIFT + 1e-4, `walking, the ${label} foot is on the plank or above it (x ${fx}, ${lift.toFixed(4)} up)`);
|
|
71
|
+
t.ok(ik["reached"] === true, `walking, the ${label} foot reached its target (error ${ik["error"]})`);
|
|
72
|
+
if (Math.abs(lift) < 1e-4) planted += 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
t.ok(planted >= 2, `while it walks a foot stays planted on the plank (${planted} of 4 targets on it)`);
|
|
76
|
+
// The back foot carried the keeper from 2.6 s to 2.9 s: it did not move.
|
|
77
|
+
const before = foot("walk-back.json")["target"] as number[];
|
|
78
|
+
const later = foot("walk-back-later.json")["target"] as number[];
|
|
79
|
+
t.ok(Math.abs(before[0]! - later[0]!) < 1e-4 && Math.abs(before[1]! - later[1]!) < 1e-4, `the planted foot stays put while the keeper passes over it (${before.join(", ")} then ${later.join(", ")})`);
|
|
80
|
+
|
|
81
|
+
// Arrived: idle again, facing the spawn, at x 7.
|
|
82
|
+
const up = skeleton("keeper-up.json");
|
|
83
|
+
t.ok(up["clip"] === IDLE && up["flipX"] === true && Math.abs(at("keeper-up.json") - 7) < 1e-4, `it arrived at 7 and breathes facing the spawn (clip ${up["clip"]}, x ${at("keeper-up.json")})`);
|
|
84
|
+
|
|
85
|
+
// A loop of 2 s from tick 0 to 2.1 s passed its event at 0 and at 2; the walk
|
|
86
|
+
// (2 s at 0.5 m/s, a step every 0.4 s) stepped 5 times, and the idle clip
|
|
87
|
+
// started over on arrival with a breath.
|
|
88
|
+
const game = run.json<Got<{ inhales: number }>>("game.json").value;
|
|
89
|
+
t.ok(game.inhales === 2, `og.Skeleton2DEvent counted two breaths before the walk (${game.inhales})`);
|
|
90
|
+
const end = run.json<Got<{ inhales: number; steps: number }>>("game-end.json").value;
|
|
91
|
+
t.ok(end.steps === 5 && end.inhales === 3, `the walk clip's events counted 5 steps, then a third breath (${end.steps}, ${end.inhales})`);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
Binary file
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
type skin
|
|
2
|
-
version 1
|
|
3
|
-
---
|
|
4
|
-
# Path Guard's skin: the engine's cartoon style pack (templates/ui-kit/tools/make-skins.py; CC0 art, OFL fonts;
|
|
5
|
-
# LICENSES.md) in a night-garden palette: cream panels on the dark lawn, the lantern's orange as the one accent.
|
|
6
|
-
name "pathguard"
|
|
7
|
-
base light
|
|
8
|
-
atlas "skins/pathguard/atlas.png" 384 256
|
|
9
|
-
scale 1
|
|
10
|
-
palette {
|
|
11
|
-
bg "#10231a"
|
|
12
|
-
bgSubtle "#15301f"
|
|
13
|
-
surface "#fff4dc"
|
|
14
|
-
surfaceRaised "#fff4dc"
|
|
15
|
-
surfaceHover "#ffe9bd"
|
|
16
|
-
materialThick "#fff4dcf5"
|
|
17
|
-
materialRegular "#fff4dcee"
|
|
18
|
-
materialThin "#fff4dce0"
|
|
19
|
-
fg "#0f1a14"
|
|
20
|
-
fgMuted "#3a483f"
|
|
21
|
-
fgSubtle "#45534a"
|
|
22
|
-
fgInverse "#fff4dc"
|
|
23
|
-
fill "#ffe08a"
|
|
24
|
-
fillStrong "#ffd466"
|
|
25
|
-
border "#20302a"
|
|
26
|
-
borderStrong "#20302a"
|
|
27
|
-
accent "#b44c0d"
|
|
28
|
-
accentHover "#ab480c"
|
|
29
|
-
accentActive "#a0430b"
|
|
30
|
-
accentFg "#fff4dc"
|
|
31
|
-
accentInk "#a8400a"
|
|
32
|
-
accentSoft "#ff8c3a40"
|
|
33
|
-
focus "#5cb6e8"
|
|
34
|
-
thumb "#ffffff"
|
|
35
|
-
scrim "#07120ccc"
|
|
36
|
-
success "#06681b"
|
|
37
|
-
warn "#7b5102"
|
|
38
|
-
danger "#700b1c"
|
|
39
|
-
info "#2256af"
|
|
40
|
-
shadow "#0b1a1299"
|
|
41
|
-
}
|
|
42
|
-
radius {
|
|
43
|
-
control 12
|
|
44
|
-
field 14
|
|
45
|
-
block 14
|
|
46
|
-
card 18
|
|
47
|
-
panel 28
|
|
48
|
-
sheet 28
|
|
49
|
-
button 9999
|
|
50
|
-
}
|
|
51
|
-
ramp {
|
|
52
|
-
body 15 20
|
|
53
|
-
meta 13 18
|
|
54
|
-
caption 13 18
|
|
55
|
-
lg 19 26
|
|
56
|
-
xl 22 28
|
|
57
|
-
xl2 26 30
|
|
58
|
-
xl3 30 34
|
|
59
|
-
displaySm 32 36
|
|
60
|
-
displayMd 40 44
|
|
61
|
-
displayLg 52 56
|
|
62
|
-
}
|
|
63
|
-
durations {
|
|
64
|
-
instant 110
|
|
65
|
-
fast 160
|
|
66
|
-
base 280
|
|
67
|
-
slow 420
|
|
68
|
-
}
|
|
69
|
-
fonts {
|
|
70
|
-
body "skins/pathguard/Fredoka-SemiBold.ttf"
|
|
71
|
-
display "skins/pathguard/LilitaOne-Regular.ttf"
|
|
72
|
-
numeric "skins/pathguard/LilitaOne-Regular.ttf"
|
|
73
|
-
fallback "fonts/NotoSansSC-subset.ttf"
|
|
74
|
-
}
|
|
75
|
-
icons "skins/pathguard/icons.png" 8 4 {
|
|
76
|
-
names "heart" "star" "coin" "lock" "pause" "gear" "sword" "shield" "potion" "gem" "key" "map" "bolt" "clock" "trophy" "home" "plus" "close" "check" "play" "back" "bag" "bomb" "crown" "flag" "apple" "leaf" "fire" "drop" "skull" "music" "person"
|
|
77
|
-
}
|
|
78
|
-
slice panel 4 4 96 96 30 tint
|
|
79
|
-
slice card 104 4 64 64 20 tint
|
|
80
|
-
slice button 172 4 48 48 20 tint
|
|
81
|
-
slice buttonHover 224 4 48 48 20 tint
|
|
82
|
-
slice buttonPressed 276 4 48 48 20 tint
|
|
83
|
-
slice buttonDisabled 328 4 48 48 20 tint
|
|
84
|
-
slice field 4 104 48 48 16 tint
|
|
85
|
-
slice slot 56 104 48 48 16 tint
|
|
86
|
-
slice tooltip 108 104 32 32 13 tint
|
|
87
|
-
slice capsule 144 104 32 32 15 tint
|
|
88
|
-
slice track 180 104 32 16 7 tint
|
|
89
|
-
slice fill 216 104 32 16 7 tint
|
|
90
|
-
soft 252 104 64 64 24
|
|
91
|
-
divider 4 172 128 24 48 16 accent
|
|
92
|
-
flourish 136 172 64 24 22 10 accent 2
|
|
93
|
-
shadow panel 0 8 0 0 "#0b1a12cc"
|
|
94
|
-
shadow card 0 5 0 0 "#0b1a1299"
|
|
95
|
-
shadow button 0 5 0 0 "#0b1a12cc"
|
|
96
|
-
shadow capsule 0 4 0 0 "#0b1a1299"
|
|
97
|
-
shadow slot 0 3 0 0 "#0b1a1266"
|
|
98
|
-
textShadow display 0 4 0 "#0b1a12"
|
|
99
|
-
# Body text gets a hairline of its own ink to the right: the Chinese fallback
|
|
100
|
-
# (Noto Sans SC Regular) reads at the weight of the Latin face.
|
|
101
|
-
textShadow body 1 0 0 "#20302a"
|
|
102
|
-
press 1.08 0.88 3
|
|
103
|
-
hover -3 1.04
|
|
104
|
-
motion {
|
|
105
|
-
enter pop
|
|
106
|
-
stagger 50
|
|
107
|
-
toast pop
|
|
108
|
-
progress 360
|
|
109
|
-
shake 8
|
|
110
|
-
pop 0.22
|
|
111
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf.ogt
RENAMED
|
File without changes
|
|
File without changes
|
/package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf.ogt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|