跳到主要内容

GCSApi 指南

GCS · Runtime API

一个覆盖战斗运行时的静态门面: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 资产到下面这个画面,就只差这一次调用:

StartBattle 之后的 Hunter demo 战斗:五张起手手牌、3/3 能量,敌人意图图标已经显示

战斗建立后,用事件驱动 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.CurrentHpUnitState.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。


推荐接入模式

自定义战斗界面按这个顺序接入:

  1. 在场景中放置 GameCardManager 并分配数据库
  2. GCSApi.StartBattle 启动战斗
  3. OnEnable 中订阅事件,在 OnDisable 中释放 handle
  4. 每次事件触发后,从 GCSApi 状态刷新 UI
  5. 卡牌按钮走 CanPlayCardRequiresTargetTryPlayCard
  6. 玩家决策走 EndPlayerTurnApplyRewardSkipReward
  7. 表现层状态和玩法状态分开存放

这样你的 UI 与 demo 解耦,底层复用的仍是同一套战斗引擎。