Refactoring: one file per node, consistent comments, shared helpers, split large modules
- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry separated, writeTyped() replaces duplicated typed writes - Comments: one style (JSDoc on non-obvious exports, // for decisions and traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars - Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts, commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe, Input.axis, setMode, titleText, World.worldPosition, unused ctor params) - Editor: Inspector split into inspector/ sections, App.ts into Editor.ts + debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared "Add collider" action, unified type labels/swatches - UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers, Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes - Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections - Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
3234eb480f
commit
1eb28314cf
189 files changed
+6231
-5543
No files matched your search
+130
-174
@@ -6,7 +6,7 @@
|
||||
* Title screen (a container with a panel, texts and a clickable Start button)
|
||||
* Level (ground, platforms, walls, ten coins, the player and the HUD labels)
|
||||
* Coin (sprite plus trigger collider – placed ten times inside the level container)
|
||||
* Win / Lose (containers shown when the coins are collected or the 30 seconds are up)
|
||||
* Win / Lose (containers shown when the coins are collected or the time is up)
|
||||
*
|
||||
* Screens are containers that are switched on and off with the `enabled` property – the same
|
||||
* switch the inspector shows for every object. Everything else is nodes: gravity and jumping
|
||||
@@ -14,7 +14,8 @@
|
||||
*
|
||||
* Usage: PATH=/opt/node22/bin:$PATH node tools/seed-jumprun.mjs [projectId] [baseUrl]
|
||||
*/
|
||||
import { PROJECTS, cliArgs, writeProject } from './projects.mjs';
|
||||
import { containerFactory, edge, node, sceneObject, variable } from './lib/builders.mjs';
|
||||
import { PROJECTS, cliArgs, writeProject } from './lib/projects.mjs';
|
||||
|
||||
const { projectId, baseUrl } = cliArgs('seed-jumprun.mjs', PROJECTS.jumpRun);
|
||||
|
||||
@@ -28,38 +29,29 @@ const COIN = 0xf5c542;
|
||||
const PANEL = 0x11141c;
|
||||
const BUTTON = 0x63c74d;
|
||||
const WHITE = 0xf2f5f7;
|
||||
const HINT = 0x9aa3b2;
|
||||
const BUTTON_TEXT = 0x10240a;
|
||||
|
||||
// --- level geometry (the camera shows x −320…320, y −180…180; +y is down) ---------------------
|
||||
|
||||
/** A solid box: the sprite plus a collider child of the same size. */
|
||||
const solid = (id, x, y, w, h, color, name = '') => [
|
||||
{ id, assetId: 'box', name, position: { x, y, z: 0 }, overrides: { width: w, height: h, color }, parentId: '', templateId: '' },
|
||||
{ id: `${id}-c`, assetId: 'solid', name: '', position: { x: 0, y: 0, z: 0 }, overrides: { width: w, height: h }, parentId: id, templateId: '' },
|
||||
const solid = (id, x, y, w, h, color) => [
|
||||
sceneObject(id, 'box', { x, y, overrides: { width: w, height: h, color } }),
|
||||
sceneObject(`${id}-c`, 'solid', { parentId: id, overrides: { width: w, height: h } }),
|
||||
];
|
||||
|
||||
/**
|
||||
* The coin is a container asset of its own: a sprite with a trigger collider child, so it reports
|
||||
* the touch without blocking. The ten coins in the level are instances of it – changing the
|
||||
* template changes all of them.
|
||||
*/
|
||||
const text = (id, x, y, content, { size = 28, color = WHITE, align = 'center', name = '', parentId = '', z = 1 } = {}) =>
|
||||
sceneObject(id, 'label', { name, x, y, z, parentId, overrides: { text: content, fontSize: size, color, align } });
|
||||
|
||||
/** One placed coin: the container object; its content comes from the coin template. */
|
||||
const coin = (id, x, y) => sceneObject(id, 'coin', { name: 'Coin', x, y });
|
||||
|
||||
/** The coin: a sprite with a trigger collider child, so it reports the touch without blocking. */
|
||||
const coinTemplate = [
|
||||
{ id: 'coin-body', assetId: 'coin-sprite', name: '', position: { x: 0, y: 0, z: 1 }, overrides: {}, parentId: '', templateId: '' },
|
||||
{ id: 'coin-body-c', assetId: 'trigger', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: 'coin-body', templateId: '' },
|
||||
sceneObject('coin-body', 'coin-sprite', { z: 1 }),
|
||||
sceneObject('coin-body-c', 'trigger', { parentId: 'coin-body' }),
|
||||
];
|
||||
|
||||
/** One placed coin: the container object, its content comes from the template above. */
|
||||
const coin = (id, x, y) => ({ id, assetId: 'coin', name: 'Coin', position: { x, y, z: 0 }, overrides: {}, parentId: '', templateId: '' });
|
||||
|
||||
const text = (id, x, y, content, { size = 28, color = WHITE, align = 'center', name = '', parentId = '', z = 1 } = {}) => ({
|
||||
id,
|
||||
assetId: 'label',
|
||||
name,
|
||||
position: { x, y, z },
|
||||
overrides: { text: content, fontSize: size, color, align },
|
||||
parentId,
|
||||
templateId: '',
|
||||
});
|
||||
|
||||
const levelTemplate = [
|
||||
// Ground, side walls and six platforms.
|
||||
...solid('ground', 0, 165, 660, 30, GRASS),
|
||||
@@ -83,65 +75,37 @@ const levelTemplate = [
|
||||
coin('c9', 255, -90),
|
||||
coin('c10', 290, 128),
|
||||
// The player: sprite, solid collider and the behavior that owns it.
|
||||
{ id: 'player', assetId: 'player-sprite', name: 'Player', position: { x: -270, y: 125, z: 2 }, overrides: {}, parentId: '', templateId: '' },
|
||||
{ id: 'player-c', assetId: 'solid', name: '', position: { x: 0, y: 0, z: 0 }, overrides: { width: 30, height: 40 }, parentId: 'player', templateId: '' },
|
||||
{ id: 'player-logic', assetId: 'player', name: '', position: { x: 0, y: -60, z: 0 }, overrides: {}, parentId: 'player', templateId: '' },
|
||||
sceneObject('player', 'player-sprite', { name: 'Player', x: -270, y: 125, z: 2 }),
|
||||
sceneObject('player-c', 'solid', { parentId: 'player', overrides: { width: 30, height: 40 } }),
|
||||
sceneObject('player-logic', 'player', { parentId: 'player', y: -60 }),
|
||||
// HUD, top left and top right of the camera frame.
|
||||
text('time-label', -300, -160, 'Time 30', { size: 22, align: 'left', name: 'TimeLabel', z: 3 }),
|
||||
text('coin-label', 300, -160, 'Coins 0/10', { size: 22, align: 'right', name: 'CoinLabel', z: 3 }),
|
||||
];
|
||||
|
||||
/** A full-frame panel with a headline, a line of help text and a button carrying a behavior. */
|
||||
const screenTemplate = (prefix, headline, hint, buttonLabel, behaviorId, headlineSize = 52) => [
|
||||
{ id: `${prefix}-panel`, assetId: 'box', name: '', position: { x: 0, y: 0, z: 0 }, overrides: { width: 640, height: 360, color: PANEL }, parentId: '', templateId: '' },
|
||||
text(`${prefix}-title`, 0, -70, headline, { size: headlineSize }),
|
||||
text(`${prefix}-hint`, 0, -10, hint, { size: 20, color: 0x9aa3b2 }),
|
||||
{ id: `${prefix}-button`, assetId: 'box', name: '', position: { x: 0, y: 80, z: 1 }, overrides: { width: 240, height: 60, color: BUTTON }, parentId: '', templateId: '' },
|
||||
text(`${prefix}-button-label`, 0, 0, buttonLabel, { size: 26, color: 0x10240a, parentId: `${prefix}-button`, z: 1 }),
|
||||
{ id: `${prefix}-button-logic`, assetId: behaviorId, name: '', position: { x: 0, y: -60, z: 0 }, overrides: {}, parentId: `${prefix}-button`, templateId: '' },
|
||||
const screenTemplate = (prefix, headline, hint, buttonLabel, behaviorId) => [
|
||||
sceneObject(`${prefix}-panel`, 'box', { overrides: { width: 640, height: 360, color: PANEL } }),
|
||||
text(`${prefix}-title`, 0, -70, headline, { size: 52 }),
|
||||
text(`${prefix}-hint`, 0, -10, hint, { size: 20, color: HINT }),
|
||||
sceneObject(`${prefix}-button`, 'box', { y: 80, z: 1, overrides: { width: 240, height: 60, color: BUTTON } }),
|
||||
text(`${prefix}-button-label`, 0, 0, buttonLabel, { size: 26, color: BUTTON_TEXT, parentId: `${prefix}-button` }),
|
||||
sceneObject(`${prefix}-button-logic`, behaviorId, { parentId: `${prefix}-button`, y: -60 }),
|
||||
];
|
||||
|
||||
const titleTemplate = screenTemplate('title', 'JUMP & RUN', 'Arrow keys to move, Space to jump.', 'START', 'start-button');
|
||||
const winTemplate = screenTemplate('win', 'YOU WIN!', 'All ten coins collected in time.', 'PLAY AGAIN', 'restart-button');
|
||||
const loseTemplate = screenTemplate('lose', 'TIME UP', 'The time is over - try again.', 'PLAY AGAIN', 'restart-button');
|
||||
|
||||
/** Which template belongs to which container asset – the lookup `expand` needs for nesting. */
|
||||
const containerTemplates = {
|
||||
const templates = {
|
||||
level: levelTemplate,
|
||||
coin: coinTemplate,
|
||||
title: titleTemplate,
|
||||
win: winTemplate,
|
||||
lose: loseTemplate,
|
||||
title: screenTemplate('title', 'JUMP & RUN', 'Arrow keys to move, Space to jump.', 'START', 'start-button'),
|
||||
win: screenTemplate('win', 'YOU WIN!', 'All ten coins collected in time.', 'PLAY AGAIN', 'restart-button'),
|
||||
lose: screenTemplate('lose', 'TIME UP', 'The time is over - try again.', 'PLAY AGAIN', 'restart-button'),
|
||||
};
|
||||
|
||||
/**
|
||||
* A copy of every object of `template`, parented below `hostId` and remembering its template id.
|
||||
* A template object that is itself a container is expanded in turn (unless the template already
|
||||
* spells its content out) – exactly what `instantiateContainer` does in the editor.
|
||||
*/
|
||||
const expand = (template, hostId) => {
|
||||
const objects = [];
|
||||
for (const t of template) {
|
||||
const id = `${hostId}-${t.id}`;
|
||||
objects.push({ ...t, id, parentId: t.parentId ? `${hostId}-${t.parentId}` : hostId, templateId: t.id });
|
||||
const nested = containerTemplates[t.assetId];
|
||||
if (nested && !template.some((o) => o.parentId === t.id)) objects.push(...expand(nested, id));
|
||||
}
|
||||
return objects;
|
||||
};
|
||||
|
||||
/** One placed container: the container object plus a copy of every template object. */
|
||||
const instantiate = (containerId, assetId, name, position, overrides = {}, extra = {}) => [
|
||||
{ id: containerId, assetId, name, position, overrides, parentId: '', templateId: '', ...extra },
|
||||
...expand(containerTemplates[assetId], containerId),
|
||||
];
|
||||
const { instantiate } = containerFactory(templates);
|
||||
|
||||
// --- graphs ----------------------------------------------------------------------------------
|
||||
|
||||
const edge = (id, from, fromPort, to, toPort) => ({ id, from: { node: from, port: fromPort }, to: { node: to, port: toPort } });
|
||||
|
||||
/**
|
||||
* "Player": gravity, walking, jumping and picking coins up – 35 nodes in three strands.
|
||||
* "Player": gravity, walking, jumping and picking coins up – three strands.
|
||||
*
|
||||
* On update vy += gravity·dt, then Move(owner) by (axis·speed, vy)·dt with collisions on.
|
||||
* Blocked vertically while falling means standing: vy back to 0, onGround true.
|
||||
@@ -151,45 +115,43 @@ const edge = (id, from, fromPort, to, toPort) => ({ id, from: { node: from, port
|
||||
*/
|
||||
const playerGraph = {
|
||||
nodes: [
|
||||
{ id: 'p-upd', type: 'OnUpdate', x: 0, y: 0, params: {} },
|
||||
{ id: 'p-owner', type: 'Owner', x: 0, y: 140, params: {} },
|
||||
{ id: 'p-axis', type: 'AxisInput', x: 0, y: 220, params: { left: 'ArrowLeft', right: 'ArrowRight', up: '', down: '' } },
|
||||
{ id: 'p-speed', type: 'GetVariable', x: 0, y: 360, params: { variable: 'speed' } },
|
||||
{ id: 'p-mulx', type: 'Multiply', x: 220, y: 300, params: {} },
|
||||
{ id: 'p-vy', type: 'GetVariable', x: 0, y: 440, params: { variable: 'vy' } },
|
||||
{ id: 'p-grav', type: 'GetVariable', x: 0, y: 520, params: { variable: 'gravity' } },
|
||||
{ id: 'p-mulg', type: 'Multiply', x: 220, y: 500, params: {} },
|
||||
{ id: 'p-addvy', type: 'Add', x: 420, y: 460, params: {} },
|
||||
{ id: 'p-setvy', type: 'SetVariable', x: 620, y: 0, params: { variable: 'vy' } },
|
||||
{ id: 'p-vy2', type: 'GetVariable', x: 620, y: 340, params: { variable: 'vy' } },
|
||||
{ id: 'p-move', type: 'Move', x: 840, y: 0, params: { collide: true } },
|
||||
{ id: 'p-br', type: 'Branch', x: 1080, y: 0, params: {} },
|
||||
{ id: 'p-seq', type: 'Sequence', x: 1260, y: 0, params: {} },
|
||||
{ id: 'p-vy3', type: 'GetVariable', x: 1080, y: 260, params: { variable: 'vy' } },
|
||||
{ id: 'p-cmp', type: 'Compare', x: 1260, y: 260, params: { op: '>=', b: 0 } },
|
||||
{ id: 'p-ground', type: 'SetVariable', x: 1460, y: 0, params: { variable: 'onGround' } },
|
||||
{ id: 'p-zero', type: 'SetVariable', x: 1460, y: 140, params: { variable: 'vy', value: 0 } },
|
||||
{ id: 'p-air', type: 'SetVariable', x: 1260, y: 400, params: { variable: 'onGround', value: false } },
|
||||
node('p-upd', 'OnUpdate', 0, 0),
|
||||
node('p-owner', 'Owner', 0, 140),
|
||||
node('p-axis', 'AxisInput', 0, 220, { left: 'ArrowLeft', right: 'ArrowRight', up: '', down: '' }),
|
||||
node('p-speed', 'GetVariable', 0, 360, { variable: 'speed' }),
|
||||
node('p-mulx', 'Multiply', 220, 300),
|
||||
node('p-vy', 'GetVariable', 0, 440, { variable: 'vy' }),
|
||||
node('p-grav', 'GetVariable', 0, 520, { variable: 'gravity' }),
|
||||
node('p-mulg', 'Multiply', 220, 500),
|
||||
node('p-addvy', 'Add', 420, 460),
|
||||
node('p-setvy', 'SetVariable', 620, 0, { variable: 'vy' }),
|
||||
node('p-vy2', 'GetVariable', 620, 340, { variable: 'vy' }),
|
||||
node('p-move', 'Move', 840, 0, { collide: true }),
|
||||
node('p-br', 'Branch', 1080, 0),
|
||||
node('p-seq', 'Sequence', 1260, 0),
|
||||
node('p-vy3', 'GetVariable', 1080, 260, { variable: 'vy' }),
|
||||
node('p-cmp', 'Compare', 1260, 260, { op: '>=', b: 0 }),
|
||||
node('p-ground', 'SetVariable', 1460, 0, { variable: 'onGround' }),
|
||||
node('p-zero', 'SetVariable', 1460, 140, { variable: 'vy', value: 0 }),
|
||||
node('p-air', 'SetVariable', 1260, 400, { variable: 'onGround', value: false }),
|
||||
|
||||
{ id: 'p-key', type: 'OnKeyDown', x: 0, y: 700, params: { key: ' ' } },
|
||||
{ id: 'p-og', type: 'GetVariable', x: 0, y: 820, params: { variable: 'onGround' } },
|
||||
{ id: 'p-brj', type: 'Branch', x: 240, y: 700, params: {} },
|
||||
{ id: 'p-jump', type: 'GetVariable', x: 240, y: 880, params: { variable: 'jumpSpeed' } },
|
||||
{ id: 'p-neg', type: 'Multiply', x: 440, y: 880, params: { b: -1 } },
|
||||
{ id: 'p-setjump', type: 'SetVariable', x: 660, y: 700, params: { variable: 'vy' } },
|
||||
node('p-key', 'OnKeyDown', 0, 700, { key: ' ' }),
|
||||
node('p-og', 'GetVariable', 0, 820, { variable: 'onGround' }),
|
||||
node('p-brj', 'Branch', 240, 700),
|
||||
node('p-jump', 'GetVariable', 240, 880, { variable: 'jumpSpeed' }),
|
||||
node('p-neg', 'Multiply', 440, 880, { b: -1 }),
|
||||
node('p-setjump', 'SetVariable', 660, 700, { variable: 'vy' }),
|
||||
|
||||
{ id: 'p-col', type: 'OnCollisionEnter', x: 0, y: 1060, params: {} },
|
||||
{ id: 'p-iscoin', type: 'IsAsset', x: 260, y: 1180, params: { asset: 'coin-sprite' } },
|
||||
{ id: 'p-brc', type: 'Branch', x: 480, y: 1060, params: {} },
|
||||
{ id: 'p-seqc', type: 'Sequence', x: 680, y: 1060, params: {} },
|
||||
{ id: 'p-game', type: 'ObjectByName', x: 680, y: 1280, params: { name: 'Game' } },
|
||||
{ id: 'p-coins', type: 'GetProperty', x: 880, y: 1280, params: { property: 'coins' } },
|
||||
{ id: 'p-add1', type: 'Add', x: 1080, y: 1280, params: { b: 1 } },
|
||||
{ id: 'p-setcoins', type: 'SetProperty', x: 1280, y: 1060, params: { property: 'coins' } },
|
||||
// The collider reports its body, which is the coin sprite; the whole coin is the container
|
||||
// above it, so that is what gets destroyed.
|
||||
{ id: 'p-coinroot', type: 'Parent', x: 1080, y: 1420, params: {} },
|
||||
{ id: 'p-destroy', type: 'Destroy', x: 1280, y: 1200, params: {} },
|
||||
node('p-col', 'OnCollisionEnter', 0, 1060),
|
||||
node('p-iscoin', 'IsAsset', 260, 1180, { asset: 'coin-sprite' }),
|
||||
node('p-brc', 'Branch', 480, 1060),
|
||||
node('p-seqc', 'Sequence', 680, 1060),
|
||||
node('p-game', 'ObjectByName', 680, 1280, { name: 'Game' }),
|
||||
node('p-coins', 'GetProperty', 880, 1280, { property: 'coins' }),
|
||||
node('p-add1', 'Add', 1080, 1280, { b: 1 }),
|
||||
node('p-setcoins', 'SetProperty', 1280, 1060, { property: 'coins' }),
|
||||
node('p-coinroot', 'Parent', 1080, 1420),
|
||||
node('p-destroy', 'Destroy', 1280, 1200),
|
||||
],
|
||||
edges: [
|
||||
edge('pe1', 'p-upd', 'exec', 'p-setvy', 'exec'),
|
||||
@@ -236,55 +198,55 @@ const playerGraph = {
|
||||
};
|
||||
|
||||
/**
|
||||
* "Game": owns the run. On start it shows the title and hides everything else; while
|
||||
* `running` it counts the clock down, writes both HUD labels and decides win (coins reached)
|
||||
* or loss (time gone) by switching the screens.
|
||||
* "Game": owns the run. On start it shows the title and hides everything else; while `running`
|
||||
* it counts the clock down, writes both HUD labels and decides win (coins reached) or loss (time
|
||||
* gone) by switching the screens.
|
||||
*/
|
||||
const gameGraph = {
|
||||
nodes: [
|
||||
{ id: 'g-start', type: 'OnStart', x: 0, y: 0, params: {} },
|
||||
{ id: 'g-title', type: 'ObjectByName', x: 0, y: 140, params: { name: 'Title' } },
|
||||
{ id: 'g-level', type: 'ObjectByName', x: 0, y: 220, params: { name: 'Level' } },
|
||||
{ id: 'g-win', type: 'ObjectByName', x: 0, y: 300, params: { name: 'Win' } },
|
||||
{ id: 'g-lose', type: 'ObjectByName', x: 0, y: 380, params: { name: 'Lose' } },
|
||||
{ id: 'g-on1', type: 'SetEnabled', x: 260, y: 0, params: { enabled: true } },
|
||||
{ id: 'g-off1', type: 'SetEnabled', x: 480, y: 0, params: { enabled: false } },
|
||||
{ id: 'g-off2', type: 'SetEnabled', x: 700, y: 0, params: { enabled: false } },
|
||||
{ id: 'g-off3', type: 'SetEnabled', x: 920, y: 0, params: { enabled: false } },
|
||||
node('g-start', 'OnStart', 0, 0),
|
||||
node('g-title', 'ObjectByName', 0, 140, { name: 'Title' }),
|
||||
node('g-level', 'ObjectByName', 0, 220, { name: 'Level' }),
|
||||
node('g-win', 'ObjectByName', 0, 300, { name: 'Win' }),
|
||||
node('g-lose', 'ObjectByName', 0, 380, { name: 'Lose' }),
|
||||
node('g-on1', 'SetEnabled', 260, 0, { enabled: true }),
|
||||
node('g-off1', 'SetEnabled', 480, 0, { enabled: false }),
|
||||
node('g-off2', 'SetEnabled', 700, 0, { enabled: false }),
|
||||
node('g-off3', 'SetEnabled', 920, 0, { enabled: false }),
|
||||
|
||||
{ id: 'g-upd', type: 'OnUpdate', x: 0, y: 520, params: {} },
|
||||
{ id: 'g-running', type: 'GetVariable', x: 0, y: 640, params: { variable: 'running' } },
|
||||
{ id: 'g-brrun', type: 'Branch', x: 220, y: 520, params: {} },
|
||||
{ id: 'g-time', type: 'GetVariable', x: 220, y: 700, params: { variable: 'timeLeft' } },
|
||||
{ id: 'g-sub', type: 'Subtract', x: 420, y: 700, params: {} },
|
||||
{ id: 'g-max', type: 'Max', x: 600, y: 700, params: { b: 0 } },
|
||||
{ id: 'g-settime', type: 'SetVariable', x: 440, y: 520, params: { variable: 'timeLeft' } },
|
||||
node('g-upd', 'OnUpdate', 0, 520),
|
||||
node('g-running', 'GetVariable', 0, 640, { variable: 'running' }),
|
||||
node('g-brrun', 'Branch', 220, 520),
|
||||
node('g-time', 'GetVariable', 220, 700, { variable: 'timeLeft' }),
|
||||
node('g-sub', 'Subtract', 420, 700),
|
||||
node('g-max', 'Max', 600, 700, { b: 0 }),
|
||||
node('g-settime', 'SetVariable', 440, 520, { variable: 'timeLeft' }),
|
||||
|
||||
{ id: 'g-timelabel', type: 'ObjectByName', x: 440, y: 860, params: { name: 'TimeLabel' } },
|
||||
{ id: 'g-time2', type: 'GetVariable', x: 440, y: 940, params: { variable: 'timeLeft' } },
|
||||
{ id: 'g-ceil', type: 'Ceil', x: 640, y: 940, params: {} },
|
||||
{ id: 'g-join', type: 'Join', x: 840, y: 940, params: { a: 'Time ' } },
|
||||
{ id: 'g-settext', type: 'SetText', x: 700, y: 520, params: {} },
|
||||
node('g-timelabel', 'ObjectByName', 440, 860, { name: 'TimeLabel' }),
|
||||
node('g-time2', 'GetVariable', 440, 940, { variable: 'timeLeft' }),
|
||||
node('g-ceil', 'Ceil', 640, 940),
|
||||
node('g-join', 'Join', 840, 940, { a: 'Time ' }),
|
||||
node('g-settext', 'SetText', 700, 520),
|
||||
|
||||
{ id: 'g-coinlabel', type: 'ObjectByName', x: 700, y: 1080, params: { name: 'CoinLabel' } },
|
||||
{ id: 'g-coins', type: 'GetVariable', x: 700, y: 1160, params: { variable: 'coins' } },
|
||||
{ id: 'g-totext2', type: 'ToText', x: 900, y: 1160, params: { decimals: 0 } },
|
||||
{ id: 'g-join2', type: 'Join', x: 1080, y: 1160, params: { a: 'Coins ' } },
|
||||
{ id: 'g-join3', type: 'Join', x: 1260, y: 1160, params: { b: '/10' } },
|
||||
{ id: 'g-settext2', type: 'SetText', x: 960, y: 520, params: {} },
|
||||
node('g-coinlabel', 'ObjectByName', 700, 1080, { name: 'CoinLabel' }),
|
||||
node('g-coins', 'GetVariable', 700, 1160, { variable: 'coins' }),
|
||||
node('g-totext2', 'ToText', 900, 1160, { decimals: 0 }),
|
||||
node('g-join2', 'Join', 1080, 1160, { a: 'Coins ' }),
|
||||
node('g-join3', 'Join', 1260, 1160, { b: '/10' }),
|
||||
node('g-settext2', 'SetText', 960, 520),
|
||||
|
||||
{ id: 'g-needed', type: 'GetVariable', x: 960, y: 700, params: { variable: 'needed' } },
|
||||
{ id: 'g-cmpwin', type: 'Compare', x: 1160, y: 700, params: { op: '>=' } },
|
||||
{ id: 'g-brwin', type: 'Branch', x: 1220, y: 520, params: {} },
|
||||
{ id: 'g-stop1', type: 'SetVariable', x: 1420, y: 440, params: { variable: 'running', value: false } },
|
||||
{ id: 'g-leveloff1', type: 'SetEnabled', x: 1620, y: 440, params: { enabled: false } },
|
||||
{ id: 'g-winon', type: 'SetEnabled', x: 1820, y: 440, params: { enabled: true } },
|
||||
node('g-needed', 'GetVariable', 960, 700, { variable: 'needed' }),
|
||||
node('g-cmpwin', 'Compare', 1160, 700, { op: '>=' }),
|
||||
node('g-brwin', 'Branch', 1220, 520),
|
||||
node('g-stop1', 'SetVariable', 1420, 440, { variable: 'running', value: false }),
|
||||
node('g-leveloff1', 'SetEnabled', 1620, 440, { enabled: false }),
|
||||
node('g-winon', 'SetEnabled', 1820, 440, { enabled: true }),
|
||||
|
||||
{ id: 'g-cmplose', type: 'Compare', x: 1160, y: 860, params: { op: '<=', b: 0 } },
|
||||
{ id: 'g-brlose', type: 'Branch', x: 1420, y: 640, params: {} },
|
||||
{ id: 'g-stop2', type: 'SetVariable', x: 1620, y: 640, params: { variable: 'running', value: false } },
|
||||
{ id: 'g-leveloff2', type: 'SetEnabled', x: 1820, y: 640, params: { enabled: false } },
|
||||
{ id: 'g-loseon', type: 'SetEnabled', x: 2020, y: 640, params: { enabled: true } },
|
||||
node('g-cmplose', 'Compare', 1160, 860, { op: '<=', b: 0 }),
|
||||
node('g-brlose', 'Branch', 1420, 640),
|
||||
node('g-stop2', 'SetVariable', 1620, 640, { variable: 'running', value: false }),
|
||||
node('g-leveloff2', 'SetEnabled', 1820, 640, { enabled: false }),
|
||||
node('g-loseon', 'SetEnabled', 2020, 640, { enabled: true }),
|
||||
],
|
||||
edges: [
|
||||
edge('ge1', 'g-start', 'exec', 'g-on1', 'exec'),
|
||||
@@ -341,15 +303,15 @@ const gameGraph = {
|
||||
/** "Start button": a click resets the counters on the Game behavior and swaps title for level. */
|
||||
const startButtonGraph = {
|
||||
nodes: [
|
||||
{ id: 's-click', type: 'OnClick', x: 0, y: 0, params: {} },
|
||||
{ id: 's-game', type: 'ObjectByName', x: 0, y: 160, params: { name: 'Game' } },
|
||||
{ id: 's-title', type: 'ObjectByName', x: 0, y: 240, params: { name: 'Title' } },
|
||||
{ id: 's-level', type: 'ObjectByName', x: 0, y: 320, params: { name: 'Level' } },
|
||||
{ id: 's-coins', type: 'SetProperty', x: 240, y: 0, params: { property: 'coins', value: 0 } },
|
||||
{ id: 's-time', type: 'SetProperty', x: 460, y: 0, params: { property: 'timeLeft', value: 30 } },
|
||||
{ id: 's-run', type: 'SetProperty', x: 680, y: 0, params: { property: 'running', value: true } },
|
||||
{ id: 's-hide', type: 'SetEnabled', x: 900, y: 0, params: { enabled: false } },
|
||||
{ id: 's-show', type: 'SetEnabled', x: 1120, y: 0, params: { enabled: true } },
|
||||
node('s-click', 'OnClick', 0, 0),
|
||||
node('s-game', 'ObjectByName', 0, 160, { name: 'Game' }),
|
||||
node('s-title', 'ObjectByName', 0, 240, { name: 'Title' }),
|
||||
node('s-level', 'ObjectByName', 0, 320, { name: 'Level' }),
|
||||
node('s-coins', 'SetProperty', 240, 0, { property: 'coins', value: 0 }),
|
||||
node('s-time', 'SetProperty', 460, 0, { property: 'timeLeft', value: 30 }),
|
||||
node('s-run', 'SetProperty', 680, 0, { property: 'running', value: true }),
|
||||
node('s-hide', 'SetEnabled', 900, 0, { enabled: false }),
|
||||
node('s-show', 'SetEnabled', 1120, 0, { enabled: true }),
|
||||
],
|
||||
edges: [
|
||||
edge('se1', 's-click', 'exec', 's-coins', 'exec'),
|
||||
@@ -367,15 +329,10 @@ const startButtonGraph = {
|
||||
|
||||
/** "Restart button": one click, one node – the scene starts over from its saved state. */
|
||||
const restartButtonGraph = {
|
||||
nodes: [
|
||||
{ id: 'r-click', type: 'OnClick', x: 0, y: 0, params: {} },
|
||||
{ id: 'r-restart', type: 'RestartGame', x: 260, y: 0, params: {} },
|
||||
],
|
||||
nodes: [node('r-click', 'OnClick', 0, 0), node('r-restart', 'RestartGame', 260, 0)],
|
||||
edges: [edge('re1', 'r-click', 'exec', 'r-restart', 'exec')],
|
||||
};
|
||||
|
||||
const variable = (key, kind, value) => ({ key, label: key, kind, default: value });
|
||||
|
||||
// --- project ---------------------------------------------------------------------------------
|
||||
|
||||
const project = {
|
||||
@@ -390,15 +347,14 @@ const project = {
|
||||
name: 'Level 1',
|
||||
props: { backgroundColor: SKY },
|
||||
objects: [
|
||||
...instantiate('L', 'level', 'Level', { x: 0, y: 0, z: 0 }),
|
||||
// All three screens sit on the camera frame and start switched off, so the level stays
|
||||
// visible in the editor; the Game behavior decides on `On start` which one is up.
|
||||
// The title screen carries an editor offset so it does not cover the level in the scene view.
|
||||
...instantiate('T', 'title', 'Title', { x: 0, y: 0, z: 10 }, { enabled: true }, { editorOffset: { x: -750, y: -500 } }),
|
||||
...instantiate('W', 'win', 'Win', { x: 0, y: 0, z: 10 }, { enabled: true }, { editorOffset: { x: 0, y: -500 } }),
|
||||
...instantiate('B', 'lose', 'Lose', { x: 0, y: 0, z: 10 }, { enabled: true }, { editorOffset: { x: 750, y: -500 } }),
|
||||
{ id: 'ocam', assetId: 'cam', name: 'Main Camera', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: '', templateId: '' },
|
||||
{ id: 'ogame', assetId: 'game', name: 'Game', position: { x: -200, y: -220, z: 0 }, overrides: {}, parentId: '', templateId: '' },
|
||||
...instantiate('L', 'level', { name: 'Level' }),
|
||||
// All three screens sit on the camera frame; the Game behavior decides on `On start` which
|
||||
// one is up. Editor offsets pull them apart in the scene view so they do not cover the level.
|
||||
...instantiate('T', 'title', { name: 'Title', z: 10, overrides: { enabled: true }, editorOffset: { x: -750, y: -500 } }),
|
||||
...instantiate('W', 'win', { name: 'Win', z: 10, overrides: { enabled: true }, editorOffset: { x: 0, y: -500 } }),
|
||||
...instantiate('B', 'lose', { name: 'Lose', z: 10, overrides: { enabled: true }, editorOffset: { x: 750, y: -500 } }),
|
||||
sceneObject('ocam', 'cam', { name: 'Main Camera' }),
|
||||
sceneObject('ogame', 'game', { name: 'Game', x: -200, y: -220 }),
|
||||
],
|
||||
},
|
||||
{ id: 'cam', type: 'camera', name: 'Main Camera', props: { width: 640, height: 360, zoom: 1 } },
|
||||
@@ -439,9 +395,9 @@ const project = {
|
||||
{ id: 'restart-button', type: 'behavior', name: 'Restart Button', props: {}, variables: [], graph: restartButtonGraph },
|
||||
{ id: 'coin', type: 'container', name: 'Coin', props: {}, objects: coinTemplate },
|
||||
{ id: 'level', type: 'container', name: 'Level', props: {}, objects: levelTemplate },
|
||||
{ id: 'title', type: 'container', name: 'Title screen', props: {}, objects: titleTemplate },
|
||||
{ id: 'win', type: 'container', name: 'Win screen', props: {}, objects: winTemplate },
|
||||
{ id: 'lose', type: 'container', name: 'Lose screen', props: {}, objects: loseTemplate },
|
||||
{ id: 'title', type: 'container', name: 'Title screen', props: {}, objects: templates.title },
|
||||
{ id: 'win', type: 'container', name: 'Win screen', props: {}, objects: templates.win },
|
||||
{ id: 'lose', type: 'container', name: 'Lose screen', props: {}, objects: templates.lose },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user