Skip to content

Object Flow integration

Components are not a separate scripting language—they plug into the same Object Flow graphs you already use for taps, drags, and scene logic.


Node catalog

When an object is selected in Object Flow, the node library includes a Components group built from:

  1. Attached components on that object (actions + conditions).
  2. Optionally, catalog entries derived from the global registry (see catalog.js).

Each catalog item becomes either:

Catalog kindFlow node action.type / condition.typeLabel example
componentActioncomponentActionhealth.damage
componentConditioncomponentConditionhealth.isAlive

Payload shape for actions:

json
{
  "type": "componentAction",
  "component": "health",
  "method": "damage",
  "params": { "amount": 10 }
}

Payload shape for conditions:

json
{
  "type": "componentCondition",
  "component": "health",
  "method": "isAlive",
  "params": {}
}

The Flow Inspector renders dropdowns for component, method, and typed parameters based on the definition schema.

Core graph nodes use the same modular pattern through defineFlowNode() and registerFlowNode(). A registered flow node owns its catalog item, creation defaults, normalization, canvas layout, labels, and graph-lint behavior. Built-ins are registered from src/game/flow/nodes/, and project/plugin node modules can register additional node types without editing the central type list.


Wired action parameters

Registry actions (moveTo, positionAbove, snapToTarget, …) expose param: data pins derived from their field schema. Connect a Get Value node (for example Active Turn Object Id or Event Payload Field) into param:targetObjectId or param:objectId so the target updates at runtime without duplicating rules per combatant.

The Position Above action snaps or tweens an object above another using each thing’s height plus an optional offsetY.

Runtime execution

rules.js dispatches during graph evaluation:

  • Conditions: thing.components.check(component, method, params, eventCtx)
  • Actions: thing.components.run(component, method, params, eventCtx)

eventCtx carries event, source, and target from the active rule so actions can respect interaction context.

The host object for the graph is the Thing that owns the rule. Component actions always target that Thing’s ComponentHost unless your action implementation resolves another object (e.g. Team conditions with objectRef params).


Expressions in graph fields

Many flow node fields accept inline expressions prefixed with =:

text
=self.hp > 0
=params.amount * 2
=Math.min(self.hp, self.maxHp)

Evaluator: src/game/components/expressions.js (evalValue).

Available bindings

IdentifierDescription
selfComponent fields when the node targets a componentAction / componentCondition; otherwise null
paramsNode parameter bag
eventActive event payload (includes payload for custom / component events)
source / targetInteraction actors
thingResolved object for the node (objectId or event actor)
varsBinding/scripting view of state (vars.counters / vars.flags still resolve for expressions; prefer declared GameState and object variables)
get(id)Sibling component fields on thing, e.g. get("health")?.hp
MathStandard Math object

Event Payload Getters

Component events can declare a typed payload:

javascript
events: {
  damaged: {
    label: "Damaged",
    payload: {
      amount: { type: "number", label: "Amount" },
      sourceId: { type: "objectRef", label: "Source" },
    },
  },
}

Object Flow uses that schema to populate Event Data getter nodes and typed data pins on explicit event nodes for the active component event graph. The persisted getter nodes stay generic getValue records with valueSource.source === "eventField" and paths such as payload.amount.

Event nodes are explicit start nodes, not a generated list of every possible rule. A designer places or opens the event they need, such as On Tap for a button object or On Event for start_game, then wires that event node into actions like Emit Event. Other objects can subscribe by placing/opening their own explicit On Event graph for the same key.

Opening an object's Flow Graph shows the explicit event nodes that have been placed on that object. It does not create a node for every event type or every legacy rule. Right-clicking the canvas can place On Tap, On Event, or a component-defined event; action nodes belong to whichever event flow they are added to or connected from.

Value nodes and data wires

Object Flow supports typed value wires alongside exec wires:

NodePurpose
Number / Text / Boolean (literal)Constant value on the graph
Get (getValue)Read GameState / scene variables, object variables, component fields, event fields, or active turn id
Set Value (setValue)Write GameState / object variables or exposed component fields when exec reaches the node

Connect a value node's value pin (right) to an action's param: pin (left), e.g. param:amount on health.damage. Data edges use gold cables; exec edges stay white/green.

Implementation: src/game/flow/bindings.js, src/game/flow/ports.js.

Component fields can opt into setter nodes:

javascript
fields: {
  hp: { type: "number", default: 10, flow: { set: true } },
}

Fields, params, actions, conditions, and events can also use flow.expose: false to stay out of the graph catalog. Action params can use flow.pin: false to remain editable without creating a data pin.

Safety rules

Expressions must be single expressions (no ;, no if/for, no new, no eval/Function, no window/document). Failures log a warning and evaluate to undefined so play mode keeps running.

Use Script action nodes when you need multiple statements.


Script action node

Flow node type: action.type === "script".

Body is a multi-statement script compiled with compileScript:

javascript
// Example body in Flow Inspector
const hp = thing.components.get("health")?.fields;
if (hp && hp.hp > 0) {
  thing.components.run("health", "damage", { amount: params.power || 5 });
}

Available: self, params, event, source, target, thing, components.


Expression condition node

Flow node type: condition.type === "expression".

Body is one expression returning truthy/falsey:

text
self?.hp > 0

Branches use standard true / false ports to chain actions.


Subscribing to component events (graphs)

Component events (e.g. health.died) are declared on the definition. To react in graphs:

  1. Use On Event / custom event nodes where the engine bridges component emissions into the rule system, or
  2. Use sibling handlers on another component on the same object (see Code authoring), or
  3. Call follow-up actions in the same graph after a componentAction node.

Health Bar’s health.damaged handler is an example of (2)— no graph required for the flash.


Scene Flow vs Object Flow

ScopeGraph locationComponent nodes
Per objectObject FlowYes — catalog from attachments
Scene-wideScene FlowUse Script / scene actions unless scene rules gain component targets

For battle controllers, either attach Turn Queue to a dedicated object and use Object Flow on that object, or invoke component actions from scene rules via script that looks up the controller by id.


Empty graphs and “On Tap”

The editor only counts flows with real logic when showing “events with logic,” and avoids auto-creating empty On Tap rules. Component nodes count as logic once placed on the canvas.


Debugging checklist

SymptomCheck
Node missing in catalogComponent attached? Registry loaded?
Action no-opMethod name typo? Params coerced? invulnerable?
Condition always falseParams default? Wrong object ref?
Expression undefinedMissing =? Forbidden token?
Works in editor, not playPlay reset restored fields? onPlayStart refill?
Warning badgeMissing component/action/condition/event/field, stale param, or component not attached

  • src/game/components/catalog.js — catalog → flow node
  • src/game/flow/define.js — flow node definition helper
  • src/game/flow/registry.js — flow node registry and lookup helpers
  • src/game/flow/nodes/ — built-in modular flow node registrations
  • src/game/interaction/inspector.js — component + script editors
  • src/game/interaction/model.js — node labels (health.damage)
  • src/game/rules/runner.js — runtime dispatch

Back to Getting started · Built-in reference

Opal Engine — MIT licensed