跳到主要内容

端口与数据流

GCS · FlowGraph Authoring

端口承载两件事:执行顺序和值。本页说明值的这一侧:目标来源、集合、动作结果、变量,以及值的读取时机。

当你的图不再是一条直线时读这一页。理解这些规则时,优先使用玩法概念,而不是 C# 类型。

图里的每根线不是控制线就是数据线。控制线在编辑器工作流里讲过;这一页讲数据这一侧。用玩法概念思考:单位、卡牌、状态、数字、是/否、文本、集合。

Rapid Fire 卡牌图,数据线从 All Enemies 经 Random Elements 连进 Deal Damage 的 Target 端口

Rapid Fire 是个紧凑的数据流示例:All Enemies 产出一个单位集合,Random Elements 把它收窄到一个单位,这个单位在每次循环迭代时流进 Deal Damage.Target。目标每次迭代重新拉取,所以四次攻击可能落在不同的敌人身上。


控制端口

控制端口决定接下来运行什么。

端口形状含义
单个输出继续到下一个节点
True / False按是/否条件分支
Body / Done每次迭代运行 Body,循环结束后从 Done 继续。LoopForeach 还输出 Index 数据端口
OnChosen / Skipped玩家做出选择后继续,或玩家放弃后继续
编号或命名输出SwitchSequence 或 Intent 模式里每个分支一条路径

每张图至少需要一个起点:入口、Hook 或 Intent 模式。没有控制路径的一堆数据节点永远不会运行。


数据端口

值类型常见来源常见消费者
单位Self UnitOpponent Unit、入口 Source / TargetPick ElementDeal DamageChange HPUnit InfoFlash Unit
单位集合All EnemiesAll UnitsFilterRandom ElementsDeal DamageForeachCount
卡牌This Card、卡牌入口、Pick ElementCard InfoPlay CardTransform Card
卡牌集合Card PilesDeck CardsFilterTakeMove CardsUpgrade CardsRemove Cards
状态状态入口、Unit Info.StatusesPick ElementReapply StatusRemove Statuses
数字入口 AmountPlayer EnergyUnit Info、数学节点Action 的数值、Loop.Count、条件、Write Hook
是/否ConditionLogicIs Empty、Hook 的 PlayableBranchWhileGate.AllowWrite Hook
文本Card Info.TypeUnit Info.TeamConstantEvent ArgCondition 比较、Switch 分支、过滤器

连线有类型检查:数字输出接数字输入,整数可以进小数槽,单个单位可以进"单位或单位集合"端口。把集合接进单目标端口也可以运行,但系统会取第一个元素;如果这是设计意图,建议显式使用 Pick Element,让图的含义更清楚。

Rapid Fire 的连线一次用上了表里的好几行:All Enemies 传给 Random Elements,选出的单位驱动 Deal Damage.TargetDealt 结果再接进表现分组。

Rapid Fire 图中的数据线:All Enemies 经 Random Elements 连进 Deal Damage 的 Target 端口,Dealt 输出接进表现分组


字段值与连接值

很多节点的可编辑字段也能变成输入端口。端口默认是藏起来的:点字段行末的 ○ 插槽按钮把它暴露出来,点 ◉ 折回内联值(操作细节见编辑器工作流)。运行时的规则很简单:端口接了线就以连接值为准,字段是没有连线时的 fallback。

情况更好的选择
这张卡总是造成 8 点伤害Deal Damage 上设 Amount = 8
伤害等于当前护甲Unit Info.Armor 连进 Deal Damage.Amount
这个状态总是施加 2 层Change Status 上设 Amount = 2
层数随本回合打出的牌数变化Count 连进 Change Status.Amount
为什么连了线字段仍然有意义

节点输入按"先拉取输入,拉不到就回退到字段"解析。之后删掉连线,或把端口折回内联值时,节点会回退到字段值。因此,即使节点已连线,也应把字段设成合理的默认值:它记录了设计基线,也让图在重新连线的过程中保持可用。


值在什么时候被读取

数据线不会主动推送值。值在消费节点运行并请求输入时才被计算:

这会带来三个实际影响:

  • Get 节点在被拉取的那一刻读取实时战斗状态。Deal Damage 之后再读 Unit Info.HP,看到的是扣完之后的 HP,不是图启动时的值。
  • 在同一个节点的一次运行内,对同一个 Get 节点的多次拉取结果一致。Random 也遵守这条:一个动作拉两次 Random 只看到一次掷骰,但控制路径上的下一个节点再拉就是新的一骰。两个动作连着同一个 Random,就是两次掷骰。
  • 想锁定一个算好的值给多个节点复用,用 Set Variable 存下来,或者把第一个动作的结果输出接下去,避免重复读取来源。

