GCSApi 指南
一个覆盖战斗运行时的静态门面:GCSApi 负责启动战斗、打出卡牌、读取实时状态、安全修改状态,并提供事件订阅。
面向在 GCS 之上构建自己的 UI 或场景流程、不使用 demo 表现层的开发者。GCSApi 是 Game Card System 的公开运行时入口。外部代码需要的一切都从这里走:启动战斗、打出卡牌、读取实时状态、在不绕过 Hook 的前提下修改状态、枚举内容数据库、维护 master deck、订阅事件,以及发现扩展注册表。这个类位于 Runtime/GCS.API.cs,它的 XML 文档就是完整参考,你在 IntelliSense 里看到的和本页讲的是同一份内容。
本页代码片段是组件节选,不是完整文件。把它们放进你自己的 MonoBehaviour、presenter 或 service class,并按需要添加 using System;、using System.Collections.Generic; 和 using TinyGiants.GCS.Runtime;。
调用前先做保护
场景中存在 GameCardManager 后,GCSApi.IsReady 为 true。大多数调用在没有 manager 时也是安全的:它们返回空列表、false、0 或 null,不会抛异常。即便如此,UI 代码仍应保护那些只在活动战斗中才有意义的路径。
if (!GCSApi.IsReady)
{
return;
}
if (!GCSApi.IsBattleActive)
{
GCSApi.StartBattle(encounter);
}
启动战斗
StartBattle 从一个 GameEncounter 资产创建实时战斗。已有战斗会先被销毁,起手手牌在调用返回前就已抽好,OnBattleStarted 也已触发,第一回合的敌人意图同样已解析完毕。
public GameEncounter Encounter;
public void Begin()
{
GCSApi.StartBattle(Encounter);
}
场景里带着 demo 表现层时,从一个 encounter 资产到下面这个画面,就只差这一次调用:

