Skip to main content

Sample Walkthroughs

GCS ยท Extending GCS

Three shipped files cover the extension surface end to end: a starter template, a real card effect with a condition, and a pure value node.

For programmers who prefer starting from working code over reading API docs.

GCS ships its custom node samples under:

Assets/TinyGiants/GameCardSystem/Samples~/CustomNodes/

Unity ignores folders whose name ends in ~, so these files ship as reference source and are never compiled in place. Copy the .cs file you want into one of your own runtime assemblies to build it. Each sample is small on purpose and exercises the public surface: [Serializable], [FlowNode], the node base classes, inferred fields, declared ports, and the context helpers.


  1. Copy one sample file out of Samples~ into your own project code.
  2. Put it in a runtime assembly that references TinyGiants.GCS.Runtime.
  3. Change the namespace to your project's.
  4. Rename the class and the [FlowNode] display name.
  5. Replace the fields with the choices your card author should see.
  6. Keep context reads on PullInputOr, ResolveUnits, and ResolveCards.
  7. Keep battle changes inside ctx.Controller.
  8. Let Unity compile, then find the node in the Add Node menu.

Copying first also means a package update never overwrites your game-specific node.


FlowNodeTemplateโ€‹

FlowNodeTemplate.cs is the broad starting point: every required moving part in one file, registered as Action / Your Node Name.

PartPurpose
[Serializable]Lets Unity save node instances inside FlowGraph assets.
[FlowNode("Action", "Your Node Name")]Adds the node to the Add Node menu.
MutatorNodeRuns writable battle logic in Execute.
UnitSource TargetSourceAuto-creates a Target unit input (defaults to Opponent).
int AmountAuto-creates an Amount scalar input.
DamageStyle StyleAn enum configuration row, shown on the node body instead of a port.
INodePortsDeclares the Result output field inference cannot create.
ctx.PullInputOrReads wired values, falling back to the node's fields.
ctx.ResolveUnitsHandles both the target dropdown and a connected target port.
ctx.Controller.DealDamageMutates battle state through the supported gateway.
ctx.StoreResultPublishes the declared Result output for downstream nodes.

Start here when your node will change battle state and may expose an output.

Safe edits:

  • Change the category and display name.
  • Replace TargetSource, Amount, and the enum with your own author-facing fields.
  • Replace the controller call with the battle change your node performs.
  • Rename the result output, as long as no saved content connects to it yet.

Risky edits:

  • Removing [Serializable].
  • Moving the file into an editor-only assembly.
  • Mutating unit, card, or pile fields directly.
  • Renaming fields or outputs after saved graphs already connect to them.

HealIfBelowHalfNodeโ€‹

HealIfBelowHalfNode.cs is the best sample for a normal card effect. It models a user-facing behavior: heal the selected units, optionally only those at or below half HP.

FieldWhat the author sees
TargetSourceWho can be healed. Defaults to Self; can be exposed and wired.
HealAmountHow much HP to restore. A fixed value or a connected input.
OnlyBelowHalfWhether the half-HP condition applies.

How it behaves:

  • HealAmount is read through ctx.PullInputOr(this, nameof(HealAmount), HealAmount).
  • Targets come from ctx.ResolveUnits(this, TargetSource, nameof(TargetSource)).
  • Dead and null units are skipped, and the half-HP check compares each target's current and max HP.
  • Healing goes through ctx.Controller.GainHp.
  • The Healed output publishes the HP actually restored, measured before and after each heal. A target already at full HP contributes 0.

Copy this pattern for project-specific actions such as healing allies under a custom condition, bonus damage against marked enemies, armor scaled by missing HP, or counting how many targets passed a check and feeding that count onward.


AddValuesNodeโ€‹

AddValuesNode.cs is the minimal pure value sample, registered as Operator / Add Values: read two inputs, return their sum.

FieldMeaning
AFirst number, with the field as fallback.
BSecond number, with the field as fallback.

How it behaves:

  • The class subclasses OperatorNode and never calls ctx.Controller.
  • It declares Result with NodePort.ResultOut("Result", PortDataType.Int).
  • It returns the sum from Evaluate(IEvaluationContext ctx, string outPort).

Copy this pattern for math, comparisons, score calculations, converters, and any project formula meant to be wired into another node's input. A node with several outputs declares each one and switches on outPort:

public override object Evaluate(IEvaluationContext ctx, string outPort)
{
int value = ctx.PullInputOr(this, nameof(Value), Value);
return outPort == "Double" ? value * 2 : value;
}

Which sample to start fromโ€‹

You want to createStart from
A state-changing card effectHealIfBelowHalfNode or FlowNodeTemplate
A calculated valueAddValuesNode
A custom selectorAddValuesNode, base class switched to SelectorNode, returning units or cards
A custom branch nodeFlowNodeTemplate, base class switched to ControlNode, with declared ControlOut ports
A presentation beatFlowNodeTemplate, base class switched to VfxNode, implementing Play

Verify the adapted nodeโ€‹

  1. Unity compiles with no C# errors.
  2. The node appears in the Add Node menu under its [FlowNode] category.
  3. Scalar fields show as editable values that can be exposed as ports.
  4. <Name>Source fields expose their matching unit or card inputs.
  5. Declared result outputs are visible for downstream connections.
  6. A test battle shows the expected state changes in the Monitor.

The Monitor's Flow tab is the fastest proof that your node actually ran. Every graph execution is captured with its trigger, node count, and timing, so a played card that reaches your node shows up as a row:

Monitor Flow tab capturing one Poison Arrow card play: trigger OnCardPlayed, ten nodes executed, timing included

If a node compiles but behaves strangely, test it with fixed field values before wiring dynamic ports. Once the fixed version works, expose one port at a time.


Common mistakesโ€‹

SymptomLikely cause
Node missing from Add NodeMissing [FlowNode], an abstract class, or the assembly does not reference TinyGiants.GCS.Runtime.
Connected value is ignoredCode reads the field directly instead of ctx.PullInputOr.
Connected target is ignoredCode skips ctx.ResolveUnits / ctx.ResolveCards.
Output cannot be connectedOutput was never declared with NodePort.ResultOut or NodePort.Out.
Output value is always missingThe StoreResult name does not match the declared output name.
No events or visuals fireCode mutates battle objects directly instead of calling ctx.Controller.