Operator 节点(Math BinaryConditionFilter……)是对输入的纯计算,每次被拉取都重新算。


目标来源

会作用到单位或卡牌的 Action 节点都带一个来源字段,它回答的是:没有数据线覆盖时,这个节点该对哪些目标生效?

来源字段选项含义
TargetSource(单位)SelfOpponentAllEnemiesAllUnits按与图拥有者的关系查找单位
CardsSource(卡牌)ThisCardHandPileDrawPileDiscardPileExhaustPileAllPiles读取当前卡牌或某个已知牌堆
Pile(牌堆范围)HandDrawDiscardExhaustMasterAll卡牌动作在哪个牌堆放置、移动或检查卡牌

这些枚举字段不需要连线就能覆盖常见情形:TargetSource = AllEnemies 本身就是完整的 AoE 目标。来源需要过滤、排序或随机时才用选择器节点。All Enemies -> Filter -> Random Elements 表示"一个符合条件的随机敌人",单个字段表达不了这个。


集合

集合是一组单位、卡牌或状态,集合节点在动作运行前构建目标集。

需求节点
所有敌人All Enemies
所有单位All Units
某个牌堆的卡Card Piles
本局牌组的卡Deck Cards
只保留匹配项Filter
统计数量Count
判空或判包含Is EmptyContains
取一个Pick Element
随机取几个Random Elements
合并或做差Merge CollectionsExclude
重排或截断SortShuffleTake

接受单位集合的动作会作用到集合里的每个单位。当每个目标需要各自的后续逻辑或各自的表现节奏时,让集合经过 Foreach,不要直接整体传入。


动作结果

有些动作会输出实际结果,这和请求的数值可能不同:护甲、Hook 和上限都会参与结算。

节点输出为什么重要
Deal DamageDealt经过护甲、Hook 和阻止之后的伤害
Change HPChanged实际变化的 HP,已按上限和归零截断
Change ArmorChanged实际变化的护甲
Change EnergyChanged实际变化的能量
Change StatusChanged真实的层数增减
Draw CardsDrawn实际进入手牌的卡
Add CardsAdded被创建出来的卡牌实例
Spawn UnitSpawned新单位(如果创建成功)

面向玩家的反馈通常应该使用这些输出。浮动文字连接 Dealt,显示的才是穿过护甲后的 4 点,而不是卡牌原本请求的 7 点。


变量

Set Variable 存一个命名值,Variable 把它读回来。节点上的 BattleScope 开关决定寿命:关闭时值只活在这一次图执行里,打开后整场战斗有效,任何图都能读。

作用域用于避免用于
图执行(默认)一次执行内复用的中间值一根线就能传的值
战斗作用域跨回合、跨入口持续的计数和标记临时计算

变量按玩法含义命名:BonusDamageCardsCreatedMarkedTarget。不要把战斗作用域变量命名为 Temp 这类含义模糊的名字,否则后续很难判断它的用途和生命周期。


事件参数

Raise Internal Event 发送命名参数;接收方在 On Internal Event 之后用 Event Arg 节点逐个读取。

发送方绑定接收方读取方式
攻击单位同名参数的 Event Arg
伤害数值Event Arg,再接数学或分支节点
新创建的卡Event Arg,再接卡牌节点

参数名保持稳定、具体。AttackerDamageAmountAPayload 更容易在重构后继续维护。


自定义节点端口

自定义节点不引用任何编辑器代码就能获得端口。简版对照:

你写的用户看到的
public 的 int / float / bool / string 字段一个带 ○ 插槽开关的字段,可随时暴露成同名输入端口
名为 <Name>SourceUnitSourceCardSource 字段一个目标字段,端口名叫 <Name>TargetSource 对应 Target 端口
INodePorts.DeclarePorts()显式命名的输入、输出、控制分支和下拉端口。名字撞车时,显式声明的端口压过自动提升的
[FlowNode(category, displayName)]Add Node 里的菜单组和节点标题

端口按玩法选择命名(TargetAmountDuration),不要暴露实现。完整的创作契约见自定义节点自定义端口


数据流排障

症状可能原因检查
节点跑了但什么都没变目标为空或选错来源字段、选择器输出、入口输出
分支永远走同一边条件输入没接Branch.ConditionCondition 节点的输入
浮动文字显示错误数字显示的是预期值而非结果改连动作的 DealtChanged 输出
两个动作连同一个 Random 却拿到不同数字每个动作拉取时都重新掷骰Set Variable 存下骰点,或复用第一个动作的结果输出
集合动作命中过多目标选择器范围太宽FilterTakePick Element
Hook 从不改变值Write Hook 或 Kind 错误数值 Hook 用 Kind = Int,可打出性用 Kind = Bool