Skip to main content

Ports and Data Flow

GCS ยท FlowGraph Authoring

Ports carry two things: execution order and values. This page covers the value side, from target sources and collections to action results, variables, and when a value is actually read.

Read this once your graphs stop being straight lines. You do not need C# types; gameplay terms are enough.

Every wire in a graph is either a control wire or a data wire. Control was covered in the editor workflow; this page is about the data side. Think in gameplay terms: unit, card, status, number, yes/no, text, collection.

Rapid Fire card graph showing data wires from All Enemies through Random Elements into Deal Damage's Target port

Rapid Fire is a compact data-flow example: All Enemies produces a unit collection, Random Elements narrows it to one unit, and that unit flows into Deal Damage.Target on every loop iteration. Each hit can land on a different enemy because the target is re-pulled per iteration.


Control portsโ€‹

Control ports decide what runs next.

Port shapeMeaning
Single outputContinue to the next node.
True / FalseBranch on a yes/no condition.
Body / DoneRun the body per iteration, then continue when the loop finishes. Loop and Foreach also expose an Index data output.
OnChosen / SkippedContinue after a player choice, or after the player declines.
Numbered or named outputsOne path per case in a Switch, Sequence, or Intent pattern.

Every graph needs at least one starting point: an entry, a hook, or an Intent pattern. A cluster of data nodes with no control path never runs.


Data portsโ€‹

Value kindTypical sourcesTypical consumers
UnitSelf Unit, Opponent Unit, entry Source / Target, Pick ElementDeal Damage, Change HP, Unit Info, Flash Unit
Unit collectionAll Enemies, All Units, Filter, Random ElementsDeal Damage, Foreach, Count
CardThis Card, card entries, Pick ElementCard Info, Play Card, Transform Card
Card collectionCard Piles, Deck Cards, Filter, TakeMove Cards, Upgrade Cards, Remove Cards
StatusStatus entries, Unit Info.Statuses plus Pick ElementReapply Status, Remove Statuses
NumberEntry Amount, Player Energy, Unit Info, math nodesAction amounts, Loop.Count, conditions, Write Hook
Yes/noCondition, Logic, Is Empty, hook PlayableBranch, While, Gate.Allow, Write Hook
TextCard Info.Type, Unit Info.Team, Constant, Event ArgCondition comparisons, Switch cases, filters

Wires are type-checked: a number output fits a number input, a whole number fits a decimal slot, and a single unit fits a port that accepts "unit or units". Wiring a collection into a single-target port also works but quietly uses the first entry; when that is what you mean, say it with Pick Element so the graph reads honestly.

Rapid Fire's wiring exercises several of these rows at once: All Enemies feeds Random Elements, the picked unit drives Deal Damage.Target, and the Dealt result carries on into the presentation group.

Data wires in Rapid Fire's graph: All Enemies into Random Elements into Deal Damage's Target port, with the Dealt output wired on into a presentation group


Field value versus connected valueโ€‹

Many nodes carry an editable field that can also become an input port. The port is hidden until you ask for it: click the โ—‹ socket button at the end of the field's row to expose it, and โ—‰ to fold it back into an inline value (the editor workflow shows the gesture). The rule at runtime is simple: a connected port wins, and the field is the fallback when nothing is connected.

SituationBetter choice
The card always deals 8 damage.Set Amount = 8 on Deal Damage.
Damage equals current armor.Wire Unit Info.Armor into Deal Damage.Amount.
The status always applies 2 stacks.Set Amount = 2 on Change Status.
Stacks scale with cards played this turn.Wire Count into Change Status.Amount.
Why the field still matters when a wire is connected

Node inputs resolve through a "pull input, or fall back to the field" rule. When you later delete the wire, or fold the port back to an inline value, the node silently reverts to its field value instead of breaking. Keep the field set to something sensible even on wired nodes; it documents the intended baseline and keeps the graph functional through rewiring.


When values are readโ€‹

Data wires do not push values around. A value is computed at the moment the consuming node runs and asks for it:

Three consequences you will actually feel while authoring:

  • Get nodes read the live battle state at the moment they are pulled. A Unit Info.HP read after a Deal Damage sees the reduced HP, not the value from when the graph started.
  • Within a single node's run, repeated pulls from the same Get node agree with each other. Random follows the same rule: one action pulling a Random twice sees one roll, but the next node in the control path pulls a fresh roll. Two actions wired to the same Random will roll twice.
  • To lock one computed value and reuse it across several nodes, store it with Set Variable, or wire the first action's result output onward instead of re-reading the source.

Operator nodes (Math Binary, Condition, Filter, ...) are pure calculations over their inputs; they recompute every time something pulls them.


