一、插件是什么


插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据。很多软件都有插件,插件有无数种。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。插件的定位是开发实现原纯净系统平台、应用软件平台不具备的功能的程序,其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据(来自百度百科)。


二、为什么需要插件


面对在不同的地方有这相同的功能,希望把这些代码提炼一下,写出一个精炼的代码,让这些地方都受益。既然代码都提炼出来了,这就完事的了吗,但是想起是每次要在不同的地方调用相同的接口多少有点枯燥,希望有个管理者来在相应的时间来自动调用这些接口(UI插件者和UI插件管理者)


三、怎样设计UI插件


为了防止UI插件被重复注册,所有需要对UI插件进行序列化。

UI插件有时候可能会初始化一些东西,比如数据初始化,事件监听等等。所有UI插件需要知道自己什么时候被注册的。

UI插件肯定会有执行任务的时候,所有UI插件需要知道自己什么时候去执行任务。

综上所述,一个基本的插件应该具有以下职责:


  • 序列化标识(防止插件被重复注册)。

  • 插件被注册事件(初始化一些插件的数据,监听相应的事件)。

  • 插件被执行的时候(插件需要执行自己的任务)。


所有UI插件的基本接口设计如下:


/**
 * UI 插件模板,所有派生出的子插件都应该继承此类
 */
export default class BaseViewPlugin {
    name: string = ""; // 插件的名字
    register() {
        // TODO 插件注册事件 -> 由子类实现
    }
    start(node: cc.Node, component: cc.Component) {
        // TODO 插件开始执行 -> 由子类实现
    }
}


既然插件有被执行的时候,那么是有谁来执行这个插件功能呢。可以把这个职责给予视图管理者(ViewManager)。


// 插件管理 比如: UI自动绑定,自动挂载事件,语言本地化, 
private  _plugins: Array<bb.BaseViewPlugin> = [];
registerPlugin(plugin: typeof bb.BaseViewPlugin)  {
    // 防止插件重复注册
    let p = new plugin();
    let findPlugin = this._plugins.some(item => item.name === p.name);
    if (findPlugin) {
        return;
    }
    p.register();
    this._plugins.push(p);
}


既然插件有被执行的时候,那么什么时候让插件执行任务呢。希望所有UI插件,当有视图被加载并渲染出来的时候,执行所有插件。


this._plugins.forEach(item => { item.start(viewCtrl.node, viewCtrl.logicComponet)});


四、以多语言本地化插件为例


本文以如何制作一个自动处理多语言本地化插件来举例,并且将这个命名为 LocalizationPlugin。 首先新建脚本文件,并且继承 BaseViewPlugin 类: 代码如下:


export default class LocalizationPlugin extends bb.BaseViewPlugin {    name = "LocalizationPlugin"

    register() {     }     start(node: cc.Node, component: cc.Component) {     } }


多语言本地化时间被触发的时机分析,第一个时候就是UI界面被渲染的时候需要对其文本进行本地化,第二个时候就是当前本地化语言被改变的时候需要对文本进行本地化。下面贴上这个语言本地化插件全部代码:


export default class LocalizationPlugin extends bb.BaseViewPlugin {
    name = "LocalizationPlugin"
    register() {
        // 插件注册时调用
        bb.EventManager.addEventListener(1, this.onChangLanguageEvent.bind(this));
    }
    start(node: cc.Node, component: cc.Component) {
        // 插件执行是调用
        this.updateAllLabel(component);
    }
    onChangLanguageEvent(data) {
        bb.ViewManager[""]
        let view: bb.ViewCtrl = bb.ViewManager.viewStack[bb.ViewManager.viewStack.length -1];
        this.updateAllLabel(view.logicComponet);
    }
    updateAllLabel(component: cc.Component) {
        // 当前界面上绑定的所有 Label 组件
        let labAllKey = [];
        for (let key in component) {  
            if (key.match(/^_lab/)) {
                labAllKey.push(key);
            }
        }
        labAllKey.forEach(item => {
            component[item].string = bb._T(component[item].$, component[T${component[item].$}]);
        })
    }
}


注意:语言本地化文本内容的时候,有时候希望一个或者多个动态数据。例如 需要对“恭喜你获得100点攻击力” 进行多语言本地化的时候,希望 100 能够动态的变化。 那么这时候需要提供相应的文本动态数据。代码如下:


const {ccclass, property} = cc._decorator;
@ccclass
export default class GameViewLogic extends bb.BaseComponent {
    // 为多语言文本提供动态的数据绑定
    _t1001: any[] = [];
    get T1001() {
        this._t1001 = [599, 339, 444];
        return this._t1001;
    }
}


多语言配置如下:

en.json
{
    "1001": "水电费是否地方{#0},{#1}, {#2}",
    "1002": "大股东给对方"
}
zh.json
{
    "1001": "dfsfsfsdf{#0},{#1}, {#2}",
    "1002": "34fdggdf"
}