Jim's GameDev Blog

可视化脚本

2020-5-2

这篇文章其实早就写好了,但是一直让它躺在文件夹里吃灰。主要是由于太过理想化了,而且有很多细节需要深入考虑,如果真的要做好,至少要一个研发团队专门跟进维护,且对开发团队要求很高。没有成功案例谁都不会买账。所以就让灰尘越积越厚吧。下面是正文。

写这篇文章的目的是对以前做过的可视化脚本的雏形一个总结。而之所以把很多年前做的东西再翻出来说一遍,是由于我计划在新项目中大范围的使用到可视化脚本。所以这里会分两部分来说,第一部分总结以前,第二部分计划未来。

那就先说说这个可视化脚本的雏形。之所以称为雏形,是因为最终的完成度很低,但是基本的功能都有了,整个流程可以跑通。

做这个工程是在 2013 年,我一个人时长两三个月左右吧。为什么要做这个,是因为老大说要做的,我当时是不理解的。首先程序员对这种图形化的方式似乎是天生的排斥,包括现在来看都很多程序员都是如此,其次大家都忙着从 Flash 转 Unity,没有人愿意花时间来做这些了(背景,页游大潮的尾声,Flash 被多方诟病,亲妈不爱后妈不管,Unity 引擎白菜价,智能移动端崛起,大势所趋)。

回到正题,当时老大要做的可视化脚本是类似 Scratch 的,我经过分析后不认同。因为 Scratch 只适于逻辑简单的编程,怎么保证逻辑简单,就需要有大量封装好的高层逻辑。即使这样,对于复杂的调用也很难处理。如果你不明白我在说什么,可以去看看 Playmaker。Playmaker 状态机的每个状态就是高层逻辑,高层逻辑可以由很多个 Action 组成,但是再想把 Action 拆成可视化脚本就很困难了,硬要拆也可以,相当麻烦。其实细想起来,我们完全可以用 XML 来配置出 Playmaker 的所有功能,Scratch 同理。对于专业的游戏开发来说,XML 就是可视化的脚本,最多按照 XML 结构做个渲染层就可以了。这里拿 Playmaker 举例不是说它不好,有现成的可视化工具当然比配置 XML 要方便,只是为了把 Scratch 这个方案的弊端说清楚。既然 XML 配置就搞定的事情,何必大费周折呢。

然后我的目标就盯上了 UDK (那时还不叫 Unreal Engine)的 Kismet (那时还不叫 Blurprint)。 老实说我没有真正用过 Kismet,哪怕是打开看一眼,因为 UDK 实在是太卡了,以至于编辑器在电脑上几乎是卡死不动的状态,所以直接看网上的视频吧。我参考了很多的 Kismet 的视频教学,最终就把方向定成这样的了。因为 Kismet 这样的结构才能满足我的要求,既能控制高层逻辑,也能写底层逻辑。当然现在看来还是有很多要改进的。

然后就有了上面那张图所展示的可视化脚本的雏形,下面我会细说每部分的功能。

这个 Demo 里有三个图形,蓝色矩形、红色圆、绿色三角形。这三个图形导出为三个 symbol,都实现了一个空的接口(用来被可视化脚本识别),导出为一个 swf。可视化脚本负责加载这个 swf,然后对其进行控制。

我们在可视化脚本里就是制作各种功能模块,这些模块可以是高层模块,也可以是底层模块。最后将这些模块嵌套组合起来,实现功能。

这里要实现的功能是让蓝色矩形旋转,并且淡入淡出。显然这两个功能都是要在 Update 里实现的,这就需要类似 Unity Monobehavior 的 Update 和 Start 回调。

可视化脚本内置了这些事件,其中的“每一帧时”和“初始化时”就对应了 Update 和 Start。当让也可以自定义事件,然后在可视化脚本中自己派发事件。

这就是一个最高层的模块,从图中可以很清楚的看到它实现了旋转和淡入淡出两个功能。具体这两个功能如何实现的,如果你是模块的使用者而不是制作者,是不需要关心的,直接把模块拖过来用就可以了。

