Skip to main content

Event guide

Guide

Subscribe to battle changes through typed shortcuts or event names, refresh UI in the correct lifecycle, and release every listener

The event model

The event layer has four parts

PieceTypeRole
Event namesGCSEventNamesStable string constants for the 43 built-in events
PayloadsClasses in GCSEventArgs.csTyped data delivered with each event
ChannelIGCSEventChannelEvent publishing and subscription interface
Unified entryGCSApiTyped shortcuts and general subscription methods; project code starts here

An event tells you that something changed, and roughly what; it is not the source of truth; use the payload to identify the changed object, then read its current values from GCSApi

The diagram above shows the complete path from battle resolution to interface refresh. The Channel dispatches events, GCSApi provides typed and named subscription entries, and callbacks read live state again after receiving a notification. This keeps the Payload from becoming a long-lived copy of battle state

Subscribe with shortcuts

Every built-in event has a GCSApi shortcut whose signature determines the event name and Payload type

private IDisposable _energySubscription;

void OnEnable()
{
_energySubscription = GCSApi.OnEnergyChanged(args =>
{
energyLabel.text = $"{args.CurrentEnergy}/{args.MaxEnergy}";
});
}

void OnDisable()
{
_energySubscription?.Dispose();
}

Each shortcut returns an IDisposable. Store the handle and dispose it when the listening object is disabled or destroyed

Subscribe by event name

Subscribe<TArgs> accepts an event name and a typed callback. Use it for dynamically constructed subscriptions or custom events

private IDisposable _damageSubscription;

void OnEnable()
{
_damageSubscription = GCSApi.Subscribe<DamageEventArgs>(
GCSEventNames.OnDamageDealt,
args => AddCombatLog(args.SourceUnitId, args.TargetUnitId, args.Amount));
}

Use the parameterless overload for events without a Payload

private IDisposable _shuffleSubscription;

void OnEnable()
{
_shuffleSubscription = GCSApi.Subscribe(
GCSEventNames.OnDrawPileShuffled,
RefreshDrawPileWidget);
}

The bundled Demo uses the OnEnemyUnitActing shortcut to play the attacker's lunge toward the player

_actingSubscription = GCSApi.OnEnemyUnitActing(
args => PlayAttackWindup(args.UnitId, args.Intent));
Name and payload type must both match

The Channel dispatches on both event name and argument type. A callback with the wrong TArgs does not run. Subscribing to one event name with two different argument types writes a [GCSEvents] error to the Console and ignores the second subscription. When the type is uncertain, use a shortcut or check the event reference

Pick the right event

UI or system needGood events
Refresh the full battle HUDOnBattleStarted, OnTurnStarted, OnTurnEnded, OnEnemyTurnStarted, OnBattleEnded
Refresh energyOnEnergyChanged, OnEnergyGained, OnEnergySpent
Refresh hand cardsOnCardDrawn, OnCardDiscarded, OnCardExhausted, OnCardRetained, OnCardAddedToHand, OnCardModified
Refresh unit HP and armorOnUnitHpChanged, OnArmorGained, OnArmorLost, OnUnitDied
Refresh status widgetsOnStatusChanged, OnStatusTicked, OnStatusInflicted, OnStatusExpired, OnStatusRemoved
Refresh enemy intentsOnEnemyUnitIntentChanged, OnEnemyPhaseChanged
Play attack wind-ups on acting enemiesOnEnemyUnitActing
Show choicesOnEffectChoiceOffered, OnEffectChoiceSelected, OnEffectChoiceSkipped
Show rewardsOnBattleRewardOffered, OnBattleRewardSelected, OnBattleRewardSkipped
Log combat detailsOnCardPlayed, OnDamageDealt, OnDamageTaken, OnStatusInflicted, OnUnitDied

Cases that use only the Payload

One-time presentation can use the change data in the Payload directly

EventPayload-only use
OnDamageDealtSpawn a damage number or a combat log row
OnDamageTakenShow a hit reaction on the target
OnArmorGainedShow an armor gain number
OnEnergyGainedShow an energy pulse
OnEffectChoiceOfferedRender the choice candidates from the payload
OnBattleRewardOfferedRender the reward cards from the payload
tip

After the one-time presentation, still refresh state from GCSApi so that floating text and the health bar settle on the same result

Subscription lifetime

Subscription and disposal must appear as a pair. Suitable lifecycles include:

  • OnEnable / OnDisable

  • View Model Initialize / Dispose

  • Scene Controller Setup and Teardown

Do not create undisposed subscriptions in these repeated paths:

  • Update

  • every button render

  • every hand rebuild that never disposes the previous subscriptions

tip

When the same callback runs multiple times for one event, an earlier subscription was usually not disposed

Events without a payload

Four built-in events have no Payload:

  • OnBattleStarted

  • OnBattleRewardSkipped

  • OnEffectChoiceSkipped

  • OnDrawPileShuffled

Subscribe to these with a plain Action:

_sub = GCSApi.OnDrawPileShuffled(RefreshPiles);

Custom named events

GCSApi.RaiseGameEvent raises an event under any name you choose, with an optional argument dictionary; the FlowGraph node Raise Internal Event does the same thing from inside a graph

GCSApi.RaiseGameEvent("PlayerMarkedTarget", new Dictionary<string, object>
{
["TargetId"] = target.UnitId
});

Subscription shape must match raise shape. A raise with an argument dictionary arrives typed as IReadOnlyDictionary<string, object>; a raise without arguments arrives on the parameterless path:

_sub = GCSApi.Subscribe<IReadOnlyDictionary<string, object>>(
"PlayerMarkedTarget",
args => HighlightUnit((int)args["TargetId"]));

Custom events also trigger a matching On Internal Event entry in Card, Status, and Enemy Behaviors, allowing a business event defined by project code to enter an existing FlowGraph directly

tip

Add a custom name only when no built-in event describes the business timing accurately. Publishing an existing state event again creates two subscription entries with the same meaning, increasing maintenance and debugging cost

Inspect the event channel in the Monitor

The Event tab in Game Card Monitor shows the live channel. GCS Channel lists each broadcast in order, while Dispatcher Probes shows the subscriber count for every dispatch. Together, they distinguish an event that never fired from one that fired with subs 0

Game Card Monitor Event tab with the chronological GCS Channel log and Dispatcher Probes showing subscriber counts

The image above places readable event details beside the low-level dispatch record. Confirm that the target event entered GCS Channel, then inspect the subscriber count on its probe to determine whether the problem is the gameplay timing or subscription lifecycle

tip

When a project also uses Game Event System, it can forward battle timings to GES or start FlowGraph entries from GES events. See GES integration for that setup. For behavior contained entirely within GCS, continue using GCSApi.Subscribe and GCSApi.RaiseGameEvent

Troubleshooting

SymptomCheck
Handler fires two or more times per eventYou resubscribed without disposing the previous handle
Handler never firesEvent name and payload type must both match; see the Event Reference; also look for a [GCSEvents] argument-type error in the Console
UI shows stale valuesRefresh from GCSApi after the event instead of caching payload values
Card buttons stay enabled during a choiceGate them on GCSApi.IsWaitingForChoice
Reward UI never closesFinish the flow with GCSApi.ApplyReward or GCSApi.SkipReward
GES listeners receive nothingThe bridge must be installed and the event picked on the node; see GES integration