2018年7月至2019年7月,负责 VR 机务乘务培训项目的这一年里,有一笔未结清的技术债始终困扰着我:机务工卡(Work Card)任务系统架构引发的性能问题。
项目初期为了快速落地功能,我直接选用 Unity 资产商店的一款游戏任务系统,依托其有向无环图 + GraphView 的架构实现业务逻辑。这套方案虽能勉强支撑基础功能运行,但存在致命的性能缺陷:只要单张工卡的任务图节点数一多,编辑器卡顿急剧恶化,严重时甚至无法正常操作。
为了彻底解决这个历史遗留问题、适配 XR 培训项目的业务场景,我决定基于 Unity 6 Graph Toolkit 0.4.0-exp.2,从零搭建一套轻量、高性能、专为 XR 场景定制的多分支无序任务系统。本次开发由我主导,大部分代码由 TRAE 生成,我负责需求定义、测试校验,并补充少量业务代码。本文将完整复盘这套系统的设计与实现过程,客观记录其中的思考、技术取舍与落地细节。
最终的成果是一套可复用的 XR 多分支无序任务系统插件:6 种任务节点、一棵倒置任务树、一个能看清真假状态并当场干预的可视化调试窗口;编辑器里画完逻辑图,自动烘焙成运行时数据,单图可承载 1000+ 节点。
01 需求,先不着急写代码
Work 模式
那时候我还不懂,光是把需求聊清楚这一步,就已经值了。
起点:从一个具体踩坑痛点,瞄准一类行业共性需求。 最开始萌生做这套系统的想法,纯粹是被第三方游戏任务系统磨得受够了:在 VR 机务乘务培训项目里,单份实体 A4 工卡就要拆成上百个跨区域的检查节点——学员可以在不同检查区域间自由跳转,做完 A 区的几项检查,随时切去 F 区,不需要强制按固定顺序走完流程,也不会因为穿插操作影响最终考核结果。但用市面上的第三方任务插件做这种多分支无序编排,编辑器卡到拖不动节点、连不上线,根本没法支撑复杂任务的高效制作。
琢磨这件事的过程中我很快意识到:这根本不是机务乘务培训独有的问题——只要是 XR 培训类项目,不管是机务维修、乘务应急演练、安检实操,核心任务逻辑几乎都绕不开“多分支、无固定顺序的自由巡检 / 操作编排”需求,市面上却没有专为这类场景做的轻量高性能工具,大家都在硬套为游戏关卡设计的任务插件老架构。
所以从设计之初,我就没打算只写一段给当前项目凑合用的临时代码:目标是沉淀成一套通用插件,只要 XR 培训项目需要实现“多分支无序任务编排”的核心逻辑,这套系统就能直接复用——轻量、编辑器流畅、带可视化运行时调试能力,不用每个项目再重复造一遍轮子。
把需求拆成几个能被“画”出来的抽象。 真正进入讨论后,最关键的几个决策是这样一个个敲定的:
-
节点种类——任务里最常见的判定就那么几种,最终收敛成 6 种:计数(Counter)、限时(Timer)、开关(Flag)、逻辑与(And)、逻辑或(Or)、终点(End)。够用,且少到能一眼看懂。
-
结构是一棵倒置树——不是任意杂乱连线,而是有约束的分层:叶子(Counter/Timer/Flag)负责"发生了什么",中间(And/Or)负责"怎么组合判断",根(End)负责"任务完成"。这个结构让我在设计阶段就锁定了数据流方向。
-
逻辑节点本来就需要接多个上游信号——像 And 节点要等所有条件全满足、Or 节点只要任意一个条件达成,这类节点天然要求输入侧支持多连接。我据此调整了全局端口规则:所有带输入口的节点,输入端口都开放多连接,允许同时接收任意数量的上游连线。
-
End 节点必须是唯一的特殊存在。这是需求讨论里最有价值的一处:一开始想用检测是否有多余终点来保证唯一,后来发现这等于让异常状态污染正常逻辑。改成了更优雅的设计——任务创建时就自动带一个 End,它不可删除、也不能手动添加。这就把非法状态从设计上直接杜绝了,而不是靠运行时兜底。
-
一次顺理成章的小重构:最开始 Counter、Flag、Timer 这类执行叶子节点,靠自带的 Name 做对外身份标识——外部业务逻辑靠 Name 就能精准定位节点,给指定 Counter 累加数值、把指定 Flag 置为 True、启动指定 Timer 计时。后来需求延伸:外部不仅要操作执行节点,还要按 Name 查询 And 节点的当前进度、判断 Or 节点是否达成,逻辑节点、End 节点也必须带 Name 才能被定位。写着写着就发现:6 个节点各自重复维护一份 Name 字段,冗余感很强。我提了下这部分重复能不能收一下,TRAE 建议把名称收敛成基类里的公共字段,代码从 6 份重复变成了 1 份共享。
需求分析收官时,我得到的资产是: 一份一目了然的需求定义、6 种节点的语义、一张倒置的任务树结构图,还有几个“想明白了”的设计决策。全程没有写一行实现代码。
这是需求从“各说各的”收敛成“最终版”的那一刻——6 种节点的规格被一张表钉死了。
02 实现,它替我把坑都踩了
Work + Code 模式
整个实现过程几乎没有一行是我从零写的。我把需求说清楚,把设计稿丢给它,把报错原样贴回去——剩下的是它在啃源码、试反射、调状态机、一个接着一个地排查原因。我更像一个验收者:判断它对不对、方向对不对、纠正它的写法,哪里该删。
以前写程序,碰到一个陌生需求,流程永远逃不出这几步:先翻官方在线文档、找社区帖子、搜别人踩坑的博客,写个小 Demo 验证这条路真的能走通,最后再把方案整合进项目里。偏偏最花时间、也最磨人的,根本不是最后敲代码的环节,而是前面这一整段“查资料—做实验—踩坑验证”的摸索过程。现在只要我把需求说清楚,TRAE 就会顺着去啃官方文档、翻源码、试各种可行的技术路径,实验当场替我跑完,确认可行了就直接整合进项目里。它替我省下来的哪里只是写代码的时间——是整整一段“完全不知道怎么做,到真的把东西做出来”的调研与试错全过程。
最开始我提的需求特别泛,颗粒度很粗:“使用 Unity 6 的 Graph Toolkit 实现一个任务系统”。TRAE 很快按最成熟的常规思路出了一套完整方案,我仔细核对实现细节才发现:它根本没用到 Unity 6 新出的 Graph Toolkit,还是沿用老版本的 GraphView 架构。
我把问题指出来之后,决定推倒重来,并定下贯穿全程的两大基调:
-
底层必须真正基于 Graph Toolkit,而不是原生的 GraphView;
-
运行时数据模型参考 Graph Toolkit 官方 Visual Novel Director 例子的做法——用一套独立的纯数据对象承载运行时状态。
Graph Toolkit 是个很新的实验预览包,带运行时的参考例子很少,最初几版要么不带运行时、要么基于原生的 GraphView。弃用 GraphView 的原因很直接:它只是底层画布,序列化、undo、右键菜单都得自己写;而 Graph Toolkit 在它之上封装了完整的节点编辑器框架,做了模型视图解耦、视口虚拟化和局部脏刷新,大图编辑与批量操作时的内存、重绘性能都远优于原生 GraphView。
这两条基调和需求阶段的产物(6 种节点、倒置任务树)一起,圈定了后面的实现骨架:编辑器图 + 运行时纯数据的双层结构。
第一阶段:搭起“编辑器图 + 运行时纯数据”的双层结构。 落地都收在同一个任务文件(.xrtask)里:编辑器编辑用的逻辑图,和运行时真正读取的纯数据。中间用一条“烘焙”通道把它们串起来——编辑好保存,导入器就把图结构翻译成轻量的运行时对象。我在编辑器里画,运行时只跑干净的数据,不用背着整个编辑器。
第二阶段:把六种节点一个个立起来。 这里遇到一个很典型的坑:很多底层能力(比如让逻辑节点接收多个输入、自定义节点外观)没有公开 API,只能靠反射去调用内部实现。TRAE 把源码翻出来、逐个试反射路径,一点点把这些半隐藏的能力接出来。
第三阶段:把 End 节点做成自动托管的特殊存在。 这是实现阶段比较亮眼的一个点。TRAE 的做法是:新建任务时编辑器自动生成一个 End,居中摆好;它没法删除,也没法从菜单手动添加。让唯一终点从一条需要检测的规则,变成了结构上根本不可能破坏的形态。
第四阶段:状态传播,从递归改成队列驱动。 需求里有个硬指标:单图要扛住 1000+ 节点不爆栈。一开始用递归会导致深层级把调用栈压爆。TRAE 把传播引擎改成队列驱动——状态变化入队、逐层往外推,既稳又不怕深。
第五阶段:真正拉开差距的是 Debug 窗口。 我给了 TRAE 一张设计稿:树状结构展示整棵任务树、节点真假状态、Force 按钮。实现上有一处细节很关键:一开始窗口在每次状态变化时都整棵重建,导致“折叠/按钮一点就被打回原形”;改成“只构建一次 + 局部就地更新”后,交互才真正顺。
第六阶段:贯穿全程的报错—定位—修复循环。 这段经历里最常发生的,其实是我把项目跑起来、把报错/异常贴回去,TRAE 找到真正的原因、改代码、我再验证。印象深的几句:
-
Timer 超时后状态锁不牢、会自己跳回 False——修成超时/完成后锁定状态;
-
一个
and_0明明条件都满足了却不翻 True——查下来是状态传播的短路中断; -
还有些是"打补丁式修复"只压住了表象;后来排查才发现问题出在事件通知没有传递,补上链路才彻底解决。
这一轮轮的"跑起来试",比写第一版代码更像主干。
03 界面,从一张手绘草图到像素级打磨
Design 模式
真正让我觉得“这个工具值回票价”的,是界面这一关。
先看起点——这是第一个能跑起来的 Debug 窗口:功能都在,界面却实在谈不上好看。
我实在看不下去,自己在飞书文档里用画板手绘了一张草图:树状结构、真假状态、一个 Force 按钮——把“我想要的样子”直接画给它看。然后切到 Design 模式,让它分析配色、把 Debug 窗口的 UI 重做一版。它交回来的东西,惊艳到我了。
这是它交付的设计稿,而且可以直接交互。同一个窗口,前后两张脸——“值回票价”就是这么来的。
视觉上还抠到了细节:折叠箭头我要求用一枚居中的 16px 图标,TRAE 按设计稿实现,还接了深浅色主题自动适配——把这张 UI 打磨到了像素级。

