Enabled switch, text assets, mouse input and the jump-and-run example game
Engine additions that the example game needed: - `enabled` as a shared property of every placeable asset (default on, an override on the scene object). A switched-off object is not drawn, does not collide and its behavior does not run - and neither does anything parented below it. Disabled objects are now hidden in the scene view as well, so the editor shows what the game shows; they stay in the hierarchy panel, which is where they are selected and switched back on. - Asset type `text` (text, fontSize, color, align), rendered in scene and game. - Pointer input in the game view: `On click`, `Mouse position`, `Mouse button`, `Is hovered`, hit-tested against colliders, else sprite/text footprints. - New nodes: Set/Is enabled, Set text, Is asset, Name of, Equals, To text, Join text, Floor/Ceil/Round, Modulo, Random, Time, Restart game. Tooling: shared CDP harness (tools/harness.mjs), central project registry with a guard that keeps destructive scripts off the playground (tools/projects.mjs), plus smoke suites for the demo scene and the jump-and-run game. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4900d7bbf6
commit
76aa441dce
33 files changed
+1905
-301
No files matched your search
@@ -10,7 +10,9 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
|
||||
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| **Asset** | Named thing with a type (`scene`, `camera`, `sprite`, `behavior`, `collider`, `container`) and typed properties. Assets have **no position**. |
|
||||
| **Asset** | Named thing with a type (`scene`, `camera`, `sprite`, `text`, `behavior`, `collider`, `container`) and typed properties. Assets have **no position**. |
|
||||
| **Text** | An asset that draws a line of text (`text`, `fontSize`, `color`, `align`) – titles, HUD readouts, button labels. Its content is an ordinary property, so `Set text` (or `Set property`) changes it while playing. |
|
||||
| **Enabled** | Every placeable asset has the switch `enabled` (default on). A disabled object is not drawn, does not collide and its behavior does not run – **and neither does anything parented below it**. Switching one instance off is a normal override, which makes a container a *screen*: switch it on to show it, off to hide it. `Set enabled` / `Is enabled` do it from a graph. |
|
||||
| **Scene object** | An asset placed into a scene: a link to the asset + a **position** (`x`, `y`, `z`) + sparse **overrides** of the asset's properties + an optional **parent** (another object of the scene). Scenes cannot be placed. |
|
||||
| **Container** | A reusable group ("prefab"): the asset holds a **template** of objects (asset link, local position, overrides, parent links) centred on its origin. Placing a container creates a container object plus fresh copies of the template below it – ordinary objects, each remembering its template id. **Create container** turns the current selection into such an asset and replaces the selected objects by one placed container at their centre (world positions unchanged). The template is edited from its instances (see *Instance*). |
|
||||
| **Selection** | One or more scene objects. Click selects, `Ctrl`-click adds/removes, dragging on empty scene space draws a rubber band (objects whose position lies inside are selected). Dragging a selected object moves the whole selection; `Delete` removes it; the inspector offers **Create container** and **Remove from scene** for several objects. |
|
||||
@@ -55,16 +57,30 @@ shown on the node and editable in the inspector.
|
||||
viewport). The `Owner` node returns it; `Self` returns the behavior itself; `Parent` walks up
|
||||
from any object.
|
||||
- **Node set** (`src/core/graph/nodes.ts`): events `On start`, `On update`, `On key down/up/held`,
|
||||
`On timer`, `On collision enter/stay/exit` · flow `Branch`, `Sequence` · data `Self`, `Owner`, `Parent`, `Object by name`, `Get/Set
|
||||
variable`, `Get/Set property`, `Get position` (local and world), literals, `Add/Subtract/Multiply/Divide`,
|
||||
`Min/Max/Abs/Sqrt/Clamp`, `Length`, `Distance`, `Normalize`, `Axis input` (four keys → a
|
||||
normalized direction, so diagonals are not faster), `Compare`, `And/Or/Not`, `Key is down`, `Is touching` ·
|
||||
actions `Move` (dx, dy, scale, dt – connect an event's dt for per-second movement), `Set position`, `Spawn`
|
||||
(asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Log`, `Stop game`. A node type is one `registerNode()` call; palette, ports, validation and
|
||||
`On timer`, `On collision enter/stay/exit`, `On click` (the mouse pressed on the owner) · flow `Branch`, `Sequence` · data `Self`, `Owner`, `Parent`, `Object by name`, `Name of`,
|
||||
`Is asset` (is this object an instance of asset X – how you tell what you collided with), `Get/Set
|
||||
variable`, `Get/Set property`, `Get position` (local and world), `Is enabled`, literals, `Add/Subtract/Multiply/Divide`,
|
||||
`Min/Max/Abs/Sqrt/Clamp`, `Floor/Ceil/Round`, `Modulo`, `Random`, `Time`, `To text`, `Join text`, `Equals`,
|
||||
`Length`, `Distance`, `Normalize`, `Axis input` (four keys → a
|
||||
normalized direction, so diagonals are not faster), `Compare`, `And/Or/Not`, `Key is down`,
|
||||
`Mouse position`, `Mouse button`, `Is hovered`, `Is touching` ·
|
||||
actions `Move` (dx, dy, scale, dt – connect an event's dt for per-second movement), `Set position`, `Set enabled`, `Set text`, `Spawn`
|
||||
(asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Log`, `Stop game`, `Restart game`. A node type is one `registerNode()` call; palette, ports, validation and
|
||||
inspector fields are generated from it. A port may declare a `default` for its inline value
|
||||
(e.g. `Move.scale = 1`), shown on the node and in the inspector.
|
||||
- **Cross-object state**: a behavior's variables *are* the properties of its object, so another
|
||||
graph reads and writes them with `Object by name` → `Get property` / `Set property`. That is
|
||||
how the example game keeps one coin counter for the whole run.
|
||||
- **Mouse**: the Game tab feeds the pointer to the world in world units. `On click` fires for
|
||||
the behavior whose owner was hit – colliders decide when there are any, otherwise the sprites
|
||||
and texts of the owner and everything below it, so a button made of a box plus a label works
|
||||
as one. A switched-off screen cannot swallow clicks.
|
||||
- **Idiomatic movement** (the demo's "Mover", five nodes): `On update → Move(Owner, dx = Axis
|
||||
input.x, dy = Axis input.y, scale = Get variable speed, dt = On update.dt)`.
|
||||
- **Idiomatic platformer** (the example game's "Player"): `On update` adds `gravity · dt` to a
|
||||
`vy` variable and moves the owner by `(axis.x · speed, vy)` with *Collide with solids* on;
|
||||
`blocked y` while falling means standing, so `vy` goes back to 0 and `onGround` becomes true;
|
||||
`On key down (Space)` sets `vy = −jumpSpeed` when `onGround`.
|
||||
- **Editing**: right-click the canvas for the searchable palette, drag port → port to connect
|
||||
(types are checked; a data input holds one link and is replaced), drag nodes, right-click a
|
||||
node or port to delete/disconnect, `Delete` removes the selected node, wheel zooms, dragging
|
||||
@@ -128,6 +144,20 @@ fast object cannot jump across a thin wall and end up on the other side. Collide
|
||||
moving object and of anything parented under it travel with it and cannot
|
||||
block it. `Move` without the setting and `Set position` ignore colliders entirely.
|
||||
|
||||
## Screens without scene switching
|
||||
|
||||
A **screen** (title, win, lose, a pause overlay) is just a container whose `enabled` is
|
||||
switched on and off. It keeps working while it is off in one important way: `Object by name`
|
||||
still finds it, so a graph can switch it back on. A behavior inside a disabled screen has no
|
||||
interpreter yet – it gets one, and its `On start`, the moment the screen is switched on, which
|
||||
makes "the screen initialises itself when shown" the default. Its variables survive being
|
||||
switched off and on again. A switched-off object is hidden in the Scene tab as well, so the
|
||||
editor shows what the game shows; it stays listed in the hierarchy panel, which is where it is
|
||||
selected and switched back on.
|
||||
|
||||
`Restart game` throws the run away and starts the scene again from the saved model – what the
|
||||
"Play again" buttons of the example game do.
|
||||
|
||||
## Stack
|
||||
|
||||
- **PixiJS v8** – rendering and pointer events. No game engine; runtime systems live in `src/runtime/`.
|
||||
@@ -150,7 +180,7 @@ src/viewport/ scene renderer: Scene tab (model, editable) and Game tab (wo
|
||||
src/editor/ App composition, state, panels, inspector field factory
|
||||
src/editor/graph/ GraphEditor (canvas), NodeView, NodePalette
|
||||
api/ PHP backend data/projects/ persisted projects
|
||||
tests/ Vitest unit tests tools/ e2e.mjs (CDP smoke test), seed-demo/seed-e2e.mjs
|
||||
tests/ Vitest unit tests tools/ harness/projects + e2e, smoke-demo, seed-*
|
||||
```
|
||||
|
||||
The toolbar's right edge shows the frame rate (frames counted over a 0.5 s window, updated
|
||||
@@ -171,15 +201,30 @@ npm run dev # http://localhost:5175/game-maker/dist/ – API calls are
|
||||
npm test # unit tests (core)
|
||||
npm run typecheck
|
||||
npm run build # writes dist/ and regenerates index.html
|
||||
node tools/seed-e2e.mjs <projectId> # write the fixture scene the e2e checks assert against
|
||||
node tools/e2e.mjs <projectId> # headless end-to-end run with screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-demo.mjs <projectId> # put the demo project back (the e2e run mutates it)
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 100+ editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-demo.mjs <demoCheckId> \
|
||||
&& node tools/smoke-demo.mjs # the shipped demo scene still loads and plays
|
||||
node tools/seed-demo.mjs # rewrite the playground with the demo scene
|
||||
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs # the example game, played end to end
|
||||
```
|
||||
|
||||
The e2e run overwrites the project it is pointed at, so it seeds its own fixture first and the
|
||||
demo is restored afterwards. The demo scene (`seed-demo.mjs`) is free to evolve independently:
|
||||
a "Player" container – green sprite with a collider and the Mover behavior – two "Enemy"
|
||||
container instances, a camera at zoom 0.5 and a Spawner that drops another enemy on Space.
|
||||
Each script writes to its own project (`tools/projects.mjs`): the **demo** project is the
|
||||
playground and is only ever touched by `seed-demo.mjs`, the **fixture** project is rewritten by
|
||||
every e2e run, and the **demo-check** project is a throwaway copy for the smoke test. Pointing a
|
||||
destructive script at the demo project aborts, so a test run can never eat scene work.
|
||||
|
||||
**The example game** (`tools/seed-jumprun.mjs`, project *Jump and Run*) is a complete little
|
||||
platformer built only from assets, containers and graphs: a title screen with a clickable Start
|
||||
button, a level of boxes with ten coins, gravity and jumping, a two-minute clock in the HUD, and
|
||||
a win / lose screen with "Play again". `tools/smoke-jumprun.mjs` plays it in a headless browser
|
||||
(start, jump, collect, win, restart, lose) – it is the regression test for everything screens,
|
||||
text, mouse and platformer physics need.
|
||||
|
||||
The split lets the two sides move independently: `e2e.mjs` asserts editor behaviour against a
|
||||
stable fixture with uniquely named objects, while `smoke-demo.mjs` only checks that the shipped
|
||||
demo – a "Player" container (green sprite, collider, Mover behavior), two "Enemy" container
|
||||
instances, a camera at zoom 0.5 and a Spawner that drops another enemy on Space – still loads,
|
||||
plays, collides and spawns. `tools/harness.mjs` holds the shared CDP driver.
|
||||
|
||||
The workspace folder itself is what nginx serves, so `npm run build` is the deployment.
|
||||
|
||||
@@ -208,4 +253,6 @@ object in the selection (hierarchy rows too).
|
||||
## Not yet
|
||||
|
||||
Textures on sprites, editing a container's template directly (today changes reach it only
|
||||
via **Apply to container** from an instance), reordering the hierarchy, auth, touch-optimised layout, graph comments/groups.
|
||||
via **Apply to container** from an instance), reordering the hierarchy, several scenes in one
|
||||
run (a `Load scene` node – screens are containers today), sound, auth, touch-optimised layout,
|
||||
graph comments/groups.
|
||||
Reference in new issue
Block a user