把节点图「读」成代码的四步法
主课给了五条规则,那是「看到代码怎么想」。这一页给的是反方向的操作流程:手边有一张你自己搭的节点图,怎么在四步之内把它变成代码。这套流程不需要你记住任何 Verse 语法——它只要求你会读自己的图。
一、四步总览
先把流程摆出来。每一步只做一件事,做完再做下一步,不要跳:
- 找起点——在图上找到那个红色的事件节点。它是整条执行线的车头,也是你代码的第一行。
- 顺着白线抄成行——从车头沿着白色执行线一路往右,每经过一个节点,就在下面加一行。
- 数据线塞进括号——每个节点身上那些彩色输入引脚上挂的东西,原样写进这一行的圆括号里。
- 分支画成缩进——遇到 Branch、ForEach 这种「岔口」,把从岔口出去的那一段,整体往右缩一格。
四步做完,你手上就是一段结构正确的代码骨架。剩下的只是查一下每个节点在 Verse 里叫什么名字——那是查表的活,不是理解的活。
二、第一步:找到执行线的起点
一张事件图表上可能有好几条互不相干的执行线(BeginPlay 一条、某个按钮的事件一条、Tick 一条)。一次只翻译一条。先用眼睛把这一条从头捋到尾,确认它的车头是谁。
车头决定你代码的第一行长什么样。常见的三种:
| 图上的车头 | 代码里的第一行 |
|---|---|
Event BeginPlay |
OnBegin<override>()<suspends>:void = |
| 某个设备/Actor 上的事件(按钮被按、触发盒被踩) | 一个你自己起名的函数,再在 OnBegin 里把它订阅到那个事件上 |
| 自定义事件(Custom Event)/ 自定义函数 | 一个你自己起名的函数,由别的地方调用它 |
第二种最常见,也最容易卡住新手,值得多说一句。蓝图里你会把一个事件拖到图上,它就自动挂好了;Verse 里没有画布可拖,所以要用一行代码明说「把这个函数挂到那个事件上」,这行代码通常写在 OnBegin 里。这跟你在蓝图里用 Bind Event to … 把一个自定义事件绑到 Event Dispatcher 上是同一回事——只不过在 Verse 里,所有事件都走这条路。
三、第二步:顺着白线,抄成从上到下的行
从车头出发,沿白色执行线走。每经过一个节点,往下写一行;白线走到哪,行就写到哪。这一步几乎不需要思考,它就是把「从左到右」旋转成「从上到下」。
三个提醒:
- 节点在画布上的位置完全不算数。你为了好看把某个节点摆得老远、或者用 Reroute 节点绕了个大弯,这些在代码里全部消失——只有白线经过的先后被保留下来。
- 纯节点(Pure,没有白色引脚的那种)不占一行。它们不在执行线上,而是挂在别人的数据引脚上。它们的归宿是第三步的括号里。
- latent 节点(Delay 这类带时钟的)照样占一行。但它们只能出现在标了
<suspends>的函数里,就像 Delay 只能放在事件图表、不能放进纯函数。
四、第三步:数据线上的东西,写进括号
现在给每一行填料。看这个节点的输入引脚上挂着什么,按引脚从上到下的顺序,写进这一行的圆括号里,逗号隔开。
- 引脚上填的是常数(Duration 里填 3.0)→ 直接写常数:
Sleep(3.0)。 - 引脚上接着一个变量 → 写那个变量的名字。
- 引脚上接着另一个纯节点(比如
Get Player Score)→ 把那个节点的调用整个写进去,变成括号里套括号。这就是为什么代码里会出现A(B(C))这种形状:它对应的正是图上「一串纯节点接力喂给一个引脚」。 - 节点是从某个对象上拖出来的(Target 引脚接着 Door 变量)→ 用点号写在前面:
Gate.Open()。
顺手说一个蓝图有、代码没有的能力:图上你可以把一根数据线拉给好几个节点共用。代码里做不到「一根线分叉」,对应的写法是先把值存进一个名字,再在几个地方引用这个名字——也就是多写一行。这是本页唯一一处「翻译后行数会变多」的地方。
五、第四步:分支画成缩进
白线遇到 Branch 就分叉:True 一条、False 一条。代码里没有分叉,只有缩进——从岔口出去的那一整段,往右缩一格,写在判断那一行的下面。
翻译口诀:「判断那一行原地不动,从它引出去的那一段整体往右缩一格」。ForEach 也一样:循环体是从循环节点引出去的一段,所以整体缩进;循环结束后继续的那部分,回到原来的那一列。
这一步是四步里唯一需要动脑的:你得看清楚,图上那条线到底是「从岔口出去的」还是「岔口之后汇合回来的主干」。汇合回主干的那部分不缩进,和判断那一行对齐。看不清的时候有个笨办法很管用:用手指顺着白线走一遍,每进入一个分支就在心里往右挪一格,每退出来就挪回去。
六、动手练一遍:一次性金库按钮
拿一张真图走一遍完整流程。机制是:金库旁边有个按钮,按一下开门,但只能用一次。你在蓝图里大概会这么搭:
| 图上的东西 | 细节 |
|---|---|
| 变量面板 | Button(按钮引用)、Vault(门引用)、Charges(整数,默认 1) |
| 车头 | 按钮的「被交互」事件——在蓝图里你会用 Bind Event 把它绑到一个自定义事件上 |
| 节点 1 | Branch:条件引脚上接着 Charges > 0 |
| 节点 2(True 分支) | Set Charges,值是 Charges - 1 |
| 节点 3(True 分支) | 调用门上的 Open |
| False 分支 | 什么都不接 |
四步走:① 车头是设备上的事件,所以要写一个自己起名的函数 OnPressed,再在 OnBegin 里把它订阅上去。② 顺着 True 分支的白线,两个节点抄成两行。③ 数据线:Charges - 1 是纯节点的计算,写进赋值那一行;Open 是从 Vault 上拖出来的,写成 Vault.Open()。④ Branch 的 True 分支整体往右缩一格。结果:
using { /Fortnite.com/Devices }
vault_device := class(creative_device):
@editable Button:button_device = button_device{}
@editable Vault:door_device = door_device{}
# 变量面板里的整数变量,默认 1
var Charges:int = 1
OnBegin<override>()<suspends>:void =
# 第一步:把 OnPressed 挂到按钮的事件上(相当于 Bind Event)
Button.InteractedWithEvent.Subscribe(OnPressed)
# 这个函数就是那个「自定义事件」
OnPressed(Agent:agent):void =
# 第四步:Branch 的 True 分支整体往右缩一格
if (Charges > 0):
set Charges -= 1
Vault.Open()
对照一下:图上那个 Branch 变成了 if 那一行;True 分支的两个节点变成了缩进的两行;False 分支什么都不接,所以代码里干脆没有对应的东西——这是代码比图省事的地方之一:空分支不用画。而 set 就是那个 Set 节点,主课的规则 ② 提过它,第 8 课会正式讲。
最后提醒一句常见的翻车点:这里用的是 Subscribe(订阅),不是主课那段代码里的 Await(等待)。区别很实在——Await 是「执行线停在这里,等一次就往下走」,适合一次性的流程;Subscribe 是「挂上去,以后每次都响应」,适合按钮这种要反复触发的。图上分别对应「等待型 latent 节点」和「Bind Event 到自定义事件」。选错了,表现就是「只灵了一次」或者「多绑了好几遍」。第 25 课专门算这笔账。
七、小测验
按四步法翻译,蓝图 Branch 节点的 True 分支在代码里变成了什么?