这是旋转模块的具体实现,有两个输入参数,要旋转的对象和旋转的速度。

这是淡入淡出模块的具体实现,可以看到逻辑还是比较复杂的。有几个蓝色的模块,这些都是自定义模块,也就是说支持模块的嵌套调用。

这个是调试用的窗口,可以自己输出调试信息,显示解析器检测到的错误的警告,断点调试。如果仔细看前面的模块的话,可以在每一个单元的左上角看到一个 B 的标识,这个标识就是用来打断点的。打上断点后,解析器执行到这里就会暂停,你可以像写代码一样调试可视化脚本。

上图就是一个示例,当打上断点后,解析器会暂定运行,堆栈窗口会显示当前的调用堆栈,点击堆栈可以跳转到对应的模块,以及显示相关变量的值。图中红色标记的就是当前模块的调用堆栈。

还有一个非常重要的功能,就是支持运行时编辑。这个非常有用,可以在不用重启的情况下,不断的调整可视化脚本,这对开发效率来说提升非常大。从图中可以看到,可以在运行时断开、连接,然后点击左侧的提交模块按钮,行为就会发生改变。解析器会进行必要的判断,检测有没有异常,进行提醒,显示在调试窗口的“错误/警告一栏里”。当然解析器会保证,没有任何错误会让程序有空指针这类问题,因为底层已经处理掉了,不管你怎么连线,程序都不会崩。

这个项目还有一个没做的功能就是变量的管理,局部变量和全局变量,用户应该可以手动编辑这些变量。像上面的运行时编辑就很依赖这个功能。因为当运行时编辑导致变量值没有达到预期的时候,要手动将其复位,再进行不断的连线逻辑,以达到功能上的预期,这是个不断迭代的过程。

再看看核心代码结构,大致可分为四部分。核心、辅助、指令、流控制。核心部分就是解析器,上下文堆栈控制,变量内存管理。辅助是一些基础类和工具类。指令部分就很清晰了,基本上每一个指令类都对应可视化脚本里的一个功能节点。流控制也好理解,对应代码里的 if 这一类的流程控制,在可视化脚本里也能找到相应的节点。

至此以前的这个项目就介绍完了。下面就要说一下为什么我打算在下一个项目中大量的使用可视化脚本,选择哪个可视化脚本插件及理由,和要怎么在项目中使用可视化脚本。

让我产生这种想法的起因是从 UI 的制作开始的。对于 UI 制作,最傻的方法是,策划出个草图,美术出效果图,程序拼出界面,程序写 UI 逻辑,美术调整细节。这还只是第一版 UI,众所周知,游戏里的 UI 不出个三四版是不会罢休的,有些项目的 UI 版本甚至多达十几版。改个 UI,所有人都得跟着改,大大降低的开发效率。而这里面还有个奇怪的地方,有些程序的 UI 逻辑和界面的层级结构还有关联,有时改个 UI 连代码都得更着改。

后来就有了 UI 编辑器。这样就大大简化了流程,美术做控件,策划在 UI 编辑器里拼界面,美术调整细节,程序写 UI 逻辑。UI 改版的时候,程序逻辑就可以不动了。这里的问题是 Unity 并没有提供 UI 编辑器,而我又不想使用例如 FairyGUI,因为它是黑盒不可控,项目少,付费也没有源码,不能在 Hierarchy 里体现层级关系,复杂点的行为在 FairyGUI 中表现的和 Unity 中的有差异,开发过程中万一达不到需求就很尴尬。那么 Unity 本身就是个很好的编辑器,为什么不用起来呢。一个利好是,现在 Prefab 可以嵌套了。所以我计划直接用 Prefab 做成一个个控件,让策划直接在 Unity 里拼就好了,直接了当。

对于做过大量 UI 的人来说,有一个体会,就是很多 UI 的逻辑都很简单,无非就是绑定一些事件,从数据层抽取数据,简单处理后显示出来。但是当 UI 的数量一多,工作量就相当可观了,这些重复的体力劳动完全落在了程序的头上,特别是小团队程序人本来就少。所以我就想到能不能把这部分工作提取出来,让没有程序基础的人也能做。而程序就只负责纯数据层。当然前期肯定是需要程序专人指导的,但是一旦一个 UI 跑通了,后面的工作量就会大大减少,程序就有更多的时间处理其他的难点了。