战斗建立后,用事件驱动 UI,不要轮询:
private IDisposable _battleStarted;
void OnEnable()
{
_battleStarted = GCSApi.OnBattleStarted(RefreshAll);
}
void OnDisable()
{
_battleStarted?.Dispose();
}
打出卡牌
读取手牌,检查可打出性,然后调用 TryPlayCard。
public void TryPlayFirstCard(UnitState target)
{
foreach (var card in GCSApi.Hand())
{
if (!GCSApi.CanPlayCard(card)) continue;
GCSApi.TryPlayCard(card, target);
return;
}
}
不是每张卡都需要玩家选目标。RequiresTarget 只对"指定单个敌方单位"的卡返回 true;对自身、全体敌人和无目标的卡,引擎会自动解析目标,直接打出即可,不需要弹出选择器。
var activeCard = GCSApi.GetActiveCard(card);
if (GCSApi.RequiresTarget(activeCard))
{
ShowTargetPicker(card);
}
else
{
GCSApi.TryPlayCard(card);
}
出牌被拒绝时 TryPlayCard 返回 false:没有活动战斗、卡不在手牌里、能量不够、卡带有 Unplayable 关键字,或某个出牌 Hook 取消了这次操作。
结束玩家回合
public void EndTurnButton()
{
if (GCSApi.Phase == BattlePhase.PlayerPhase)
{
GCSApi.EndPlayerTurn();
}
}
EndPlayerTurn 会推进到敌人阶段,在 PlayerPhase 之外不做任何事,所以残留按钮上的误点不会打乱回合顺序。
为 UI 读取状态
常见的 UI 读取都有命名清晰的 helper:
turnLabel.text = GCSApi.TurnNumber.ToString();
energyLabel.text = $"{GCSApi.PlayerEnergy}/{GCSApi.MaxEnergy}";
var player = GCSApi.Player();
hpLabel.text = $"{GCSApi.Hp(player)}/{GCSApi.MaxHp(player)}";
foreach (var enemy in GCSApi.AliveEnemies())
{
RenderEnemy(enemy, GCSApi.CurrentIntentTag(enemy), GCSApi.CurrentIntentValue(enemy));
}
事件触发后,从 API 当前状态刷新,不要信任缓存副本。运行时掌握事实,UI 只是它的投影。
安全修改战斗状态
外部系统通过 GCSApi helper 修改状态。这些调用会进入运行时 controller,所以 Hook 照常执行,事件照常触发。
var player = GCSApi.Player();
GCSApi.GainArmor(player, 5);
GCSApi.DrawCards(1);
GCSApi.ApplyStatus(player, shieldedStatus, 2, player);
不要自己写 UnitState.CurrentHp、UnitState.Statuses 或各个牌堆列表。直接写入会跳过 Hook 和事件,状态不再响应,UI 也永远收不到这次变化。
胜负在阶段转换和出牌之后判定,不在每次 HP 写入时判定。用修改 helper 把敌人打到 0 HP,会在下一次判定点生效,不会当场结束战斗。
订阅事件
内置事件有快捷方法,全部返回 IDisposable handle:
private readonly List<IDisposable> _subs = new();
void OnEnable()
{
_subs.Add(GCSApi.OnEnergyChanged(_ => RefreshEnergy()));
_subs.Add(GCSApi.OnUnitHpChanged(_ => RefreshUnits()));
_subs.Add(GCSApi.OnCardDrawn(_ => RefreshHand()));
}
void OnDisable()
{
foreach (var sub in _subs) sub.Dispose();
_subs.Clear();
}
自定义事件名,或想直接操作 channel 时,用 Subscribe<TArgs>:
_subs.Add(GCSApi.Subscribe<DamageEventArgs>(
GCSEventNames.OnDamageDealt,
args => LogDamage(args.SourceUnitId, args.TargetUnitId, args.Amount)));
无参数事件用 Subscribe(string, Action)。所有内置事件名都是 GCSEventNames 上的常量,payload 类型在 GCSEventArgs 中。经 RaiseGameEvent 触发的自定义事件,payload 以 IReadOnlyDictionary<string, object> 送达,订阅时 TArgs 就用它。
内容发现
GCSApi 可以枚举当前活动数据库和其中的内容实体。
foreach (var card in GCSApi.Cards())
{
AddCardToBrowser(card);
}
var poison = GCSApi.FindStatus(poisonGuid);
枚举方法每次构建新列表,可能产生分配。每帧都刷新的菜单应缓存结果。
维护 master deck
master deck 是 run 层级的牌组,后续战斗都从它构建。StartBattle 会用它填充抽牌堆。
GCSApi.ClearMasterDeck();
GCSApi.AddCardToMasterDeck(strike);
GCSApi.AddCardToMasterDeck(defend, isUpgraded: true);
GCSApi.StartBattle(encounter);
修改 master deck 不会改写已经开始的战斗里的运行时牌堆。
扩展注册表
做工具或调试面板时,四个反射注册表都有对应 helper:
var nodes = GCSApi.NodeTypes();
var cardTypes = GCSApi.CardTypes();
var tags = GCSApi.CardTags();
var tokens = GCSApi.DescriptionTokens();
结果包含从所有引用了 TinyGiants.GCS.Runtime 的已加载程序集中发现的自定义节点、卡牌类型、标签和描述 token。
推荐接入模式
自定义战斗界面按这个顺序接入:
- 在场景中放置
GameCardManager并分配数据库 - 用
GCSApi.StartBattle启动战斗 - 在
OnEnable中订阅事件,在OnDisable中释放 handle - 每次事件触发后,从
GCSApi状态刷新 UI - 卡牌按钮走
CanPlayCard、RequiresTarget和TryPlayCard - 玩家决策走
EndPlayerTurn、ApplyReward和SkipReward - 表现层状态和玩法状态分开存放
这样你的 UI 与 demo 解耦,底层复用的仍是同一套战斗引擎。