controller_live2d.js

import { EventEmitter } from 'eventemitter3';
import defaultOptions from '../config/options.json';
import { DLive2dOptions } from '../models/index.js';
import { FBasePlugin, plugins } from '../plugins/index.js';
import { EEvent, FHelp } from '../utils/index.js';
import { UModelController } from './model.js';
import { UStageController } from './stage.js';
import { UTipsController } from './tips.js';

/**
 * @class
 * @summary live2d 控制器
 * @classdesc 用于整合 stage, model 等其他控制器, 并否则插件的安装与卸载等等
 * @memberof module:controller
 * @alias ULive2dController
 */
export class ULive2dController {
  /**
   * PIXI.Application 实例
   * @summary app 实例
   * @protected
   * @type {TApplication}
   */
  _app;

  /**
   * Live2d 数据集合, 用于存储 live2d 对应的数据
   * @protected
   * @summary Live2d数据
   * @type {DLive2dOptions}
   */
  _data;

  /**
   * EventEmitter3 实例, 更多请查看 [EventEmitter3]{@link https://www.jsdocs.io/package/eventemitter3}
   * @protected
   * @summary event 实例
   * @type {EventEmitter}
   */
  _event;

  /**
   * 负责存储所有插件实例的数组
   * @protected
   * @summary 插件集
   * @type {FBasePlugin[]}
   */
  _plugins;

  /**
   * 用于控制 stage 相关的的控制器
   * @summary stage 控制器
   * @protected
   * @type {UStageController}
   */
  _stage;

  /**
   * 用于控制模型相关的的控制器
   * @summary model 控制器
   * @protected
   * @type {UModelController}
   */
  _model;

  /**
   * 用于控制 tips 相关的的控制器
   * @summary tips 控制器
   * @protected
   * @type {UTipsController}
   */
  _tips;

  /**
   * 以键值对进行记录的对象引用
   * @summary 对象引用
   * @protected
   * @type {Record<string, any>}
   */
  _ref;

  /**
   * 创建 live2d 控制器
   * @summary live2d 控制器构造
   * @param {DLive2dOptions} options Live2d 数据
   * @throws {Error} PIXI.Application is null
   */
  constructor(options) {
    this._data = options = new DLive2dOptions(FHelp.mergeAll(defaultOptions, options));
    this._event = new EventEmitter();
    this._ref = {};
    // 控制器
    this._stage = new UStageController(this, options.selector);
    this._model = new UModelController(this, options.models);
    this._tips = new UTipsController(this, options.tips);
    this._plugins = options.plugins.filter(p => FHelp.is(FBasePlugin, p));
    // 创建 app 实例
    if (FHelp.isNotValid(PIXI?.Application)) {
      throw Error('PIXI is not import');
    }
    this._app = /** @type {TApplication} */new PIXI.Application({
      view: this._stage.canvas,
      backgroundAlpha: 0,
      backgroundColor: 0x000000,
      resolution: 2,
      autoStart: true,
      autoDensity: true,
      resizeTo: this._stage.wrapper
    });
    this.init();
  }

  /**
   * getter:  Application 实例
   * @summary app 实例
   * @type {TApplication}
   * @readonly
   */
  get app() {
    return this._app;
  }

  /**
   * getter:  live2d 原始数据
   * @summary live2d 数据
   * @type {DLive2dOptions}
   * @readonly
   */
  get data() {
    return this._data;
  }

  /**
   * getter:  EventEmitter3 实例
   * @summary event 实例
   * @type {EventEmitter}
   * @readonly
   */
  get event() {
    return this._event;
  }

  /**
   * getter: 获取 stage 控制器实例
   * @summary stage 控制器
   * @type {UStageController}
   * @readonly
   */
  get stage() {
    return this._stage;
  }

  /**
   * getter: 获取 model 控制器实例
   * @summary model 控制器
   * @type {UModelController}
   * @readonly
   */
  get model() {
    return this._model;
  }

  /**
   * getter: 获取 tips 控制器实例
   * @summary tips 控制器
   * @type {UTipsController}
   * @readonly
   */
  get tips() {
    return this._tips;
  }

  /**
   * getter: 获取所有插件实例数组
   * @summary 插件集
   * @type {FBasePlugin[]}
   * @readonly
   */
  get plugins() {
    return this._plugins;
  }

  /**
   * getter: 以键值对进行记录的对象引用
   * @summary 对象引用
   * @type {Record<string, any>}
   * @readonly
   */
  get ref() {
    return this._ref;
  }

  /**
   * 传递 options 数据, 并创建一个 live2d 实例
   * @summary 创建 live2d 实例
   * @param {DLive2dOptions | null} [options=null] live2d 选项
   * @return {ULive2dController}  live2d 实例
   * @static
   */
  static create(options = null) {
    return new ULive2dController(options);
  }

  /**
   * 如果 plugin 不是 FBasePlugin 的子类则不会进行安, 否则根据插件的优先级进行安装
   * @summary 安装插件
   * @template T FBasePlugin 的子类
   * @param {...Extract<T, FBasePlugin>} plugins 插件实例集
   */
  installPlugin(...plugins) {
    // 插件名称集合
    const names = this.plugins.reduce((names, current) => {
      names[current.name] = current.name;
      return names;
    }, {});
    // 筛选出有效插件,并且按照优先级从高到低排列及执行
    plugins = plugins.filter(plugin => FHelp.is(FBasePlugin, plugin) && !names[plugin.name])
                     .sort((prev, next) => next.priority - prev.priority);
    this.plugins.push(...plugins);
    this.plugins.sort((prev, next) => next.priority - prev.priority);
    for (const plugin of plugins) {
      plugin.install(this);
    }
  }

  /**
   * 从插件集中卸载指定的插件
   * @summary 卸载插件
   * @template T FBasePlugin 的子类
   * @param {...Extract<T, FBasePlugin>} plugins 插件实例集
   */
  uninstallPlugin(...plugins) {
    for (const plugin of plugins) {
      const index = this.plugins.indexOf(plugin);
      if (index >= 0) {
        plugin.uninstall(this);
        this.plugins.splice(index, 1);
      }
    }
  }

  /**
   * 开始安装插件并进行 live2d 控制器初始化
   * @summary 初始化控制器
   * @fires EEvent#init 控制器初始化事件
   */
  init() {
    // 安装插件
    this.installPlugin(...Object.values(plugins).map(T => new T));
    // this.installPlugin(new FSwitchTexturePlugin, new FSwitchModulePlugin, new FCapturePlugin, new FInfoPlugin, new FQuitPlugin, new FDragPlugin);
    // say hello
    this._data.sayHello && FHelp.sayHello();
    // init 事件通知
    this._event.emit(EEvent.init);
  }

  /**
   * 卸载插件, 销毁控制器, 销毁 app 实例
   * @summary 销毁控制器
   * @fires EEvent#destroy 控制器销毁事件
   * @param {boolean} [removeView=false] 从 DOM 中移除 Canvas 元素
   */
  destroy(removeView = false) {
    const plugins = this.plugins;
    this.uninstallPlugin(...plugins);
    plugins.splice(0, plugins.length);
    this.event.emit(EEvent.destroy);
    this.app.renderer.destroy(removeView);
  }
}