Skip to content

第一个自定义插件

RoyEngine的体系下的各种逻辑都是在插件中完成的,在项目开发的时候,就是要定义自定义插件,每一个插件都是一个npm包的形式。这样做的好处是:

  • 业务与业务之间解耦。
  • 业务的复用,比如一个全景显示的插件,新的项目如果刚好也要用到全景,那么直接用就好了
  • 独立发包发版

一些显而易见的问题:

  • 插件本身一定是依赖于RoyFrame的,这决定了插件都具有差不多的形式,其他人接收和理解一个插件时,理解成本会有一定的降低。
  • 插件与插件之间会有依赖关系,比如一个插件A是依赖于其他人写的插件B,那么插件B在改动的时候应该避免breakchange,但插件B的开发者并没有义务来通知插件A的开发者他的更改。插件A的开发者在决定依赖其他插件的时候已经做好了这方面的觉悟,且插件A依赖的插件B的某一个特定版本而非最新版本,所以插件A可以一直依赖插件B的特定版本。
  • 引擎侧不会介入插件的纷争当中,但会为插件评级,一些较好的插件会被纳入核心插件,向所有插件开发者推荐。

那么如何来开发一个新的插件呢?与第一个前端工程类似,插件也是有模板的,我们可以依照这个模板来创建自己的插件,下面主要讲解一下这个模板。目录位于plugins/plugin_template。

首先,工程本身集成了很多易于开发的开发套件:

  • eslint:用于检查代码规范
  • prettier:用于格式化代码,同时与eslint配合
  • husky:git的钩子,在git上传的时候,使用eslint和prettier检查代码
  • commitlint:git的message的规范化,提交的时候也要求提交信息是规范的
  • editorconfig:编辑器的文本输入规范,如使用tab还是space,如果是space的是4个空格还是2个空格等。

第二,工程基于webpack作为打包工具:

  • 基本过程:编译ts且处理外部依赖,打包assets。是一个正常的webpack流程。
  • 执行pnpm build:dev即可在libs中生成最终产物.js文件。
  • 除了js文件,我们还需要生成dts文件,执行build:types。这里注意,我们使用了rollup以及其处理dts的插件来生成了我们自己的dts,这主要是在webpack的框架下,dts生成的插件也有,但是都不完美,自己开发自定义webpack插件又觉得不必。对于rollup和webpack之争,主要是一个生态的问题,目前RoyEngine是拥抱webpack的。
  • 需要注意的是,为了规避循环依赖的问题,以external的方式引入@sd开头的包。

最后,我们开看一下插件里面有什么文件

  • 定义结构体,定义类,导出。一个标准的lib的样子。
  • 值得注意的是PluginTemplate,它继承自AppPlugin,也就是说它可以在启动是注册给RoyFrame,并响应一些事件和行为。
typescript
import { AppPlugin, AppPluginMgr } from '@sd/royframebase';
import { RoyScene, RoyCameraComponent } from '@sd/royinterface';

export class PluginTemplate extends AppPlugin {
    public static PLUGIN_NAME: string = 'PluginTemplate';

    protected m_scene: RoyScene;
    public get scene(): RoyScene {
        return this.m_scene;
    }
    public set scene(value: RoyScene) {
        this.m_scene = value;
    }

    protected m_cameraComp: RoyCameraComponent;
    public get cameraComp(): RoyCameraComponent {
        return this.m_cameraComp;
    }
    public set cameraComp(value: RoyCameraComponent) {
        this.m_cameraComp = value;
    }

    public constructor(mgr: AppPluginMgr) {
        super(mgr);

        this.m_name = PluginTemplate.PLUGIN_NAME;

        this.onInit();
    }

    onStart() {
        super.onStart();
    }
}

一些注意的点

  • 我们不一定非要写PluginXX这个类,正如终端工程中的App类一样,并不是要把这个项目的所有逻辑都写到App身上;同理,不是要我们把这插件的逻辑都写到这个PluginXXX上。我们可以看到一些插件甚至没有写PluginXXX,如果一个插件并不需要以这种方式响应RoyFrame的回调,那么其实并不需要这个类。
  • 对于自定义插件这个lib包,我们可以写任意类和任意接口,然后导出,给其他的插件和终端工程去调用。但任意类和任意接口,没有任何约束的化,插件和插件之间的差异必然很大,理解起来很费劲。为了解决这个问题,推荐使用Object-Component形式来解决这个问题。熟悉Unity或UE的开发者,都会以Component或者Actor的形式来扩展自己的逻辑,所以也推荐以Component的形式来定义插件的内容,所有受到引擎推荐的插件,也都是这种形式和形态。
  • RoyEngine框架下的Object-Component的形态,可以参考引擎推荐的插件,也有一个文档来详细说明这个问题。