这样我们就把程序写 UI 逻辑拆成了两部分,一部分是程序处理纯数据层,另一部分是非程序人员写表现层逻辑。这样的话,只要服务器数据层不变,客户端数据层也百分百不会变。哪怕 UI 翻天覆地的变化,也只是表现层逻辑的变化。

所以,我就需要有一个工具,来让非程序人员处理表现层的逻辑。

对于经历过 Flex 的人来说,第一个应该想到的就是数据绑定(其实也不是 Flex 首创啦)。对于简单数据,数据绑定确实很好用,但是有很多时候服务器传过来的数据和 UI 要表现的数据是不能工整对应的,要经过处理才能用。所以我认为 Flex 那套还是 App 开发用起来方便,游戏开发并不好用。

所以思考了很久,只有可视化脚本可以满足需求。首先服务器数据传到客户端后,程序员直接把数据存到数据层,不需要经过任何处理,非程序人员通过可视化脚本从数据层读取数据,处理数据,显示在 UI 上。如果有 UI 要随着数据层实时刷新的,那么程序员在代码里派发一个事件,可视化脚本监听到了刷新显示即可。

然后就是选择可视化脚本编辑器了。我对可视化脚本编辑器有这么几个需求。要像代码一样能胜任较复杂的逻辑处理,最基本的对数据层的增删改查、事件的监听派发、控制 UI 的表现都是必须的。要能够自定义模块,实在复杂或者非程序人员无法胜任的功能,由程序员封装成模块,非程序人员调用即可。要能够热更新,做不到这一点的弊端就不用多少了,做过开发的都明白的。

在 AssetStore 上可以找到几款可视化脚本插件,经过反复比较和测试,最终的候选停留在了 Bolt 和 FlowCanvas 上。Bolt 集成了流图和状态机,而 FlowCanvas 只有流图,目前没有考虑到要使用状态机的情况,但是以后不排除要使用,所以 Bolt 暂时有点小优势。Bolt 虽然号称提供源码,但是实际上都是 dll,论坛上也有人问过这个问题,Bolt 官方回复说可以自行用 ILSpy 进行反编译查看,而 FlowCanvas 倒是提供了全部的源码,这点 FlowCanvas 占了上风。Bolt 和 FlowCanvas 的讨论区都比较活跃,提问都能很快的到回复,Bolt 比较新,活跃度稍微高一点。Bolt 的另一个优势是,roadmap 中的下个版本,会可选的把流图转换成 C# 代码,执行起来会更快,这样如果有开销大的地方,不用手动转 C# 了,而万一流图出了没办法解决的问题,直接转 C#,用 ILRuntime 或 xlua 临时处理下也未尝不可。最后就是热更新了,两个都支持,限制是 AOT 环境下,不能创建新的节点,可以利用已有的节点组合拼装出新的逻辑,这个限制其实和 xlua 是一样的。Bolt 和 FlowCanvas 都支持运行时编辑,这对于没有程序经验的人来说还是很有用的,因为他们对程序的执行顺序没有感官的认识,可以一边编辑一边看到效果,找感觉,对他们来说很有帮助。

最后结论是,我打算使用 Bolt。

如果让非程序人员放开了使用 Bolt,一是他们也无从下手,二来会对程序代码有破坏性的侵入。所以需要制定这方面的规范,哪些功能可以用,哪些功能不能用。而 Bolt 有个很好的功能是,可以手动指定暴露给可视化脚本的接口,这样我们就可以把不需要的功能隐藏起来,防止误调用。当然一个好的文字上的开发规范还是必不可少的,这个后面我会开开发的过程中不断完善。

以上说的只是在 UI 上的可视化脚本的使用,而我还需要考虑的是把它拓展的其它的功能开发上,看看能不能有所进展。