最后站到调试窗口前,一眼看清整棵树里谁真谁假,还能当场“点成”某个条件。省掉了无数轮“改参数—重编译—看效果”。
04 不止写代码,它还肯删、肯给“人”看
Work 模式
写代码是主线。
删多余的、写文档,是它顺手做掉的收尾。
项目功能齐全之后,我让它“找找项目中多余的文件和可以删除的代码”,让它列出一份清单:没被调用的外观类、重复的连接类、一套没有作用的样式。
确认后删干净,收尾时我还让它写了两份文档,《软件设计说明书》和《用户手册》,接口、语义、示例一应俱全。
这一章我最想说:TRAE 不只在“写”,它同样肯“删”,肯把东西写给人看。对于开发人员来说,项目后期的整理和文档书写,省了我不少事。
05 结语
写代码这件事,变了。
过去最难的是“怎么写”。
现在最难的是“想清楚要做什么,判断它对不对”。
我用自然语言描述一个 XR 任务系统该有的样子,TRAE 把它翻译成可落地的工程实现、解决了一些开发过程中的坑、把界面画到像素级,甚至帮我整理出哪些代码该删。
很多人问 AI 会不会取代程序员。这篇不是要回答这个问题。
我只想说,一个人加一套趁手的工具,能让一个想法真正长成一个能用的插件。
以后的差别,大概不在谁敲代码更快,而在谁能把需求想清楚、并善用工具把它做出来。
而这段从想法到插件的路,本身就是 TRAE 好不好用的证明。










