API 使用指南
通过统一的 GCSApi 启动战斗、提交玩家操作、读取实时状态、安全修改战斗并订阅事件
项目代码通过 TinyGiants.GCS.Runtime 命名空间访问 GCSApi,战斗启动、玩家操作、状态查询、运行时修改与事件订阅都从这个入口完成
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);
}
StartBattle 返回后,起始手牌、玩家能量、敌人阵容和第一组 Intent 都已经可供表现层读取
战斗建立后,使用事件驱动 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);
}
没有活动战斗、卡牌不在手牌、能量不足、卡牌带有 Unplayable 关键词,或者某个出牌 Hook 取消操作时,TryPlayCard 都会返回 false
结束玩家回合
public void EndTurnButton()
{
if (GCSApi.Phase == BattlePhase.PlayerPhase)
{
GCSApi.EndPlayerTurn();
}
}
EndPlayerTurn 会推进到敌人阶段,在 PlayerPhase 之外不做任何事,所以残留按钮上的误点不会打乱回合顺序
读取 UI 所需状态
常见 UI 数据都有对应的具名查询方法
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 的具名方法修改状态,这些调用会进入运行时 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 刷新都不会收到这次变化,请使用 GCSApi 修改方法或 Controller
胜负在阶段转换和出牌之后判定,不在每次 HP 写入时判定,用修改 helper 把敌人打到 0 HP,会在下一次判定点生效,不会当场结束战斗
订阅事件
内置事件都提供快捷订阅方法,并返回用于释放监听的 IDisposable
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();
}
订阅自定义事件名,或者需要直接操作事件通道时,使用 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 不会改写已经开始的战斗牌堆
扩展注册表
工具或调试面板可以通过四组方法读取扩展注册表
var nodes = GCSApi.NodeTypes();
var cardTypes = GCSApi.CardTypes();
var tags = GCSApi.CardTags();
var tokens = GCSApi.DescriptionTokens();
结果包含从所有引用了 TinyGiants.GCS.Runtime 的已加载程序集中发现的自定义节点、卡牌类型、标签和描述 token
完整成员签名和返回类型见 API 参考