Target sourcesโ€‹

Action nodes that touch units or cards carry a source field that answers: which things should this node use when no data wire overrides it?

Source fieldOptionsMeaning
TargetSource (units)Self, Opponent, AllEnemies, AllUnitsFind units by their relationship to the graph's owner.
CardsSource (cards)ThisCard, HandPile, DrawPile, DiscardPile, ExhaustPile, AllPilesRead the current card or a known pile.
Pile (pile scope)Hand, Draw, Discard, Exhaust, Master, AllWhere a card action places, moves, or inspects cards.

These enum fields cover the common cases without any wiring: TargetSource = AllEnemies is a complete AoE target. Reach for selector nodes when the source needs filtering, sorting, or randomness. All Enemies -> Filter -> Random Elements reads as "a random matching enemy", which no single field could express.


Collectionsโ€‹

A collection is a list of units, cards, or statuses, and collection nodes build the target set before an action runs.

NeedNode
Every enemyAll Enemies
Every unitAll Units
Cards from a pileCard Piles
Cards from the run deckDeck Cards
Keep only matching entriesFilter
Count entriesCount
Check for emptiness or membershipIs Empty, Contains
Pick one entryPick Element
Pick several at randomRandom Elements
Combine or subtract listsMerge Collections, Exclude
Reorder or trimSort, Shuffle, Take

An action that accepts a unit collection applies to every unit in it. When each target needs its own follow-up logic or its own presentation beat, run the collection through Foreach instead of passing it whole.


Action resultsโ€‹

Several actions output what they actually did, which can differ from what you asked for once armor, hooks, and caps have their say.

NodeOutputWhy it matters
Deal DamageDealtDamage after armor, hooks, and prevention.
Change HPChangedHP actually changed, after clamping at max or zero.
Change ArmorChangedArmor actually changed.
Change EnergyChangedEnergy actually changed.
Change StatusChangedThe real stack delta.
Draw CardsDrawnThe cards that arrived in hand.
Add CardsAddedThe card instances that were created.
Spawn UnitSpawnedThe new unit, if one was created.

Player-facing feedback should almost always use these outputs. Floating text wired to Dealt shows the 4 that got through the armor, not the 7 the card intended.


Variablesโ€‹

Set Variable stores a named value; Variable reads it back. The node's BattleScope toggle decides the lifetime: off means the value lives for the current run of the graph, on means it persists for the whole battle and any graph can read it.

ScopeUse forAvoid for
Graph run (default)Intermediate values reused within one execution.Anything a direct wire can carry.
Battle scopeCounters and flags that persist across turns and entries.Temporary calculations.

Name variables by gameplay meaning: BonusDamage, CardsCreated, MarkedTarget. A battle-scope variable named Temp will hurt exactly when you least expect it.


Event argumentsโ€‹

Raise Internal Event sends named arguments; the receiving graph reads each one with an Event Arg node after On Internal Event.

Sender bindsReceiver reads with
The attacking unitEvent Arg with the matching argument name
A damage amountEvent Arg, then math or branch nodes
A created cardEvent Arg, then card nodes

Keep argument names stable and specific. Attacker and DamageAmount survive refactors; A and Payload do not.


Custom node portsโ€‹

Custom nodes get ports without referencing any editor code. The short version:

You writeUsers see
A public int / float / bool / string fieldA field with the โ—‹ socket toggle, ready to expose as an input port of the same name.
A UnitSource or CardSource field named <Name>SourceA target field whose port is called <Name>: TargetSource backs a Target port.
INodePorts.DeclarePorts()Explicit named inputs, outputs, control branches, and dropdown ports. On a name clash, the declared port wins over the auto-promoted one.
[FlowNode(category, displayName)]The menu group and node title in Add Node.

Name ports after the gameplay choice (Target, Amount, Duration), not the implementation. The full authoring contract lives in custom nodes and custom ports.


Troubleshooting data flowโ€‹

SymptomLikely causeCheck
The node runs but nothing changes.Empty or wrong target.The source field, the selector output, or the entry outputs.
A branch always takes one side.The condition input is unwired.Branch.Condition and the Condition node's inputs.
Floating text shows the wrong number.It shows the intended amount, not the result.Wire the action's Dealt or Changed output instead.
Two actions fed by one Random show different numbers.Each action re-rolls when it pulls.Store the roll with Set Variable, or reuse the first action's result output.
A collection action hits too many targets.The selector is too broad.Add Filter, Take, or Pick Element.
A hook never changes the value.Write Hook missing or wrong kind.Kind = Int for numeric hooks, Kind = Bool for playability.