干货分享:Cocos Creator 编辑器技术架构与实践

Cocos Creator 的编辑器从立项之初便坚定地在前端技术栈上进行投入,目前已积累了大量业界领先的底层经验。这些底层设计支撑着更上层的框架及业务层开发,一方面满足 Cocos Creator 自身快速迭代的需求,另一方面也支撑着 Cocos 的 HMI、虚拟角色等其它业务线的编辑器实现。由于 Cocos Creator 的编辑器并不开源,相信大家也对这样一个庞大工程的实现机制十分好奇。

在本届 GMTC 大会上,Cocos Creator 前端负责人 vsj 受邀出席,发表了以《Cocos Creator 编辑器技术架构与实践》为主题的演讲,分享了研发期间所遇到的痛点和解决方案、Cocos 在 Web 技术领域的探索以及对未来前端技术趋势的观点。图片

项目背景

Cocos 从最早到现在共经历了 13 年,已经发展成了全球领先的数字互动内容开发平台,在中国的游戏引擎领域稳占第一,在全球有 160 万的注册开发者,这些开发者为 Cocos 生态做了大量的贡献。

比如用户通过 Cocos Creator 的插件系统扩展出了许多优秀的插件,如可视化材质编辑器、Easy NavMesh 寻路插件和网格寻路插件。

可视化材质编辑器是一种低代码编辑器,通过预设一些功能,以连线的方式将一些功能连在一起,通过设置连线的一些参数或者条件,输出为引擎能够识别的资源,通过引擎驱动,最终实现一个效果。图片

而 Easy NavMesh 和 NavMensh 寻路插件,则是通过组件和脚本系统,给游戏运行时扩展了寻路功能。图片

这些都离不开 Cocos Creator 的插件系统。它不仅能够支持编辑器内的编辑操作,还能够直接扩展游戏里的内容。Cocos Creator 又是基于前端技术栈,才能方便的将这些能力开放给开发者。

选择 Electron 的原因

在众多的前端技术栈中,Electron 能够提供非常多的能力。它内嵌了 Web Runtime,能够完整地使用 Web 上的功能,一些复杂的界面操作在 Web 上都能够比较方便地实现,如果无法实现,还可以使用 Canvas 实现一些特殊或者复杂的交互操作。

同时 Electron 使用 NodeJS 作为开发语言,能够给我们带来方便的模块管理,动态的插入、卸载脚本。

此外就是跨平台一次性开发和多端发布。在一次性开发完成之后,可以同时发布在 Mac 和 Windows 两个平台。

同时还处理了许多功能、平台差异的适配问题。比如适配不同缩放分辨率的屏幕等。

最重要的是给我们带来了 NodeJS 的生态,它能够加速业务开发,让我们更加专注地在业务上,一旦产品提出一个业务需求,都能够在 NPM 市场上找到相应的解决方案或工具,快速将功能迭代出来,让我们后续有时间去优化。图片

Web 应用桌面化

使用 Electron 或者说使用 JS、TS 开发一个大型应用,所遇到的最严重的并不是技术上的难点,而是工程上的管理难度上升。JS、TS 的自由在项目复杂后,会给整个项目带来各种各样的不确定和问题。

业务量多和相关解决方案

我们把这些问题拆分成了两个方向,第一个是代码复杂度高,第二个是异地协同或者多人协同。

整个编辑器(包含引擎)的代码量大概在 123 万左右。这个量级对一个普通的 Web 应用或者页面来说已经非常恐怖。Cocos 的业务量也非常多,有 60 多个功能插件。我们同时支持了 15 个平台,都需要进行适配。图片

第二个问题是多人协同。编辑器的开发同学遍布了北京、上海、成都、深圳和厦门各地,职能、甚至技术栈都不一样。图片

为了应对工程上的难点,Cocos Creator 使用了一个分层和模块化的设计思路,如下图,共分为三层。图片

编辑器最上层的业务以插件为核心。在 Cocos Creator 里,万物皆插件。除了编辑器最底层的 API 和一些我们封装成 NPM 的模块,所有大家能看见的功能都被封装成了一个个插件。插件系统是编辑器内非常重要的一个灵魂,不止是开发者提供的插件,所有编辑器模块也都是插件。图片

Creator 插件的运行结构基本上参照了 Electron 的进程划分。图片

在书写上,Cocos Creator 插件和 NPM 模块基本一致,一个 package.json 描述当前插件的基本信息。json 里的 main,是插件主要逻辑入口。插件启动后,会一直运行,类似后台服务。

panels 则是面板逻辑入口,一个插件可以注册多个面板。面板可以理解成页面上的显示模块。他是可以被关闭的。而 Contributions 则是插件的核心机制之一,为插件带来了互相影响的能力。

业务间的交互

