State machines: the start state and how it is set, the two event nodes with their order, the cap and who hears them per scope, the two reader nodes, and the highlight in the tab while playing. The old "events and a highlight are later steps" sentence is gone, as is "a global machine starts each run in no state at all". Behaviors: the four new nodes in the node set. Home: the state machines row. Development: 885 checks, twice.
1 parent
5f71a420bc
commit
84df59c535
4 files changed
+68
-12
No files matched your search
+2
-2
@@ -97,7 +97,7 @@ shown on the node and editable in the inspector.
|
|||||||
deliberately not used: its delay and rate are an operating-system setting, not a game's. Both
|
deliberately not used: its delay and rate are an operating-system setting, not a game's. Both
|
||||||
rows stay off the node body while the delay is 0, so a plain key node keeps its single line.
|
rows stay off the node body while the delay is 0, so a plain key node keeps its single line.
|
||||||
- **Node set** (`src/core/graph/nodes/`, one file per node): events `On start`, `On enable`, `On update`, `On disable`, `On destroy` (see *Lifecycle*), `On key down/up/held`,
|
- **Node set** (`src/core/graph/nodes/`, one file per node): events `On start`, `On enable`, `On update`, `On disable`, `On destroy` (see *Lifecycle*), `On key down/up/held`,
|
||||||
`On timer`, `On collision enter/stay/exit`, `On click` (the mouse pressed on the owner — anywhere when it has none), `On animation finished`, `Start` · flow `Branch`, `Sequence`, `Repeat`, `Run behavior`, `End`, and the three that read a state machine, `State branch`, `Set state`, `State transition` (see *State machines*) · data `Self`, `Owner`, `Parent`, `Object by name`, `Name of`,
|
`On timer`, `On collision enter/stay/exit`, `On click` (the mouse pressed on the owner — anywhere when it has none), `On animation finished`, `On state entered`, `On state exited` (one output per state, see *State machines*), `Start` · flow `Branch`, `Sequence`, `Repeat`, `Run behavior`, `End`, and the three that move or follow a state machine, `State branch`, `Set state`, `State transition` (see *State machines*) · 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),
|
`Is asset` (is this object an instance of asset X — how you tell what you collided with),
|
||||||
`Variables`, `Properties` (every property row of the target's type, the transform rows `scale`, `rotation`, `mirrorX`, `mirrorY` included — how a graph turns a sprite), `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`, `Add/Subtract/Multiply/Divide`,
|
`Variables`, `Properties` (every property row of the target's type, the transform rows `scale`, `rotation`, `mirrorX`, `mirrorY` included — how a graph turns a sprite), `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`, `Add/Subtract/Multiply/Divide`,
|
||||||
`Min/Max/Abs/Sqrt/Clamp`, `Floor/Ceil/Round`, `Modulo`, `Random`, `Time`, `To text`, `To number`, `Join text`,
|
`Min/Max/Abs/Sqrt/Clamp`, `Floor/Ceil/Round`, `Modulo`, `Random`, `Time`, `To text`, `To number`, `Join text`,
|
||||||
@@ -107,7 +107,7 @@ shown on the node and editable in the inspector.
|
|||||||
`Join lists`, `Index of`, structs `Pack`/`Expand`,
|
`Join lists`, `Index of`, structs `Pack`/`Expand`,
|
||||||
`Length`, `Distance`, `Normalize`, `Axis input` (four keys → a
|
`Length`, `Distance`, `Normalize`, `Axis input` (four keys → a
|
||||||
normalized direction, so diagonals are not faster), `Compare`, `And/Or/Not`, `Key is down`,
|
normalized direction, so diagonals are not faster), `Compare`, `And/Or/Not`, `Key is down`,
|
||||||
`Mouse position`, `Mouse button`, `Is hovered`, `Is touching` ·
|
`Mouse position`, `Mouse button`, `Is hovered`, `Is touching`, `Current state` (name and id), `Is in state` (one boolean per state) ·
|
||||||
actions `Move` (dx, dy, scale, dt — connect an event's dt for per-second movement; in the object's own axes, which a turned parent turns too), `Set position`, `Spawn`
|
actions `Move` (dx, dy, scale, dt — connect an event's dt for per-second movement; in the object's own axes, which a turned parent turns too), `Set position`, `Spawn`
|
||||||
(asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Emit` (starts a particle source over, see *Particles*), `Log`, `Stop game`, `Restart game`. A node type is one file exporting a `NodeDef`
|
(asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Emit` (starts a particle source over, see *Particles*), `Log`, `Stop game`, `Restart game`. A node type is one file exporting a `NodeDef`
|
||||||
(ports, settings, `run`), listed in `nodes/index.ts`; palette, ports, validation and
|
(ports, settings, `run`), listed in `nodes/index.ts`; palette, ports, validation and
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
|
|||||||
# code is lost (tail's is reported), so a red run reads as green
|
# code is lost (tail's is reported), so a red run reads as green
|
||||||
npm run build # writes dist/ and regenerates index.html
|
npm run build # writes dist/ and regenerates index.html
|
||||||
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
|
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
|
||||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 868 editor checks, screenshots in /tmp/gm-e2e-*.png
|
node tools/seed-e2e.mjs && node tools/e2e.mjs # 885 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||||
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
|
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
|
||||||
./e2e.sh start [GM_STEPS] # the same run detached, see below
|
./e2e.sh start [GM_STEPS] # the same run detached, see below
|
||||||
./e2e.sh status # live progress, or the last run's result
|
./e2e.sh status # live progress, or the last run's result
|
||||||
@@ -183,4 +183,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
|
|||||||
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
|
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
|
||||||
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
|
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
|
||||||
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
|
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
|
||||||
WebGL 2.0, and the same 868 checks as here.
|
WebGL 2.0, and the same 885 checks as here.
|
||||||
+1
-1
@@ -16,7 +16,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
|
|||||||
| [Textures](Textures) | Getting images in, where they go, tinting, the filter, frames, upload limits |
|
| [Textures](Textures) | Getting images in, where they go, tinting, the filter, frames, upload limits |
|
||||||
| [Fonts](Fonts) | Uploading a typeface, what a text wears, what the editor falls back to |
|
| [Fonts](Fonts) | Uploading a typeface, what a text wears, what the editor falls back to |
|
||||||
| [Animations](Animations) | Animation assets, the Animation tab, how they run |
|
| [Animations](Animations) | Animation assets, the Animation tab, how they run |
|
||||||
| [State machines](State-machines) | The state machine asset, its scope, named transitions, `State branch`, `Set state` and `State transition` |
|
| [State machines](State-machines) | The state machine asset, its scope, named transitions, the start state, `State branch`, `Set state`, `State transition`, the two state events and the live highlight |
|
||||||
| [Shaders](Shaders) | The shader asset, the Shader tab, the node set, the Adjust colors recipe, wearing a shader |
|
| [Shaders](Shaders) | The shader asset, the Shader tab, the node set, the Adjust colors recipe, wearing a shader |
|
||||||
| [Particles](Particles) | The particles asset, a particle as a formula of its age, the source's rows, the Particles tab |
|
| [Particles](Particles) | The particles asset, a particle as a formula of its age, the source's rows, the Particles tab |
|
||||||
| [Containers](Containers) | Editing a template, instances and their edits, screens without scene switching |
|
| [Containers](Containers) | Editing a template, instances and their edits, screens without scene switching |
|
||||||
|
|||||||
+63
-7
@@ -1,6 +1,6 @@
|
|||||||
A **state machine asset** is a named set of states and the moves allowed between them: its graph
|
A **state machine asset** is a named set of states and the moves allowed between them: its graph
|
||||||
holds one `State` node per state (a title and a *Name* setting) and one edge per transition. Events
|
holds one `State` node per state (a title and a *Name* setting) and one edge per transition. The
|
||||||
like "on state entered" and a highlight of the current state while playing are later steps. The states are edited in the **State machine** tab, which is the graph
|
states are edited in the **State machine** tab, which is the graph
|
||||||
canvas the Behavior tab uses — so placing, dragging, the header's **Snap** checkbox, renaming in
|
canvas the Behavior tab uses — so placing, dragging, the header's **Snap** checkbox, renaming in
|
||||||
the inspector, connecting, `Delete` and undo are the graph's.
|
the inspector, connecting, `Delete` and undo are the graph's.
|
||||||
Double-clicking the asset (or creating it) opens the tab, like every asset with a tab; a click
|
Double-clicking the asset (or creating it) opens the tab, like every asset with a tab; a click
|
||||||
@@ -13,7 +13,7 @@ A machine's **Scope** is a dropdown in its own inspector, since the machine is t
|
|||||||
current state hangs off its id. **Local** (the default) gives every behavior instance a current
|
current state hangs off its id. **Local** (the default) gives every behavior instance a current
|
||||||
state of its own — two doors open independently. **Global** keeps one current state for the whole
|
state of its own — two doors open independently. **Global** keeps one current state for the whole
|
||||||
run, so every behavior using the machine sees the same one, and a second behavior can branch on a
|
run, so every behavior using the machine sees the same one, and a second behavior can branch on a
|
||||||
state it never set itself. A global machine starts each run in no state at all, like a local one.
|
state it never set itself. Scope also decides who hears a move: see *Start state and events* below.
|
||||||
|
|
||||||
## Transitions
|
## Transitions
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ is wired to. What the arrows are for at runtime is `State transition`.
|
|||||||
unconnected (or takes a `State machine` node, the literal every kind has):
|
unconnected (or takes a `State machine` node, the literal every kind has):
|
||||||
|
|
||||||
- **`State branch`** grows **one exec output per state**, named after the state, and continues
|
- **`State branch`** grows **one exec output per state**, named after the state, and continues
|
||||||
along the one the machine is in. Before `Set state` has run the machine is in **no state**, and
|
along the one the machine is in. Without a start state, and before `Set state` has run, the
|
||||||
the flow ends there — there is no start state. A state the machine no longer has (deleted, or of
|
machine is in **no state** and the flow ends there. A state the machine no longer has (deleted,
|
||||||
another machine) ends it too — the run ignores a ghost port, whatever the canvas draws.
|
or of another machine) ends it too — the run ignores a ghost port, whatever the canvas draws.
|
||||||
- **`Set state`** is the mirror image: **one exec input per state**, one exec output. The input the
|
- **`Set state`** is the mirror image: **one exec input per state**, one exec output. The input the
|
||||||
flow *arrives through* names the new state — so `OnStart → Set state (Open)` and `On key down →
|
flow *arrives through* names the new state — so `OnStart → Set state (Open)` and `On key down →
|
||||||
Set state (Closed)` are two edges into one node. The interpreter hands every node the exec input
|
Set state (Closed)` are two edges into one node. The interpreter hands every node the exec input
|
||||||
@@ -72,12 +72,68 @@ ports belong to is read off the graph, not off a running value — the inline pi
|
|||||||
ports, exactly as `Run behavior` knows its behavior only as a setting. Picking another machine
|
ports, exactly as `Run behavior` knows its behavior only as a setting. Picking another machine
|
||||||
orphans the edges of the old states, the way any setting that decides a node's ports does.
|
orphans the edges of the old states, the way any setting that decides a node's ports does.
|
||||||
|
|
||||||
|
Two more read it as **data** rather than as a flow, both pure readers (no exec ports, answered
|
||||||
|
whenever they are asked):
|
||||||
|
|
||||||
|
- **`Current state`** hands out two texts: `name` for showing (`Join`, a `Text` object) and `id`
|
||||||
|
for comparing, since a rename moves the name but never the id. Both are empty while the machine
|
||||||
|
is in no state, and a state the machine does not know keeps its id but has no name.
|
||||||
|
- **`Is in state`** grows **one boolean output per state** — the data twin of `State branch`, for
|
||||||
|
the places a flow cannot go (`Branch`, `And`, a property). Every output is answered, so a state
|
||||||
|
nobody is in reads `false` rather than nothing.
|
||||||
|
|
||||||
|
## Start state and events
|
||||||
|
|
||||||
|
A machine may name the state every instance **begins** in. Right-click a state and choose
|
||||||
|
**Set as start state**; the same entry then reads *Remove start state*, which is the only switch
|
||||||
|
there is. The start wears a **ring** on the right of its title in the tab. It is kept on the asset
|
||||||
|
(`start`, a state node's id) and not on the node, because only one state can be the start and a
|
||||||
|
single field is what makes that structural instead of a rule someone has to keep. A start pointing
|
||||||
|
at a **deleted** state reads as no start — the dead id stays in the document and means nothing.
|
||||||
|
|
||||||
|
Without a start the old behaviour stands: the machine is in no state until `Set state` runs.
|
||||||
|
|
||||||
|
Two event nodes hear the moves, each with a `machine` input and **one exec output per state**,
|
||||||
|
like `State branch`:
|
||||||
|
|
||||||
|
- **`On state entered`** runs when the machine enters a state, along that state's output.
|
||||||
|
- **`On state exited`** runs when it leaves one. Coming out of *no* state there is nothing to
|
||||||
|
leave, so nothing runs.
|
||||||
|
|
||||||
|
The rules, all of them worth knowing before wiring a graph on them:
|
||||||
|
|
||||||
|
- A `Set state` into the state the machine is **already in** is no move: nothing is written and
|
||||||
|
nothing fires. That is what keeps a `Set state` run every frame from firing `entered` every frame.
|
||||||
|
- The **start state counts as entered**. A local machine is entered per instance, right after that
|
||||||
|
instance's `On start`; a `global` one is entered **once for the whole run**, before the first
|
||||||
|
instance exists, and every instance created with the scene hears that one entry. An instance
|
||||||
|
spawned later joins a global machine where it already is, without an `entered` of its own.
|
||||||
|
- Order is **`exited` of the old state, then `entered` of the new**, and both fire **after** the
|
||||||
|
exec flow that caused the move has finished — never in the middle of it. Moves the events
|
||||||
|
themselves cause are delivered in the same way, round after round until none are left; a graph
|
||||||
|
whose events keep moving the machine stops the run with an endless-loop error, like the step limit.
|
||||||
|
- Who hears it follows the **scope**: a local machine is heard by the instance that moved it alone,
|
||||||
|
a global one by **every** instance with a matching event node — which is the point of sharing a
|
||||||
|
state. A move a nested `Run behavior` makes to a *local* machine dies with the call and is not
|
||||||
|
reported; a move it makes to a global one is a move like any other.
|
||||||
|
|
||||||
|
## The current state while playing
|
||||||
|
|
||||||
|
While a run is on, the **State machine tab** marks the states it is in: weakly for every state some
|
||||||
|
instance holds, and **strongly** for the state held by the instance of the **selected scene
|
||||||
|
object** (the behavior itself or the object it hangs under). Without a selection nothing is strong,
|
||||||
|
which keeps "many instances, many states" readable instead of picking one of them at random. The
|
||||||
|
marks are the play colour, inside the border so a selected state still shows both, and they are
|
||||||
|
gone the moment the run stops. Play repaints the tab the way it repaints the Behavior tab
|
||||||
|
(`PlaySession.tick`); an editor run has no session to do it, so the editor's frame does it instead.
|
||||||
|
|
||||||
**A local machine's current state is the behavior instance's own**, kept beside its variables
|
**A local machine's current state is the behavior instance's own**, kept beside its variables
|
||||||
(`NodeContext.states`, machine id → state id): two crates running the same behavior are in
|
(`NodeContext.states`, machine id → state id): two crates running the same behavior are in
|
||||||
states of their own, and a behavior run through `Run behavior` starts in no state, as it starts
|
states of their own, and a behavior run through `Run behavior` starts in no state, as it starts
|
||||||
with fresh variables. A **global** one is kept for the run instead (`SharedState.states`, built
|
with fresh variables. A **global** one is kept for the run instead (`SharedState.states`, built
|
||||||
once per run in `core/graph/sharedState.ts`) and every instance reads and moves that one — which
|
once per run in `core/graph/sharedState.ts`) and every instance reads and moves that one — which
|
||||||
bag a node touches is decided by the machine's scope, not by the node (`stateBag`). Neither
|
bag a node touches is decided by the machine's scope, not by the node (`stateBag`). Neither
|
||||||
survives a stop: both start in no state again on the next Play, the way a Variables asset starts
|
survives a stop: both begin the next Play at the start state again, or in no state where there is
|
||||||
|
none, the way a Variables asset starts
|
||||||
at its declared values. A state machine is a **kind** like a texture — a variable, a struct field,
|
at its declared values. A state machine is a **kind** like a texture — a variable, a struct field,
|
||||||
a list item (tagged `{"$statemachine": id}`), a port — and a `statemachine` asset is never placed.
|
a list item (tagged `{"$statemachine": id}`), a port — and a `statemachine` asset is never placed.
|
||||||
Reference in new issue
Block a user