A behavior asset holds a Blueprint-style graph edited in the Behavior tab of the centre
area (the Scene tab shows the editable scene, the Game tab the running game). Ports come in two kinds: exec (white
arrows, control flow) and data (coloured circles; number, boolean, string,
object, list, struct, any). Event nodes start a pulse; the interpreter follows exec edges and pulls
data inputs lazily from connected outputs. Unconnected data inputs use the inline value
shown on the node and editable in the inspector. Nothing typed in, an input carries the zero of
its kind (0, false, empty text, an empty list), and the node shows that value beside the name:
empty text and an empty list read (empty). An unconnected object or struct input has
nothing to type in and carries null, which the node shows as (null). Live output values while
playing use the same two words. An input and an output in the same row share its width: the
output's name stays whole, and a long inline value is cut with … before it (the full value is in
the inspector).
-
Variables are the behavior's own property schema (
BehaviorAsset.variables): they appear in the inspector, can be overridden per placed instance, and each running instance gets its own mutable copy. One node reads and writes them,Variables: its settings are a list of variables, and each one is an output that can be read at any time — no flow has to reach it — and is read at the moment it is asked, so a read after a write sees the new value. A variable switched to Set in the inspector is an input too, written when the flow arrives; the node has its flow pins only while at least one is. Unconnected and with nothing typed, it writes the zero of its kind (0, false, empty), and the node shows that value beside the name. All of them are written together, their inputs read first, so one node swaps two variables.count = count + 1is oneVariablesnode feeding its own output through anAddback into its input. The ports are picked from the behavior's variables rather than named, they take their type from the declaration, and renaming a variable carries them (and their edges) along. The node's Variables setting, above the list, decides whose declarations those are: empty means the behavior's own, a Variables asset means one value per key for the whole run (see below). Switching it changes nothing else — a port whose name the new source does not declare is marked, not dropped. A variable switched Private (the lock beside the Keep pin, off for a new variable) belongs to the behavior's own graph alone: a placed object neither shows it in its inspector or Variables tab nor overrides it, not even with its live value while the game runs — look into the graph for that. Switching a variable private drops the overrides every placed object had for it (one undo step brings them back), and a running game writes no private value back into the scene. Inside the graph nothing changes:Variablesnode, default, Keep and one value per instance. On the behavior asset the value of a private variable is greyed out and read-only; the graph sets it. Switch the lock off to change the default there. APropertiesnode of type Behavior does not offer a private variable under+ Property, but a port that names it still reads and writes it — one picked while it was public, or one typed by hand under type Any. An animation does not offer a private variable for a new row either (#270); a row that aimed at it before keeps playing and keeps its label.Propertiesis the same node for the object it is handed (fixed inputobject). Its Type setting, above the list, decides what+ Propertyoffers: Sprite, Text, Collider, Camera, Container and Clip area offer their fields, Behavior the variables of the behavior picked below it (renaming such a variable carries the port along, in any graph), and Any offers the kinds (Number, Text, …) and leaves the name to be typed. Under Sprite the list goes on with the parameters of every shader in the project, since which sprite the node will meet is only known while the game runs; each of those entries names the shader it belongs to —glow (Neon glow), andbrightness (Adjust colors, Neon glow)for a name two shaders both use (one entry, one port: the name is what a sprite stores it under). A picked property keeps its field and default — an unconnectedcoloris a colour field starting white, shown on the node as#ffffff, and a camera'swidthis its whole-pixel view width. Switching the type keeps the ports; one the new type lacks is marked. The node shows its type as a setting row (Type: Sprite, plus Behavior: Game under that type). Textures and scenes are no type here: they are not objects in a running game. A variable is a number, a text, a yes/no, a colour, a texture, a state machine, an asset, an object, a list or a struct; a list declares nothing about its items — each one is a number, a text, a yes/no, a colour, a texture, a state machine, an asset, an object, a list or a struct and says so itself, so one list may hold several kinds at once. A list is compared by its items, never by identity — otherwise every reload would look like an override. Objects can be held wherever a value can (#238): an object variable, an object field of a struct (+ Field → Object) and an object item of a list. Write theobjectoutput ofSpawn(or ofObject by name,Child by name, a collision) into one, and every later frame reads the same object back:Expandgives the field back as the object,Item atthe item.Raycast all(#157) hands out a list of objects, and written into a list variable it stays one. An object lives for the run only. At rest (in the file, a snapshot, the inspector) its place holds the empty object, stored as{"$object": ""}wherever it sits, or an object of the same scene picked on the placed behavior (#258).- Picking one: on a placed object the row of an object variable, an object field or an
object item is a dropdown with
Noneand every object of the scene in hierarchy order, the object itself included. The pick is stored as the object's id,{"$object": "<id>"}, and a run reads the object itself. On the behavior asset there is no scene, so the row says(null)and offers nothing; the default is always no object. - The eyedropper beside the dropdown picks in the view instead (#276). It brings up the scene
view, or the tab of the container the object sits in, with a bar over it
(
Target for <object> · <field>: <candidate> [Take] [Cancel]) and a crosshair. A click only makes a candidate, outlined in its own colour, and the object with the variable stays selected. It follows the selection rule: a click on the candidate passes on to the object behind it, round to the front again, and a click elsewhere takes the one in front there. Empty space keeps the candidate, and so does a spot with nothing the dropdown offers, with a notice why. Enter or Take writes it as one undo step (none when it is the target already). Escape, Cancel, a right-click in the view or a second press of the eyedropper leave the place as it was. Started from a Variables tab, it goes back there afterwards. Another tab, another selection, undo, redo or Play end it without writing. While it runs the view does nothing else (no dragging, marquee, brush, handles or menu); panning and zooming still work. - Inside a container an object only gets the objects of the same container to choose from, named as the container's own tab names them. That includes the content of a container placed inside it (#285). Every placement of the container then reaches its own copy, nested content included.
- The reference follows the object: removing it empties the reference in the same undo step, copying and pasting several objects points a reference among them at the copy (one out of them stays), Create container and Unpack carry it along. A reference to the content of a placed container follows the placement the same way: pasted together, it points into the copy, and made into a container together, it points into the placement inside the new container.
- Removing an object from a container template (#269) empties every reference to it as well,
in the same undo step: in the template, in each placement of the container in every scene and
every other container (nested ones included), and on objects outside a placement that name its
copy. The row then says
None. A placement that now holds the same value as its template follows the template again. If the template has no value of its own there, the placement keepsNoneas its own value, as if picked by hand, and Reset hands it back to the template. - Deleting an asset (#286) does the same for every object it takes along: its placements in scenes and container templates and everything parented below them. For a container asset that includes the content of its placements. One undo brings the asset, the objects and the references back.
- An object that is gone reads as
(null), and the row shows(missing). That is left for references that were already dead before #269: they disappear the next time the row is set.
During a run the object sits right in the value, and every way out of the run empties what a graph wrote there again (
core/graph/liveObjects.ts,withoutObjects): the values the editor run writes back into the scene as overrides, the save of a persistent Variables asset, the live values the inspector shows and the test hooks. A picked reference is left alone: a graph that writes another object into the place keeps it for the run, and the scene keeps its pick. A struct whose only change is an object therefore writes no override. A list that grew by an object does: the scene learns the new item, as the empty object. A destroyed object reads as(null), alone as well as out of a list or a struct, as does one from a scene that was restarted or left. "Is it still there?" isEqualswith the object onaandbleft empty: true once it is gone. Kept variables of a called behavior (#229) keep their objects across calls too, andPropertiesof type Behavior reads and writes another behavior's object variable. A list is drawn inline (#252): its row says how many items it holds and has a+ Item ▾, and right under it each item gets a row with the same widget the inspector would use for that kind — a struct item is its fields as rows under each other, a list item is a list again, drawn inline one step further in. The inspector draws the first 20 items and then says… N more, a click on that line opens the whole list in a tab; the rows follow a list that a running game fills. Where the list is stored (asset property, object override or node setting, plus apathof struct keys and list indices when the list sits inside the value, #239) is aListSlot, resolved again on every edit, so an inline edit is the same command and the same undo step as any inspector row. Variables tab: the count of a list is a button that opens that list in a Variables tab of its own, all items drawn. Its title spells the path out, for exampleEnemies · [3] · drops, and clicking the count of a list inside it opens another tab beside it — it never replaces the tab it was clicked in. A list that has its tab already brings that tab up.Openin theVARIABLESheader of a behavior, a Variables asset or a placed behavior opens a Variables tab with all of its variables (Pickup · Variables,Coin 3 · Variables), drawn by the inspector's own rows: declarations editable on the asset, overrides with reset and apply on a placed object, the values of the run while one is going. A tab whose owner is deleted closes. The tabs are documents like an asset's (values:<key>in__gm.documents()); no tab may reach into theEditor, so a button asks for one throughopenValueson the editor state. An unconnectedlistinput of a node is drawn inline too, without a tab to open: its inline value lives in the very same params bag a setting does, and the slot stops resolving the moment an edge feeds the port. What an item holds is its own and is chosen there: the kind mark in front of a row (#for a number,Aafor a text, a small picture for every other kind: a tick for a boolean, a half-filled square for a color, a grid for a texture, a square with a cross for a struct) is the picker — a click opens the menu, with no chevron, since the mark has to stay as narrow as its column — and+ Item ▾asks for the kind of the one it appends. Because the kind lives in the value, every such change is an ordinary value edit: one command, one undo step, and no neighbouring item touched. A struct item carries its own shape the same way: its fields are renamed, retyped, added (+ Field ▾) and removed on its own rows, which grows that one item and not the list. A field of a struct item may be a struct again, drawn below it one indent step further in with a+ Field ▾of its own, or a list, which is a count button. Three kinds need saying, because JSON cannot tell them from the value beside them: a colour is a number, and a texture or a state machine is an id, which is a text. Such a tagged kind wears its name as the one key of a small object —{"$color": 16711680},{"$texture": "walk-up"}(the picker a sprite'stextureproperty has — a whole picture or one frame of a sheet),{"$statemachine": "doors"}. A field name cannot start with$(isValidFieldName), so a tag can never collide with a struct item of one field. Either reads as what it wraps on a data edge (Item at), the graph having neither colours nor pictures of its own, andSet itemleaves the slot the kind it was. A texture is a kind wherever a kind is picked — a variable, a struct field, a list item, an Any port ofProperties, a field ofPack— and theTexturenode hands one into the graph, asNumberandTextdo a number and a text; a state machine is the same kind of kind, with theState machinenode. An asset is one too ({"$asset": "crate"}, theAssetnode): declared as a variable or picked in a list it offers every placeable asset, which is whatSpawnplaces, so a list of assets and anItem atspawn one of several. Every kind is one row of one table (core/properties/kinds.ts): its word and mark, what it travels as on an edge, its empty value, how a value is read as it, whether it is tagged and where it may be picked. The kind lists, the+ …menus, the tags, the port fields and the literal nodes all follow from the row, so a new kind is that row plus, if it names something in the project, its picker. Declared (a variable, a struct field of one, a port) it needs no tag: the declaration says what it is, and the value is the id — the way a colour field is a number. A declared port of kind colour or texture carries the field that edits it and travels as a number or a text, since the graph has neither; a port declared as the number a colour travels as still names the property (isPortOf). A struct variable is the other way round: few fields, so they fit under each other right in the inspector. Its head row renames it and adds fields (+ Field), each field row below carries its own name, its value and a remove — the port editorPackalready has, one level down and with a value column — and a field may itself be a list, which is the one count button that opens the tab one level deeper, or a struct (#239), which declares its own fields: it is a head row with its own+ Field, and its fields sit below it one indent step further in, at any depth. What arrives in such a field is brought into that declared shape, like the struct variable itself. Adding, renaming or removing a field reaches the declaration, the behavior's own value and every instance override in one command, so no stored value keeps a shape its declaration no longer promises. A placed instance draws every variable, whatever its kind, with the very same rows (buildVariable), minus the renaming, the kind chooser and the+ Field/xbuttons: it may override the values, not declare them. A struct's ↺ / ↑ sit on its head row, because an override covers the whole bundle rather than a single field. All of it sits on one column grid with the rest of the inspector: mark (28 px), gap and name (90 px) are exactly as wide as a plain row's label (124 px), a nested field's name gives up what its indent takes, and names are cut with…rather than pushing their value out of line. - Picking one: on a placed object the row of an object variable, an object field or an
object item is a dropdown with
-
Reordering (#310): variables, the fields of a struct at any depth, the items of a list and the declared ports of a Start, End,
Pack/Expandor shader function, and the picked rows of aVariablesorPropertiesnode (a row's warning travels with it), are dragged by the mark in front of their row — the kind mark, for a list item its number. Such a mark carries six faint dots at its left edge, 3 px clear of the edge and 3 px clear of the mark, so a row that cannot be dragged (a placed object's variable, the fields of a typed struct) is told apart at a glance. With the pointer on the mark the dots light up and the block that would go with it is washed in the accent colour. A press that moves less than 4 px stays a click, so the kind picker of a list item still opens. A block moves with all of its rows (a struct with its fields, an item with its nested list) and is dimmed while it is carried; an accent line shows where it lands, and at the top or bottom edge the panel scrolls along. The other marks do not answer the pointer while a block is carried, and every mark shows the grabbing hand until it is dropped. It moves among its siblings only: a field stays in its struct, an item in its list. Every move is one undo step. Values and edges hang on names, so only the order changes: of the rows, of the fields ofPack/Expand, of the ports on every node running the behavior. A list's order is its value, soItem at0 reads the new first item. A list cut off with… N moreis reordered among the rows drawn, the whole list in its tab. A placed object draws its variables in the asset's order and cannot change it, and the fields of a typed struct are moved on the Struct asset. -
Owner = parent: a placed behavior is usually parented to the object it controls (inspector → Parent, or drag it onto that object in the hierarchy; dashed line in the viewport). The
Ownernode returns it;Selfreturns the behavior itself;Parentwalks up from any object. -
Auto-repeat on
On key down: two settings on the node, Repeat after (ms) and Repeat interval (ms) — milliseconds, so both stay whole numbers. A delay of 0 leaves the node as it was — one fire per press; with a delay set, a key that stays held fires again after it and then every interval (0 = every frame). That is the sideways slide of a Tetris piece, or a held arrow walking a menu. The browser's own key repeat is 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. -
Keys ignore case: a letter is the same key with or without Shift or Caps Lock, so a key node set to
aalso hearsA, and a key picked with Shift held is stored lower-case. A key is let go under the name it went down as, even when Shift changes in between (1down,!up). -
Node set (
src/core/graph/nodes/, one file per node): eventsOn start,On enable,On update,On fixed update(see Fixed update),On disable,On destroy(see Lifecycle),On key down/up/held,On timer(fires on the first frame past each due time;latesays by how much, sotime - lateis exactly a multiple of the interval),On collision enter/stay/exit(stay every frame, or with an interval; while playing enter and stay tell normal, impulse and point of the contact; on a trigger tile of a tilemap all three telltileandcell x/y, once per area or with the inputper cellonce per cell, see Tilemaps),On click(the mouse pressed on the owner — anywhere when it has none),On mouse wheel(the wheel turned over the owner, or anywhere without one;deltain notches, positive downwards, a trackpad gives fractions),On animation finished,On composition finished(see Compositions),On state entered,On state exited(one output per state, see State machines),On behavior(another behavior heard as an event, see A behavior as an event below),Start· flowBranch,Sequence,Repeat,For each,Find…Match,Run behavior,End, and the four that move or follow a state machine,State branch,Set state,Set state by name,State transition(see State machines) · dataSelf,Owner,Parent,Object by name,Child by name(the nearest object of that name below the given one: its children, then their children, and so on; within a level the first in hierarchy order, nothing when none is called so),Name of,Is asset(is this object an instance of asset X — how you tell what you collided with), and the plural lookups, each handing on alistof objects taken at that moment (destroyed objects are never in it, switched-off ones are, as with the single lookups):Objects by name(every object of that name, in scene order),Children by name(every one of that name below the given object, nearer levels first, the orderChild by namesearches in),Children(the children of an object; with recursive every object below it, level by level) andObjects of asset(every instance of the chosen asset, spawned ones included),Variables,Properties(every property row of the target's type, the transform rowsscale,rotation,mirrorX,mirrorYincluded — how a graph turns a sprite),Get position(local to the parent),Get world position(in world units),Get Z(the object's ownzandworld z, summed up with its parents'),Is enabled, literalsNumber/Text/Boolean/Texture/State machine/Sound/Scene/Animation/Asset,Texture size(a texture's, frame's or pixel art's own size in pixels, 0 for none — wired into a sprite'swidth/height, a picture shows pixel for pixel),Add/Subtract/Multiply/Divide,Min/Max/Abs/Sqrt/Clamp,Lerp(from a to b as t runs from 0 to 1, t held in 0…1; the settings Easing and Direction bend the way with exactly the choice an animation key has: Linear, Step (stays at a until t is 1), the ten curves from Sine to Bounce with In, Out or In-out, or Curve, which follows the curve asset picked in the Curve row. As on a key, Direction only shows for one of the ten curves and the Curve row only for Curve, on the node and in the inspector. Back, Elastic and a curve may overshoot b; a Curve with none picked, or once it is gone, runs linear and says so on the node — one node where a slide or a flight used to take a Subtract, a Multiply and an Add per axis),Floor/Ceil/Round,Modulo,Random,Time,To text,To number,Join text,Substring,Find text,Equals,First not empty(a, unless a is empty, then b; the setting Empty means says how much counts as empty: Any empty value is nothing, 0, "", false, an empty list, or a struct whose fields are all empty such as a position (0, 0), so a real 0 or false on a is passed over; Nothing there keeps 0 and false and falls back only on nothing, an empty text or an empty list; Only a missing value falls back only where nothing arrives at all),Select(trueif the condition holds, elsefalse— the same action with one of two values, such as a walk or an idle animation for onePlay animation;Branchis for two different actions), listsList(a constant),Fill(one value repeated — how an empty board is made),Count,Item at,Set item(a new list, the one that came in is left alone; with a Type it sets only a struct with exactly that struct asset's fields, likeAdd item),Add item(a new list withvalueas exactly one more item: a struct or a list goes in whole, whereJoin listswould take it apart; at the end, or beforeindex, and an index outside the list appends; the item takes the kind the list's items share, likeSet item; with a Type it adds only a struct with exactly that struct asset's fields and reports anything else in the console, see Structs),Sub list,Join lists,Index of(by identity where the items are objects), structsPack/Expand,Length,Distance(between two objects in world units, however deep each one is nested),Normalize,Axis input(four keys → a normalized direction, so diagonals are not faster),Compare(two numbers count as equal up to float noise, a relative 1e-9, sotime - late == 6holds without aRound;Equalsdoes the same for two numbers, and for a number against a typed text that is a number, like130),And/Or/Not,Key is down,Key was pressedandKey was released(true for the one frame in which the key went down or came up, the frameOn key downandOn key upfire in; a key that stays held is not pressed again),Mouse position,Mouse button,Is hovered,Is touching,Layer of(the collision layer of an object by name and number, see Colliders),Render layer of(the render layer an object is drawn on, inherited or its own, see Concepts),Animation playing(whether an animation runs on an object, its progress and time left) andAnimation length(see Animations),Get velocity,Joint position(how far a hinge or a slider stands from how it was placed, see Physics),Current state(name and id),Is in state(one boolean per state) · actionsMove(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; with Collide with solids it slides along what it hits, less friction, and outputsblocked x/y,hitand the contactnormal x/y, see Colliders),Move toward(x, y in world units, speed, dt: steps the object toward that point by at most speed times dt and stops exactly on it, also below a moved or turned parent; with Collide with solids it moves the wayMovedoes; outputsarrived, thedistancestill left andhit. The way to walk an enemy to a spot, home or after the player),Set position,Set Z(the object's ownz, which is its draw order: higher is in front from the next frame on, see Scene editing),Spawn(asset input + x/y/z, optional parent → outputs the new object; the asset is picked on the node or arrives over an edge, from anAssetnode, a variable or a list item. A pick saved while it was still a setting reads as that input's value, so older projects spawn as before; the same holds for the asset ofIs asset),Destroy(removes the object with everything below it; with keep children on, only the object goes: its children move up to its parent (top level when it had none) and keep their place, rotation and size in the world, and the outputchildrenis the list of them, nearest level only. Its behaviors, its rigidbody and its joints go with it either way. In the editor run, children that are content of a placed container move up for the run only: their place belongs to the template, so the scene does not learn it),Add joint(ties an object to another while the game runs: the picked Joint asset is placed on object at x/y and connected to connected, the scene when nothing arrives; outputs the new joint, whichDestroylets go again, see Physics),Emit(starts a particle source over, see Particles),Play sound/Stop sound/Set sound FX parameters(see Sounds: the asset says how loud and whether it loops,fxsends it through a sound FX, whose parameters aSound FX parametersnode sets throughfx params, on a playing sound too, the node'svolumemultiplies that and itsloopeither leaves it alone or overrules it in either direction; every play is a voice of its own, so two in a row overlap, and the voice ends with the behavior that started it unlessdetachedsays otherwise),Play note(one note of an instrument, its parameters from anInstrument parametersnode,fxa sound FX of its own, see Instruments),Play pattern/Stop pattern(a whole pattern through its instrument, looped or once, owned like a sound,fxone sound FX for all of it, see Patterns),Play song/Stop song(a song at its tempo, an intro once and then round its loop, faded in and out,fxone sound FX for the whole of it, see Songs); the four play nodes have an inputbus,Set bus volumesets how loud a bus or the master is and the data nodeBus volumereads it (see Sounds),Load scene(swaps the running scene once the frame is over — see Scenes),Log,Stop game,Restart game,Start recording/Stop recording(record the game view with its sound as a video, see Videos),Play composition/Stop composition(a composition placed in the scene, its items made and destroyed as it runs, see Compositions),Pause game,Resume game,Set time scalewith the data nodesIs game pausedandTime scale(see Concepts, The game pauses itself),Break(holds the editor's Play like its pause button once the frame is over, a released game goes past it — see Concepts, Starting paused, stepping and breaking),Apply force,Apply impulse,Set velocity,Set angular velocity,Set gravity(see Physics),Raycast(what a ray meets first on the layers it looks at: object, collider, point, normal, distance — see Colliders),Raycast all(everything on that line instead, as parallel lists sorted by distance, with acountand amax hits— see Colliders),Ignore one-way(fall through one-way colliders and tiles for some seconds — see Colliders),Set tilewith the data nodesTile at,World to cell,Cell to worldand the constantTile(read and change the cells of a placed tilemap while the game runs — see Tilemaps). A node type is one file exporting aNodeDef(ports, settings,run), listed innodes/index.ts; palette, ports, validation and inspector fields are generated from it. A port may declare adefaultfor its inline value (e.g.Move.scale = 1), shown on the node and in the inspector. -
Ports that are not fixed by the type: a
NodeDefmay saydeclaresPorts(the node carries its own list, edited in the inspector) orderivePorts(the node works them out from its settings and from the graph around it).resolveDef(node, graph, lookup)is what applies both, so the canvas and the inspector see a definition that fits this node; a node whose ports are derived reads them back off that definition, which is whyrunis handed it.Sequencekeeps one free output below the last connected one — connecting the spare grows it, unplugging the bottom edge shrinks it again (keysa,b,c, … up to 26).Run behaviorandOn behaviortake their ports from the behavior they name, and a behavior'sStart/Endnode declares them. Pointing a call at a different behavior takes the edges of the ports that go away and gives them back on undo. -
Ports that carry one and the same value say so with
sharedType({inputs, outputs}), and their outputs then take the type of what is plugged in: withaandbof aFirst not empty(orfalseandtrueof aSelect) both fed by a number,valueis a number port — coloured as one, and only accepted where a number fits. The inputs stayany: they take everything, so a connected one can be swapped for another type without unplugging it first — their pin is still painted as whatever flows in, each one on its own, so a pair fed by a number and a text shows two colours and thereby says why the output stays grey. That colour is paint only (paintType): the port itself keeps taking everything, and everyanyinput is painted this way, not just one in a group. The output falls back toanywhenever an input is unconnected or the two disagree. The type travels through a chain of such nodes: aSelectfed by twoFirst not emptythat both hand out an animation is an animation port too, and one mixed or open input anywhere in the chain makes everything behind itanyagain. Nodes that feed each other in a circle stayany— the question of what feeds a group remembers whom it has asked (shapedDefincore/graph/nested.ts), so a circle ends instead of asking forever, and a node feeding both inputs of the next is asked once. Nothing is cached between two changes: a chain of 50 resolves in about 10 ms on the slowest machine measured. An edge that a new type makes invalid is left alone and drawn in the danger colour; nothing is disconnected behind your back. All of this is the editor's view — at run time every value travels the same way either way. A group may bring its own rule (agree) in place of "all the same": the arithmetic nodes of a shader graph use the compiler's, where one colour makes the result a colour (see Shaders). -
An edge can outlive its port, because a derived list loses a key without anything touching the edges: a state deleted in its machine, the
machineedge cut, aStartnode or a whole asset removed. Only a setting of the node itself is cleaned up (above) — the other four reach across graphs, where no single command can see them. So the canvas puts the key back as a ghost port: a hollow red pin, the key as its label, the edge drawn dashed in the warning colour and the node marked "1 edge on a port that is gone". It is a handle, not a port —resolveDefnever has it, so nothing runs through it and nothing new can be connected to it; right-clicking it offers the usual link menu, and Disconnect is how it goes away. Leaving it alone is just as valid: put the state back and the edge works again. -
Structs: several named values travelling as one, so a bundle needs one edge instead of one per part.
Packdeclares its fields as inputs,Expandreads them back out as outputs, a behavior port may be declaredstruct— which is how a bundle crosses a call — and a variable may be one, which is how a bundle is stored. APack/Expandcan take its fields from a variable that already has the shape (From variable: a struct variable, or the first struct item a list holds — a list declares nothing, so its value is the only source) instead of having them typed again; that adds the fields it does not have yet as one undo step and leaves the ones it has alone. Writing a struct variable keeps the fields it declares: what arrives fills the ones it names, the rest stay put (mergeStruct), so one node can set part of a bundle without erasing it — "the same piece, one row lower" is aPackof that one field. Nothing bounds the depth (#239): a list item and a struct field may each be any kind, a list or a struct included (NestedKindinkinds.ts), and nothing deeper is dropped on load, in a node setting or on the way through a graph. There is no depth limit either. A struct inside a struct field is declared (fieldson the field, like a struct variable), while a list still declares nothing about its items, so a struct item in a list takes its shape from its value at any depth. A struct may also follow a struct asset instead of declaring its fields itself (#253): its Type row picks one, andPack/Expandhave a Type setting that makes their ports the asset's fields — see Structs.Pack,ExpandandItem attake nested data apart one level at a time: a struct in a struct is twoExpandnodes in a row, and there is no path syntax likea.b[2].c.+ Itemoffers every kind, on a list of its own and on one a struct field holds alike. A live entity is an object on a data edge too, so it carries aSymbolbrand (ENTITY_HANDLE) that no struct field name can impersonate; nothing may hand a property value toString()(textOfinstead), or a struct would read as[object Object]. A struct port has no inline value (its parts come from aPack), and compatibility is per kind, not per shape — any struct fits any struct port. -
Loops:
Repeatruns itsbodyoutput a number of times, countingindexup from 0, and continues atdone. Data nodes are cached for the length of one event pulse, but a loop round drops that cache, so an expression built onindexreally changes with it. That is what makes a graph able to work through a list: the Tetris example draws its 200 board cells that way,Object by nametaking the name the round put together ("c" + index). Two guards, not one: a pulse may take 100 000 steps (a loop legitimately runs long), but exec edges may only nest 1000 deep — that is what catches a flow feeding back into itself before the call stack does. -
For each runs
bodyonce per element of a list,itemandindextelling which one, and continues atdone. Itsbreakinput is read anew before every round, afteritemis set: once it is true, the loop ends there. That round'sbodydoes not run, anddonesees itsitemandindexwithstoppedtrue. Without a breakdonesees no item,index-1 andstoppedfalse. A break built on the item is the predicate of a search:Expand(item).NameintoEquals "Gold"intobreakfinds the first loot named Gold, likelist.FirstOrDefault(x => x.Name == "Gold"). As withRepeat, every round reads its data inputs anew, so a variable the body changes counts for the next round's break. -
Find … Match is the same search as a block that reads left to right, without an edge running back into the loop node.
Findtakes thelistand hands outitemandindex; whatever is built from them ends in thematchinput of aMatch, and that is the test. The first element it is true for continues atfoundof the Match, with thatitemandindex; none continues atnot found,itemempty andindex-1. Adding aFindbrings itsMatchalong beside it. The block is nothing but the wiring: a Match closes the Find its test is built from, so a copy pasted elsewhere is a block of its own. A soft frame behind the Find, the Match and the nodes between them shows what belongs to the test (a literal feeding the test from outside stays outside). While a Find has no Match, or two Matches test it, or one Match is built from two Finds, both nodes say so and nothing runs past the Find. The test is data only: every element reads it anew, like the break ofFor each. Over a typed list, right-click theitempin and split it into the fields of the type (item.Name, …), so the test needs noExpand— see Structs, Split outputs. The Find's setting Mode says what the Match answers. First is the search above. All looks at every element and continues atdonewithitemsandindices, the lists of every element the test is true for (empty when there is none). Any stops at the first hit and hands onany(true or false), Count hands oncount. The Match takes the outputs of its Find's mode; switching the mode drops the edges on outputs the Match no longer has (undo brings them back). Typing filter into the palette adds a Find already set to All. A Find saved before the setting existed is First. -
A behavior as a node (nesting): a behavior, usually one with a Start and an End node, can be run as a step of another behavior's graph. Every input declared on the start node becomes an input port of the calling node, every output declared on the end node one of its outputs; the node is drawn under the behavior's own name, and the palette lists it there too, so a call is one pick. Declaring a port keeps both sides in step — renaming carries the typed-in value along, removing takes the edges hanging on it in every graph that calls the behavior. A call is a call, not an inlining: the nested graph runs in its own pulse cache with a fresh copy of its variable defaults, so values travel in and out through the ports and nothing else. That makes a call repeatable and keeps a nested behavior free of hidden state. The one exception are local state machines, which belong to the instance running the call (see Keep below). A behavior that would run itself raises an error instead of looping; there is one start and one end node per behavior. A behavior without a start node is called from its On start nodes instead, all of them in graph order, as when it starts on its own. It takes no inputs then, but its end node still hands back what it is given. With a start node in the graph, its On start nodes stay out of the call.
-
More than one way in and out (flow ports): next to numbers, texts and the other kinds, the port list of a Start and an End node offers the kind Flow. A flow port declared at the End is a further flow output of every node running the behavior, one declared at the Start a further flow input. They stand above the value ports, below the fixed
exec, and share their names with the values of the same node;execstays taken. A call that comes in by the inputopenbegins at the Start's outputopenand nowhere else; the Start's plainexecfires only for a call that came in byexec. The values are the same whichever way the call came. On the way out the node goes on first through the output named like the End's flow input that was reached last, then throughexec.exectherefore always fires, as it did before there were flow ports, and reads as "and then". A call whose End was reached byexeclast, or not at all, goes on throughexeconly. One call takes at most one of the further outputs: when its flow reaches the End several times, the last one decides, as it does for the values. Every call decides anew, so a call inside a loop may leave differently each round. A behavior without a Start node has no further ways in, since they are declared there; the ones at its End work all the same. In a behavior that runs on an object and is not called, the Start's outputs never fire, the further ones no more thanexec. -
A behavior as an event (
On behavior): the palette lists every other behavior that has an End node a second time, as On name among the events. That node has no flow input. The behavior it names lives at the node for as long as the behavior around it runs: all of its own events fire as if it were placed on the same object (keys, mouse,On update, timers, collisions, animations, state events, andOn start,On enable,On disableandOn destroyin step with the behavior around it), and its variables, timers and local state machines keep their state, one set per node. Two nodes hearing the same behavior share nothing, and Keep plays no part here. The node fires whenever a flow in there reaches the End: through the output named like the flow input the End was reached by, with the values the End was handed. Unlike a call, the plainexecfires only when the End was reached byexec; a flow that never gets to the End is not heard at all. The outer flow starts once the inner one has run out, so a flow reaching the End twice is heard once, by the way it took last. The value inputs of the Start node are inputs of the node, typed in or connected, and read anew for each of the inner flows; the Start itself never fires, and its flow ports do not show. Example, a "Confirm" that is Space or Enter everywhere: in the behavior Confirm, twoOn key downnodes lead to a flow portpressedof its End. Any other behavior then holds one node On Confirm and wires itspressedoutput; a third key is added in Confirm alone. Behaviors may hear each other as deep as you like. A behavior that would hear itself, directly or through others, says so on the node and is not started a second time. Inside a behavior that is run as a node anOn behaviorstays silent, like every event there. The node has no Order of its own: the events inside carry theirs and run in their kind with everyone else's. Double-click opens the behavior heard, with the breadcrumb of a call. While it runs, its graph lights up like a called one. A node that cannot fire says why: no behavior picked, the behavior deleted, or no End node in it. -
Keep (the pin on each variable of the called behavior, in its inspector): a kept variable picks up where the last call from this node, on this object left it; the others start at their default every call. Every variable starts out not kept, a new one as well as one from a project saved before the switch existed; Keep is switched on by hand. Two call nodes running the same behavior keep a state each, and so do two objects running the same graph; a call inside a call has one per outer node. It follows today's declarations: a variable added since starts at its default, a removed one is dropped, and one whose kind changed starts over. The kept value lives as long as the object does in the run and is never saved. Local state machines need no Keep: a call reads and moves the states of the instance running it, so they carry over from call to call anyway (see State machines). For state shared between objects, use a Variables asset or a behavior object in the scene (see Cross-object state below). Three ways in and out of a call: drag a behavior out of the asset panel onto the graph and it lands as a call where you let go; double-click a call node to open the graph it runs. The way down stays above the graph as a breadcrumb (
Game > Move > Other): click a crumb to go back there; the first one returns to where you started and the bar goes away. Following a call stays in the same tab, and the trail belongs to that tab: a behavior opened any other way comes up in a tab of its own, without a trail, and switching back brings the trail back with it. And a call that cannot do anything says so on the node — no behavior picked, the behavior deleted, or neither a start node nor an On start in it. It also warns when a State machine input is set to another machine than the one its Start input carries (see State machines). The missing start is the one worth naming: without either there is no way in, so the call is skipped and the flow simply carries on at itsexecoutput. A missing end node is not a defect — a call is always synchronous, "done" is the nested flow running out, and the end node only decides what comes back. -
Shared state: a Variables asset is a list of declarations and nothing else — no graph, no position, never placed. A
Variablesnode naming it reads and writes one value per key for the whole run, however many graphs point at it and however many objects wear them. The values start at the declared ones every time Play does and are never written back, so playing in the editor cannot change the project. While a run is on, the asset's inspector shows the values as they stand and an edit there reaches the run at once — the declaration underneath keeps the value the next run starts from. Two Variables assets may declare the same name without meeting: the asset is the key, not the name. A coin counter is one such asset withcoinsin it; picking a coin up is one node,coinsfed throughAddback into itself. -
Scope: a Variables asset is shared either way, and the dropdown says for how long. Global (the default) is the whole run: the values travel through every
Load scene, which is what lets a score survive the next level. Scene is one set per scene, built again from the declarations whenever a scene is loaded — for what a level counts and the level takes with it, like the enemies left in it or a switch that was flipped. A scene asset cannot be persistent; there is nothing to keep between runs when every scene throws the values away, so that switch is not shown. -
Saved values (savegame): tick Persistent in a Variables asset's inspector and its values outlive the run — a highscore, the levels unlocked, the player's settings. The next Play starts where the last one ended instead of at the declared values. Nothing new is needed in the graph: the same
Variablesnodes read and write them, and every change is saved as it happens, so a closed tab loses nothing. The save lives in this browser, per project, and never in the project file. Played and editor runs save apart, so trying a behavior with Run in editor does not touch the player's save. To test a first start again, Clear saved values under the switch forgets that asset, and Menu → Clear saved data forgets the whole project. A save is read through the declaration as it is now: a variable deleted since is dropped, one added since starts at its declared value, and a value whose kind changed is converted like any other value. An object variable may sit in a persistent asset too, but is never saved: the object is an instance of that run, and the next run has new ones. It starts every run at(null)while the other values go on, and the inspector says so under the switch. The same goes for an object in a saved list or struct: the save keeps its place, as the empty object. -
Cross-object state the other way: a behavior's own variables are the properties of its object, so another graph reads and writes them with
Object by name→Propertiesof type Behavior. That reaches one named object, where a Variables asset belongs to no object at all. -
Mouse: the Game tab feeds the pointer to the world in world units.
On clickfires 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. A behavior without an owner has no shape that could be hit, so it takes every click inside the camera frame instead (theworld x/world youtputs say where) — that is the "click anywhere" listener.On mouse wheelfollows the same rule; what turned within one frame arrives as onedelta, one notch of a mouse wheel being 1. A part a clip area cuts off is never hit, by neither of them nor byIs hovered(see Concepts).Mouse positionandIs hoveredtake acamera(#304). Left open, they see the mouse through the main camera as before. With a camera, they see it through that camera's picture as an overlay camera lays it on, so a HUD button that only an overlay camera shows can be hovered.in viewthen says whether the mouse is on that camera's picture; a camera that is not drawn (switched off, or a second camera without Overlay) never has it. The position is kept on the picture, so a main camera on the move does not drag the mouse across the HUD.On clickandMouse buttonstay with the main camera. A HUD button is clicked withpressedofMouse buttontogether withIs hoveredthrough its camera. -
Idiomatic movement (the demo's "Mover", five nodes):
On update → Move(Owner, dx = Axis input.x, dy = Axis input.y, scale = speed, dt = On update.dt),speedbeing aVariablesnode. -
Idiomatic platformer (the example game's "Player"):
On updateaddsgravity · dtto avyvariable and moves the owner by(axis.x · speed, vy)with Collide with solids on;blocked ywhile falling means standing, sovygoes back to 0 andonGroundbecomes true;On key down (Space)setsvy = -jumpSpeedwhenonGround. -
The palette answers by how well it matches, not alphabetically: an exact hit on a node's name or type first, then a name that begins with what was typed, then one that contains it, then the type, and only last a node whose description mentions it. Category and alphabet order what is equally good. That is what
Entertakes, so typingvariableadds the node called that and notRun behavior, whose description happens to mention variables (#43). -
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,
Deleteremoves the selection, wheel zooms. The wheel zooms over a node too, nothing in a node takes it (#383). Everything is undoable. -
Copy and paste (#240) works in every graph (behavior, state machine, shader, particles):
Ctrl+Cor right-click → Copy takes the selected nodes, the links between them and the groups and comments selected with them.Ctrl+Vpastes under the pointer, or one grid step beside the original when the pointer is elsewhere; the palette on empty canvas offers Paste at the spot it was opened. A link to a node that was not copied stays behind. A node is pasted only where the palette would offer it: aStatenever lands in a behavior, a behavior node never in a shader, a secondStart/Endand a behavior calling itself are left out. What is left out is named in a notice, the rest is pasted as one undo step and selected. -
Mouse and selection work as in the scene view. The middle button pans from anywhere, a node under the pointer included (shift-drag on empty canvas does too); the left button never pans. Dragging on empty canvas pulls a rubber band instead, which selects every node it touches — nodes are large rectangles, so touching counts, unlike the scene view's test of an object's position. Ctrl/Shift-click adds or removes single nodes, dragging any node of a selection moves all of them, and
Deleteremoves all of them — one undo step either way. -
A link is picked on its line (#289). A click on the curve selects that link and draws a halo behind it; Ctrl/Shift-click adds or takes one, and the rubber band takes every line it crosses, into the same selection as the nodes and boxes it caught.
DeleteorBackspaceremoves what is picked as one undo step, and a right-click on a line offers Hide link and Delete link (it speaks about the whole selection, like a box's menu). The inspector shows a picked link: the node and port at each end and the type it carries; for several it says how many. It has no buttons of its own, hiding and deleting live in that menu. A press counts within 6 screen pixels of the line, and where two cross the one drawn last wins. A hidden link has no line and is never picked this way: its mark at the port stays its handle. The port's own menu is unchanged, Disconnect included. The hit test iseditor/graph/edgeHit.ts, the canvas only asks it. -
The band catches groups and comments too (#95), and they end up in the same selection as the nodes: one drag then moves all of it, one
Deleteremoves all of it, one undo takes it back. A box counts only when the band holds all of it, where a node counts on touching alone. That difference is not a mood: a group wraps the nodes it holds, so it overlaps every band drawn over any of them, and since a group drags its nodes along, catching it by accident would be felt at once. A node inside a group that is selected too is still moved once — it belongs to both, and the offset is applied to each thing once. -
Dragging an edge tells you where it would land before you let go: the port it would connect to wears a white ring and the line snaps to it. Dropping on a node's body rather than a port takes that node's first free compatible port — or, when none is free, the last compatible one, whose link is then replaced. A drop into empty space opens the palette filtered to the node types this edge could reach at all, and the picked node arrives already wired; node and link go in as one undo step (
core/graph/dropTarget.tsdecides all of this, the canvas only draws it). The dangling edge stays on screen while that palette is up, so the connection being made is still visible while its node is chosen. "On a node" means the area the node's pointer events use (NodeView.containsLocal), which reaches a port radius past the left and right edge: an input port sits at exactly the node's edge, so a drop that hits the port hits the node, and never the empty canvas a hair beside it. -
A link can be hidden, so a connection across the whole graph does not have to be drawn as a line across the whole graph. Right-click a port → Hide link; the curve is replaced by a short stub at each end, ending in a dot captioned with the node at the other end. The connection is untouched — hiding is drawing only, and
foldedon the edge is the entire change. The curve comes back, dimmed, while either of its two nodes is selected, and always while a pulse is travelling it, so the live trace never loses its thread. Clicking a dot selects the node at the far end, which both answers "where does this go?" and lends the curve back; right-clicking it offers Show link and Disconnect (editor/graph/folding.tsholds the visibility rule, the canvas only draws it). A port's hidden links share one mark — drawn per link, several of them would stack stub, dot and caption on the very same pixels. The mark then grows a little, is captionedAdd +2, and one click selects every far end at once, which is also what lends all of its curves back; the menu counts what it acts on (Show 3 links), and does it as one undo step. -
A node can be collapsed to its title bar: the chevron in front of the title (it leaves the selection alone, like the hierarchy's), or right-click → Collapse node / Expand node, which acts on the whole selection as one undo step. Every link of one side then meets at a single point on the title bar, and its hidden links share one mark per side. It narrows to its title (never wider than an open node, where a long title is cut). A collapsed node has no ports to grab, so a link dropped on it goes nowhere — expand it to wire it. Like hiding a link, this is drawing only:
collapsedon the node is saved with the project. A collapsedVariablesorPropertiesnode, titled just that while open, is titled by what it does, since its ports are out of sight: Set 2 variables when more than one is written, else Get 2 variables when more than one is only read, else Set speed / Get speed, and No variables when it has none. -
Groups and comments put words on a graph that has grown. They are two separate things, because they mean two different things (#38):
- A group is a titled box drawn behind the nodes. It carries what it encloses: dragging its title bar moves every node whose centre lies inside, and any group or comment centred in it as well — with, in turn, whatever those carry. The centre, not an overlap, because a group and the node beside it always touch a little; membership is worked out fresh each time the box is grabbed, so a node dragged out of it is simply out, with nothing to "ungroup".
- A comment is a card of free text. It frames nothing and moves alone.
- Both are created from the right-click palette (Add group, Add comment, listed above the
nodes); a selection of nodes can be boxed directly with right-click → Group these 3 nodes.
The search finds them under "group" or "comment", deliberately not under "add" — that is
the word you type to find the
Addnode. - A group is grabbed by its title bar only. Its body stays transparent to the pointer, or a rubber band could never be drawn across the nodes it holds. The corner at the bottom right resizes it, and resizing moves nothing: what the box ends up around is what it carries next time. With the magnet on, the corner being dragged lands on the grid — the far one, since the near corner is exactly what a resize must leave alone. A comment, framing nothing, is grabbed anywhere on it.
- Deleting one takes the box and nothing else. The nodes stay exactly where they were — a box that silently deleted ten nodes with it would be the most expensive surprise in the editor.
- Title, text and colour are edited in the inspector. The colour is a free picker, and the
text on it is drawn black or white, whichever reads better (relative luminance,
ui/color.ts) — that is what makes a free picker safe on a dark canvas. Use the default color takes a box back to the theme's. - All of it is drawing only:
groupsandnoteson the graph, saved with the project, read by no interpreter. A graph that has neither stores neither array, so an old document means exactly what it says. Every graph tab has them — behaviors, state machines, shaders, particles.
-
Header bar (the same one the Texture and Container tabs have): Fit (which zooms out far enough to hold every node, measuring each by its own drawn height, and every group and comment with them — a fit that framed the nodes alone would cut the box drawn around them, and a graph holding nothing but a comment would frame the empty origin;
editor/framing.ts), and the size of the graph as "N nodes · M links". It stops zooming out at 0.125 (0.25 before #387), so a box parked far enough away can still lie outside what a fit can show. -
Snap (the magnet in the header, an icon toggle that stays lit while on; on by default in every graph tab since #391, a curve and a vector art still start without it): a drag drops each node it carries on the nearest 24 px grid line — the same grid the background draws. It takes effect on the next drag only, so switching it on never shuffles a graph that is already laid out, and a node nobody drags never moves. Every node of a selection snaps for itself, so dragging a group is how you line one up; the offset is a decision about rectangles and lives with the others in
editor/framing.ts. -
Arrange (next to the magnet, in every graph tab, in a song's only with its routing; #429) lays the graph out: the flow runs left to right in columns, a node that only feeds others (a Get, the Owner) stands just left of the first one it feeds, each column is put in the order that crosses the fewest links — of two orders that cross as often, the one with the flow on top — and the nodes are moved up or down towards the ports they link to without ever overlapping. With several nodes selected it arranges those and leaves the rest; they keep their place if they fit there and no link to the rest runs backwards from there. Else they go next to the node they link to — left of one they hand a value to, right of one they take from — where there is room and their links cross the fewest others, and of those places the one nearest to the two ports lying level. Nodes put between two that stay, with no room there, push what they feed and everything right of it further right; only when that fails too, right of everything. Groups and comments go along in the same undo step, Arrange nodes: a group whose nodes all moved alike moves as it is, else it is fitted round its nodes again; a comment goes with the group or the nodes just below it (or just above it). It measures every node as drawn; the rules are the pure
editor/graph/arrange.tsandeditor/graph/annotationFollow.ts. -
One tab per behavior: each behavior opens as a document of its own (double-click in the asset panel), several at once, each keeping its zoom and scroll for the session. A selection never changes what a tab shows; selecting a node of another graph brings that graph's tab up. A deleted behavior closes its tab.
-
Live view: Play always switches to the Game tab; switch to Behavior while playing and the exec edges and nodes that just ran light up — the same canvas draws editor and game, so this costs nothing extra. Each fire of an exec edge also sends a bead along it (0.3 s, at most one per 0.15 s and edge): a key press is a single bead,
On updatean even chain. Beads run on game time, so a paused game holds them where they are. Every data output shows, in the same colour, the value it last carried (0.017 dt,Pause box object), refreshed ten times a second. With several objects running the same behavior it is the one that produced last; in a loop, the last round. An output nothing has asked for yet shows nothing. The graph stays editable meanwhile: the interpreter reads the runtime copy's graph on every pulse, so an edit takes effect on the next frame and is discarded with the run. A newOn startnode does not fire for a behavior that has already started:On startis asked once, when the instance comes to be (see Lifecycle). -
Lifecycle: a behavior gets its instance when it is effectively on — itself and every parent switched on (and, in the editor run, "run in editor" set): at play start, when spawned, or the moment a hidden screen is shown. The events come in this order:
On start→On enable→On update… →On disable→On destroy.On startruns once per instance.On enableruns right after it and again every time the behavior comes back on;On disableruns in the frame it goes off, a parent's switch included. Off, the instance is frozen, not gone: variables, timers and states survive, and switching it on again isOn enable, notOn start.On destroyruns when the object leaves the world — throughDestroy, as a child of a destroyed object, and when the game stops or restarts; a restart is thereforeOn disable→On destroy→ (new world)On start→On enable. An object destroyed during a frame gets no further events of that frame, the two farewell events follow at the end of it; an object destroyed while off getsOn destroyalone. Off and on again within one frame is no edge: neither event fires. A behavior that comes to be during a frame — spawned, made by a composition or switched on the first time — hearsOn startandOn enablestill in that frame, before it is drawn, andOn updatefrom the next. An animation itsOn startplays is drawn at frame 0 right away. Only what such anOn startspawns in turn waits for the next frame. -
Event order: a frame goes event kind by event kind, each across every behavior:
On disable,On enable,On key down,On key up,On key held,On timer,On collision enter,On collision stay,On collision exit,On animation finished,On composition finished,On click,On mouse wheel,On fixed update(as many steps as are due) and lastOn update. So a key event of any behavior runs before the firstOn update. The state events a kind caused follow right after it. New instances get all theirOn startand then all theirOn enable, a finished world allOn disableand then allOn destroy. Within one kind every event node has the setting Order, a whole number, 0 by default: a lower order runs first, whatever behavior it sits in, so-10runs before everything left at 0. Equal orders run in hierarchy order, top to bottom as the hierarchy shows it right now (a spawned object stands where it was put), and within one graph in the order the nodes were made. The node showsOrder: 5only when the order is not 0. -
Fixed update:
On fixed updateruns 60 times per second of game time, each time withdtexactly 1/60, however long the frames are. A frame at 120 Hz has a step only every other time, a frame at 30 Hz has two, one after the other, beforeOn update. A node inside a step that readsdt(Time,Move,Apply force) gets 1/60 too. So something that counts up or down in fixed amounts per step (a pitch sweep, a countdown) comes out the same on every run and every machine, where adding up a frame'sdtwobbles with the frame rate. It counts its steps by the same rule as the physics, so it runs exactly as often as the physics steps. The time scale slows it down like everything else, and while the game is paused it stands still, also for a behavior that runs while paused. After a hitch it catches up 8 steps at most and drops the rest, as the physics does. Code:fixedStepsinsrc/core/time.ts. -
Runtime:
BehaviorSystemcreates oneGraphInterpreterper behavior entity; graphs only touch world entities. A runaway pulse throws (see Loops for the two limits) and stops play with a notice.
Using the editor
- Concepts
- Accounts
- Agents (MCP)
- Scenes
- Scene editing
- Behaviors
- Structs
- Colliders
- Physics
- Textures
- Vector art
- Pixel art
- Fonts
- Videos
- Sounds
- Instruments
- Patterns
- Songs
- Animations
- Curves
- Tiles
- Tilemaps
- State machines
- Shaders
- Particles
- Containers
- Compositions
- Keyboard
Working on the code