Starting a Battle
Use Bootstrap when a scene always runs the same encounter, or call GCSApi.StartBattle when a map, quest, or level flow selects the encounter dynamically. Both entry points share the same battle lifecycle
Decide where the Encounter comes from
- One encounter per scene
- Map or flow control
When the same battle should start every time Play Mode begins, save the encounter in Default Encounter on GameCardEncounterBootstrap. This suits Demo scenes, isolated battle tests, and scenes loaded specifically for one encounter.
When a map node, quest state, level branch, or match result selects an encounter dynamically, disable automatic Bootstrap startup and call GCSApi.StartBattle(encounter) from the project flow.
Let Bootstrap handle fixed scenes
Select the Encounter to run in Default Encounter on GameCardEncounterBootstrap, leave Auto Start On Play enabled, and confirm that the Inspector shows Ready — starts on Play at the top.

When Play Mode begins, Bootstrap starts the battle through the direct reference in Default Encounter. Ready validates Editor discovery and scene configuration only. At runtime, it does not check again whether that Encounter Database is Active. The direct-reference chain continues from GameEncounter to the player, enemies, starting deck, and cards, so these ScriptableObject references remain valid even if their databases are Inactive.
Active controls the discovery scope for Game Card Editor, selectors, and GCSApi queries and participates in some runtime ID resolution. Databases and Content Discovery defines the exact boundaries.
GameCardEncounterBootstrap must be on the same GameObject as GameCardManager. When added, Unity supplies a missing Manager automatically. In addition to storing a fixed Encounter, Bootstrap can invoke UnityEvents or GES events after StartBattle returns.
| Field | Default | Purpose |
|---|---|---|
Default Encounter | Empty | Encounter passed to GameCardManager.StartBattle when Play Mode begins |
Auto Start On Play | Enabled | Starts Default Encounter during the component's Start phase |
Use GES Events | Disabled | After GES is installed, selects between UnityEvent and GES event lists for Bootstrap output |
On Bootstrap | Empty | Invoked after StartBattle returns when GES is not used |
On Bootstrap Events | Empty | Raised in list order after StartBattle returns when GES is used |
If Default Encounter is empty or no valid Manager is available, Bootstrap records a warning and does not create a battle.
These outputs belong to the Bootstrap component, not the shared battle lifecycle. Encounters started directly from code do not trigger them.
See GES Integration to connect a fixed entry point to GES.
Call GCSApi.StartBattle from a dynamic flow
Once a map or level flow selects the Encounter at runtime, project code also owns startup. Keep GameCardManager in the scene, disable Auto Start On Play, and store the current activeEncounter in the flow coordinator.
private GameEncounter activeEncounter;
public void StartEncounter(GameEncounter encounter)
{
if (encounter == null || activeEncounter != null || !GCSApi.IsReady) return;
activeEncounter = encounter;
GCSApi.StartBattle(encounter);
}
GCSApi.IsReady means only that the scene contains a valid Manager. If the same Manager receives another StartBattle command, it clears the current state machine immediately and creates a new battle. The old battle does not raise OnBattleEnded first. If a map flow stores its own activeEncounter, it must reject another startup while that value is not null. Checking only GCSApi.IsBattleActive is not sufficient. During the reward wait after victory, battle progression has stopped, but the battle is not fully complete because OnBattleEnded has not yet returned the final result to the project flow. Release activeEncounter only when OnBattleEnded is received, and restore the map at the same handoff point after the data transfer is complete.
Both entry points converge in Manager
Bootstrap and GCSApi both pass the selected GameEncounter to GameCardManager.StartBattle. From that call onward, the map, HUD, results UI, and save system need only listen to the GCS lifecycle events. They do not need to know which entry point started the battle.
| Event | Arguments | Timing |
|---|---|---|
OnBattleStarted | None | The player, enemies, initial library, and opening hand have been created and TurnNumber is 1. The runtime then resolves enemy intents, handles battle-start statuses, and enters the first player turn |
OnBattleRewardOffered | BattleRewardOfferedEventArgs | Victory is confirmed and reward candidates have been generated. The battle waits for a selection or Skip |
OnBattleRewardSelected | BattleRewardSelectedEventArgs | The player has confirmed a reward and its result has been applied to the Run |
OnBattleRewardSkipped | None | The player has skipped this battle's reward |
OnBattleEnded | BattleEndedEventArgs | On defeat, raised after battle-end state handling. On victory, raised after a reward is selected or skipped. Won carries the final result |
Follow these lifecycle and call details to keep the integration correct and memory-safe:
-
OnBattleStartedis raised synchronously insideStartBattle. Any object that needs the startup notification for this battle must subscribe before the call. -
GCSApi.OnBattleStarted,GCSApi.OnBattleEnded, and the generalGCSApi.Subscribeall returnIDisposable. Dispose the corresponding handle when the listener is disabled. -
GCS raises
OnCardDrawnfor every opening-hand card beforeOnBattleStarted. Objects that animate or process opening cards individually must also subscribe beforeStartBattleis called. -
OnBattleRewardOfferedonly publishes candidates and pauses the victory flow. The reward UI or project logic must callGCSApi.ApplyReward(choice, pickedCard)orGCSApi.SkipReward(). Otherwise, the battle never raises its victory result throughOnBattleEnded. -
Reward UI can submit only once while
GCSApi.IsWaitingForRewardistrue. Disable selection and Skip buttons immediately after the first click. -
RewardChoice.Cardrequires a valid card from theOnBattleRewardOfferedcandidates. Repeated calls toApplyRewardcan modify Run state again but do not raise reward-selection or battle-end events again. -
After the entry points converge, the defeat path ends immediately, while the victory path waits for a reward choice. The two paths converge again at
OnBattleEndedonly after the reward is applied or skipped.
The diagram above brings both startup paths to the same OnBattleEnded return point. Project code can confirm that the battle flow has ended and read its final result only after receiving this event. Bootstrap's On Bootstrap and GES lists are not part of this lifecycle. They run only after StartBattle returns. Maps and long-lived flows should use GCS lifecycle events instead. See the Event Guide and Event Reference for the remaining phase, card, unit, and FlowGraph events.
Connect the map and battle with a coordinator
The map coordinator switches to battle presentation before calling StartBattle and restores the map after receiving OnBattleEnded. It writes activeEncounter at startup and clears it at completion, preventing another battle from starting before the current one ends.
using System;
using TinyGiants.GCS.Runtime;
using UnityEngine;
public sealed class MapEncounterFlow : MonoBehaviour
{
[SerializeField] private GameObject mapRoot;
[SerializeField] private GameObject battleRoot;
public event Action<GameEncounter> EncounterStarted;
public event Action<GameEncounter, bool> EncounterFinished;
private IDisposable battleStartedSubscription;
private IDisposable battleEndedSubscription;
private GameEncounter activeEncounter;
private void OnEnable()
{
battleStartedSubscription = GCSApi.OnBattleStarted(HandleBattleStarted);
battleEndedSubscription = GCSApi.OnBattleEnded(HandleBattleEnded);
}
private void OnDisable()
{
battleStartedSubscription?.Dispose();
battleStartedSubscription = null;
battleEndedSubscription?.Dispose();
battleEndedSubscription = null;
}
public void StartMapEncounter(GameEncounter encounter)
{
if (encounter == null || activeEncounter != null || !GCSApi.IsReady) return;
activeEncounter = encounter;
battleRoot.SetActive(true);
mapRoot.SetActive(false);
GCSApi.StartBattle(encounter);
}
private void HandleBattleStarted()
{
if (activeEncounter != null) EncounterStarted?.Invoke(activeEncounter);
}
private void HandleBattleEnded(BattleEndedEventArgs args)
{
if (activeEncounter == null) return;
var finishedEncounter = activeEncounter;
activeEncounter = null;
battleRoot.SetActive(false);
mapRoot.SetActive(true);
EncounterFinished?.Invoke(finishedEncounter, args.Won);
}
}
Place the coordinator and GameCardManager on objects that remain active throughout the switch. Use MapRoot and BattleRoot as child objects controlled by the coordinator. StartMapEncounter activates BattleRoot before calling StartBattle, allowing HUD and presentation components that subscribe in OnEnable to receive the synchronously raised startup and opening-hand draw events.
If the victory or defeat presentation must continue using the battle view, wait for it to finish inside HandleBattleEnded before switching the root objects. BattleEndedEventArgs still carries the result to map progression and the save system.
See Custom UI Integration for the exact connection between presentation and battle state.
Initialize the Master Deck before a multi-battle Run
If the map starts another battle after returning, activeEncounter is cleared when the current battle ends, but the Run-level Master Deck must persist between battles. When the Master Deck is empty, GCS builds the current battle library temporarily from the current Encounter player's StartingDeck, but does not write that fallback into the Master Deck. If the first victory then adds only one reward card, the Master Deck contains only that reward, and the next battle does not fall back to the starting deck again. At the beginning of a new Run, clear the previous Run state, then write every card from the starting deck into the Master Deck.
using System.Collections.Generic;
using TinyGiants.GCS.Runtime;
using UnityEngine;
public sealed class RunDeckInitializer : MonoBehaviour
{
[SerializeField] private GameDeck startingDeck;
public void BeginNewRun()
{
if (startingDeck == null || startingDeck.Entries == null || !GCSApi.IsReady) return;
var masterDeck = new List<MasterDeckEntry>();
foreach (var entry in startingDeck.Entries)
{
if (entry?.Card == null) continue;
for (var copy = 0; copy < entry.Copies; copy++)
{
masterDeck.Add(new MasterDeckEntry
{
CardGuid = entry.Card.Guid
});
}
}
GCSApi.ClearMasterDeck();
GCSApi.SetMasterDeck(masterDeck);
}
}
The Master Deck stores card GUIDs. When a battle starts, GCS resolves those GUIDs only from the current Encounter player's StartingDeck and the Encounter RewardDeck. Entries not found in either deck are skipped. Every Encounter in a Run must therefore include all card assets that can appear in the Master Deck in its StartingDeck or RewardDeck.
GCSApi.ClearMasterDeck clears both the old Master Deck and the Run energy bonus. Call it only when starting a new Run.
Do not clear the Master Deck between later encounters. It preserves the deck after rewards, upgrades, removals, and cost changes, while still following the card-resolution scope above when a battle starts.
Continue with Hand and Card Piles and the GCSApi Guide for the complete Run deck APIs.
Dispose the battle explicitly when leaving early
Normal battles hand control back through OnBattleEnded. When the player leaves an unfinished battle, call GCSApi.DisposeBattle() to clear the current battle state. This API does not raise OnBattleEnded and does not clear the Master Deck or Run energy bonus. The project flow must restore the map, determine the outcome, and save progress itself.
Do not start a new battle as a substitute for disposal. A new StartBattle call clears the old state machine but does not raise the old battle's ending event.
Let the project flow restore the map, record the result, and clear activeEncounter before starting another battle. This keeps the map and battle states aligned.
Checklist before entering Play Mode
- The scene contains only one enabled
GameCardManager, andGCSApi.IsReadyreturnstrue. - A fixed scene has a
Default Encounterselected withAuto Start On Playenabled. - A dynamic flow has
Auto Start On Playdisabled, storesactiveEncounterin project code, and callsGCSApi.StartBattleitself. - Objects that need
OnBattleStartedor opening-handOnCardDrawnhave subscribed before startup and dispose every subscription handle when disabled. - A multi-battle Run initializes its Master Deck before the first battle, and every Encounter can resolve its cards through
StartingDeckorRewardDeck. - The victory path calls
GCSApi.ApplyRewardorGCSApi.SkipRewardonly once, disables reward buttons after the first click, then waits forOnBattleEnded. - The disposal path restores the map, records the result, and clears
activeEncounterexplicitly.
After connecting the startup entry, lifecycle, and Run state, continue to Battle Scene Setup and assemble the Manager, battlefield, hand, HUD, and presentation objects into a playable battle scene.