Cocos Creator 在使用插件进行业务隔离后,第一个要解决的问题是不同业务间的交互。我们选择使用通讯管线或者内置一个通讯管理器。举个例子,当在场景区域选中了一个物体的时候,需要更新层级管理器,属性检查器的内容也需要进行一次更新。图片

最简单的通讯是单向通讯,而它带来的是单向的依赖。ExtensionA 依赖了 ExtensionB。图片

捋清楚依赖关系对工程管理十分重要。但这里我们用先看看单向通讯的简单示例。

如果在场景编辑器内更新一个属性检查器,过程如下图:图片

我们在场景编辑器首先点中了一个人物节点,选中节点之后,场景去通知属性检查器,属性检查器收到通知或消息之后就开始进行一次渲染,渲染到一定程度,如果数据不够,它就主动再向场景查询附加数据。等到场景返回了信息之后,整个渲染就完成了。

但这时候就出现了一个双向依赖的问题。双方都在互相操作、查询对方。这在工程上会带来不少的隐患。所以在复杂的异地协同或者多人协同上,必须解决这种问题。

解决也很简单,有单播,肯定也有广播。广播其实就是进行了一次逻辑上的依赖倒置。图片

对应上述例子,如果使用广播,整个的通讯流程会发生改变,如下图所示:图片

整个逻辑链路变成了——场景和属性检查器依赖消息管理器,它们之间没有互相的依赖,属性检查器也依赖场景。

Message 与 IPC

Message 是 Cocos Creator 的通讯机制,其实是在 Electron 提供的 IPC 基础上做了一些扩展。

IPC 的问题在于它是进程间交互,同进程不同模块的交互无法实现。而且 IPC 只有单向通讯,如果只有单向的交互,整个程序交互起来会非常吃力。

所以 Message 主要就是解决了上面两个问题。第一步就是进行了一次消息转发,在发送的时候将数据压缩后,通过 IPC 发送到对应进程,再由对应进程将这些信息解码之后,再分发到对应的模块。

而消息回复也很简单,在发送的时候记录一个 id,在处理完后将返回数据和 id 一起通过 IPC反向发送回请求的进程,通过 id 找到之前发送的代码或对象,再执行后面的代码。

最早的 Cocos Creator 第一个版本,实现的是左边 callback 这种方式。在消息处理的时候,第一个参数是一个 event, event 上有个 reply,它需要手动执行一次 reply,才能返回数据。这带来了一个非常大的隐患。如果用户不执行reply,发送方没有机会知道它返回了,而且发送方也不知道实际处理的函数是否已经终止。图片

所以在 Cocos Creator 3.0,因为 ES6 引入了 promise 后,我们能够将所有消息改为 promise 解决 callback 不回调的问题。在发送消息的时候,管理器内会生成一个 promise 对象并缓存,接收方也只要生成一个异步函数,异步函数只要 return 了肯定会结束。收到结束的数据后发送方只需要找到刚刚缓存的 promise 对象,执行对应的 resolve 函数,就能够实现一次完整的消息的通讯。

Contributions 机制

Contributions 机制也是 Cocos Creator 的一个核心机制。

Cocos Creator 在开发业务的过程当中经常会出现需要随机添加业务或无法预知时间点等情况,所以必须预留出比较好的扩展能力。

我们希望一个业务相关的所有功能都包含在一个插件内。以动画图为例,动画图包含了三部分。第一部分是面板 – 动画图基础交互的区域。第二部分是偏好设置内需要增加一些动画图的配置。第三部分是属性检查器需要渲染一些动画图相关的元素。只有这三部分都能放到动画图插件里,才能做到真正的插件化或者模块化。图片

还是以动画图和属性检查器为例,这种情况下他们的依赖关系跟前面的场景是一样的。动画图依赖消息管理器,属性检查器依赖消息管理器和动画图插件。但是问题在于在开发属性管理器的时候并不知道未来动画图需要在属性检查器里显示。当在动画图插件上进行一次更改或者功能迭代的时候,属性检查器作为依赖它的一方也必然要进行一次更改,或者要进行一次功能测试。这种情况在业务量特别多的情况下比较麻烦。

我们希望进行一次业务的拆分,将依赖再进行一次倒置。在属性检查器开发完成之后,不再关心任何的业务,只做一次转发或只管流程。图片

要实现依赖倒置,就要将主动权交给动画图,由动画图向属性检查器主动地注册渲染类型。属性检查器收到之后,将数据缓存起来。

当选中的物体是动画图注册的类型的时候,它就会将渲染的权限递交到动画图,动画图拿到数据再根据业务进行渲染,最终将渲染的内容返回给属性检查器,属性检查器就能完成一个流程。通过 Contributions 将属性检查器变成依赖数据,并且作为一个基础机制不再被其他的业务所影响。

具体流程如下,动画图插件启动的时候,向属性检查器里提供一份渲染类型的数据。属性检查器在接收到选中的物体的通知后,就会去 Contributions Manager 里面根据类型找到对应的数据,如果找到了数据,它就会将渲染权限递交给可以渲染这份数据的插件,这里就是动画图。图片

Contributions 机制的目的就是提供插件与插件之间的互相扩展的能力。

举一个最简单的示例:新建一个简单的扩展,在 package json 里就有 Contributions 数据,里面又有两个属性,一是 menu,二是 message。从而可以猜到这个插件注册了一些菜单,同时也注册了一些通讯用的消息。图片

通过 message 和 Contributions 两个机制,Cocos Creator 将所有功能都以插件为单位隔离开来。其中也有一些意想不到的插件,例如示例的 Contributions 数据内的 menu 和 message,他俩也是一个插件。在其他的竞品或者编辑器来说,menu 和 message 都是做成底层机制。但在 Cocos Creator 里,万物皆插件。

源码保护

除了工程管理,在源码保护上,Cocos Creator 做了非常多的尝试,Electron 里使用的是 JS,JS 只能进行压缩或者混淆,使得代码逻辑变得复杂难看,但花一些心思还是能够找出核心的运行逻辑。

Cocos Creator 上我们尝试的第一个做法是将一些 JS 原生化,翻译成了 C++。通过两种方式,一种是编译成 wasm,通过 Web Assembly 的方式。第二种是编译成了.node。这种方式在大部分情况下是可以的,但也遇到了一些问题。图片

因此我们尝试了第二种方案——预编译字节码。Electron 里运行的是 V8 引擎。V8 运行一个 JS 的时候,是将一段 JS 代码预编译成了二进制的字节码,最终在运行的时候,运行这份二进制数据。而我们要做的就是先将 JS 代码编译成了二进制的字节码,在需要运行的地方,调用底层接口解析这段代码。

但是这个方案也同样有严重问题。它强依赖了运行时的 vm 环境,当基座或者 Electron 升级,原字节码会失效。甚至在运行环境出现变化的时候,也会使预编译的字节码无法解析。Cocos Creator 最无法接受的是一些 NodeJS 全局 flags 变化导致预编译代码失效。

所以 Cocos 尝试了第三种方案,在 C++ 层内置解码工具。从 JS 层读取文件,取到文件数据之后发送到 C++ 进行解密。这种方式的好处是工作量非常小,只要在 C++层找到任意地方放代码即可。但对 Electron 来说, C++ 层是 Electron 源码或者 NodeJS 的源码。这些是一种侵入式的修改,侵入式修改肯定会给后续的升级带来一些工作量。

总结与展望图片

使用 Electron 的过程中,除了刚刚说的工程管理和源码保护问题,还有以下几个问题。

首先是前端技术迭代速度太快。Cocos Creator 在开发游戏编辑器的时候,非常看中的就是生态,我们希望公开 API 尽量不要出现不兼容。而前端频繁更新的框架、版本,恰恰限制了我们对外使用这些功能。举个例子,在 Cocos Creator 最早的时候,使用了 vue 1.x 制作 UI,在对外的一些 API 中,也直接将 vue 对象暴露给了插件开发者。几年后 vue 2.x 甚至 vue 3.x 发布了,当我们想升级的时候发现已经无法升级。因为一旦升级,原来的插件都失效了。当然我们可以选择强行兼容原来的代码,另开一套机制或者 API 使用新的语法和框架,但这会带来更大的学习成本和维护负担。所以我们可以使用前端的各种框架,但一般不会对外提供。

其次是 JS 语言的性能低,通过原生化部分 JS 上的功能,也能解决。

接下来是权限管理,这里的权限管理指的是某段代码只能够访问哪些部分的文件或者网络的 API。这部分 NodeJS 上很难做到,我们可以通过劫持 API 模拟一些权限管理。

最后是平台能力的扩展困难。Electron 给我们提供了非常多平台能力,但如果有超出这些能力的需求,就只能被迫去修改 Electron 源码。图片

最后,展望一下 Web 的未来。前端技术已经应用在广阔的领域,而这些应用大概可以分成两类,第一类是内嵌 Web Runtime 的场景,代表有 Electron,类似的也有 tauri 等。第二种是技术引用互相借鉴、融合的方式,如Flutter,它借鉴了 CSS 的一些模型、弹性布局等等。

其中内嵌 Runtime 可以理解成是让开发者走进前端生态和环境。当 Runtime 能力和性能足够的情况下,相信会有许多 APP 愿意迁移到 Web 生态里。

技术引用和融合则是前端技术向外走,向外散布自己的影响力。比如游戏或者车机、VR,它们可能运行的不是 Web Runtime。它们使用的可能是自研,也可能是成熟的一些布局框架。他们的共同点就是都能够从 Web 上吸收一些成熟的想法或思路。这种方式恰恰就是一种为了向外衍生自己影响力,有可能会影响到这些新兴领域的基础发展方向。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论