Source: tiny/core/Application.js

import Device from 'ismobilejs';
import TWEEN from '../../libs/tween.js';
import Container from './display/Container';
import { shared } from './ticker';
import { config } from './settings';
import { RENDERER_TYPE, WIN_SIZE } from './const';
import * as utils from './utils';
import Text from './text/Text';
import Transition from '../transitions/Transition';
import CanvasRenderer from './renderers/canvas/CanvasRenderer';
import WebGLRenderer from './renderers/webgl/WebGLRenderer';

/**
 * 故事从这里开始
 *
 * @example
 *
 * // 定义启动参数
 * var config = {
 *   showFPS: true,
 *   renderOptions: {
 *     backgroundColor: 0x2a3145
 *   }
 * };
 * // 新建App
 * var app = new Tiny.Application(config);
 * // 通过 fromImage 实例化精灵
 * var sprite = Tiny.Sprite.fromImage('https://zos.alipayobjects.com/rmsportal/nJBojwdMJfUqpCWvwyoA.png');
 * // 启动
 * app.run(sprite);
 *
 * @class
 * @memberof Tiny
 */
export default class Application {
  /**
   * @param {Tiny.config} conf 启动参数
   */
  constructor(conf) {
    this._fps = 0;

    //游戏是否已经停止
    this._paused = true;

    this._updatePoll = {};

    /**
     * Ticker for doing render updates.
     *
     * @member {Tiny.ticker.Ticker}
     */
    this.ticker = shared;

    /**
     * 所有显示类的根容器
     *
     * @member {Tiny.Container}
     */
    this.camera = new Container();

    /**
     * 舞台对象,用户创建的显示类都会添加到这个对象中
     *
     * @member {Tiny.Container}
     */
    this.stage = new Container();
    this.camera.addChild(this.stage);

    this.setup(conf);

    if (config.showFPS) {
      this._createStatsLabel();
    }

    /**
     * WebGL renderer if available, otherwise CanvasRenderer
     * @member {Tiny.WebGLRenderer|Tiny.CanvasRenderer}
     * @example
     * var renderer = app.renderer
     */
    this.renderer = this.autoDetectRenderer(config.newWidth, config.newHeight, {
      view: this.view,
    });

    WIN_SIZE.width = Math.round(this.renderer.width);
    WIN_SIZE.height = Math.round(this.renderer.height);
  }

  /**
   * Render the current camera.
   */
  render() {
    this.renderer.render(this.camera);
  }

  /**
   * 停止
   * 注意:该方法已不推荐使用,请直接使用 `pause` 方法
   *
   * @deprecated since version 1.1.7
   */
  stop() {
    this.pause();
  }

  /**
   * 开始
   */
  start() {
    if (!this._paused) {
      return;
    }
    this._paused = false;
    shared.add(Application._tweenUpdateFn);
    shared.start();
  }

  /**
   * 恢复
   *
   * - 恢复暂停的 `Tiny.ticker.shared` 下的所有事件(含主调度)
   * - 恢复暂停的 `Tiny.TWEEN` 动画
   * - 恢复暂停的 `CountDown` 实例
   * - 恢复暂停的 `tinyjs-plugin-audio` 的 Audio 实例
   */
  resume() {
    if (!this._paused) {
      return;
    }
    this._paused = false;
    try {
      shared.start();
      utils.CountDownCache.forEach((cd) => {
        if (!cd.isManualPause()) {
          cd.start();
        }
      });
      Tiny.TWEEN.resume();
      Tiny.audio.manager.resume();
    } catch (e) {
    }
  }

  /**
   * 暂停
   *
   * - 暂停 `Tiny.ticker.shared` 下的所有事件(含主调度)
   * - 暂停 `Tiny.TWEEN` 动画
   * - 暂停 `CountDown` 实例
   * - 暂停 `tinyjs-plugin-audio` 的 Audio 实例
   */
  pause() {
    if (this._paused) {
      return;
    }
    this._paused = true;
    try {
      shared.stop();
      utils.CountDownCache.forEach((cd) => {
        cd.pause(true);
      });
      Tiny.TWEEN.pause();
      Tiny.audio.manager.pause();
    } catch (e) {
    }
  }

  /**
   * 是否暂停中
   *
   * @return {boolean}
   */
  isPaused() {
    return this._paused;
  }

  /**
   *
   * @private
   * @param {number}                width     - the width of the renderers view
   * @param {number}                height    - the height of the renderers view
   * @param {Tiny.RENDER_OPTIONS}   options   - The optional renderer parameters
   * @return {Tiny.WebGLRenderer|Tiny.CanvasRenderer} - Returns WebGL renderer if available, otherwise CanvasRenderer
   */
  autoDetectRenderer(width = 320, height = 568, options = {}) {
    Object.assign(options, config.renderOptions);

    if (config.renderType !== RENDERER_TYPE.CANVAS) {
      if (utils.isWebGLSupported()) {
        // console.log('WebGLRenderer');
        return new WebGLRenderer(width, height, options);
      }
    }
    // console.log('CanvasRenderer');
    return new CanvasRenderer(width, height, options);
  }

  /**
   * 启动某个场景
   *
   * @param {Tiny.DisplayObject}  startScene
   */
  run(startScene) {
    this.replaceScene(startScene);

    if (config.autoRender) {
      this.mainLoop();
    } else {
      //手动渲染一次
      this.render();
    }
  }

  /**
   * 切换场景,如果你想切换下一个场景,使用 replaceScene,还可以使用转场动画
   *
   * @example
   *
   * var app = new Tiny.Application({..});
   * app.replaceScene(scene, 'SlideInR', 800);
   *
   * @param {Tiny.DisplayObject}  scene         场景对象
   * @param {string}              transition    转场动画的字符,比如:Fade、MoveIn等。更多参照 {@link Tiny.Transition}
   * @param {number}              duration      转场动画时长(单位:ms)
   */
  replaceScene(scene, transition, duration) {
    if (transition) {
      const instance = new Transition(this.stage, scene, duration);
      const trans = transition;
      Array.prototype.splice.call(arguments, 0, 3);
      instance[ trans ](arguments);
    } else {
      this.stage.removeChildren();
      this.stage.addChild(scene);
    }
    this._currentScene = scene;
  }

  /**
   * @private
   */
  mainLoop() {
    shared.add(function (t) {
      this.render();

      for (const key in this._updatePoll) {
        if (this._updatePoll.hasOwnProperty(key)) {
          this._updatePoll[ key ].call(this, t);
        }
      }
    }, this);

    let __lastTime = 0;
    let __accumDt = 0;
    shared.add(function (t) {
      __accumDt++;
      const currentTime = performance.now();
      if (this._label && currentTime - __lastTime >= 1000) {
        const fps = __accumDt;
        this._fps = fps;
        this._label.text = `SPF: ${(1 / fps).toFixed(3)}\nFPS: ${fps.toFixed(1)}`;
        __accumDt = 0;
        __lastTime = currentTime;
      }
    }, this);

    // Start the rendering
    this.start();
  }

  static _tweenUpdateFn() {
    TWEEN.update();
  }

  /**
   * 游戏的主调度
   *
   * @example
   *
   * var app = new Tiny.Application();
   * var fn = function() {
   *  console.log('update.');
   * }
   *
   * app.onUpdate(fn);
   *
   * @param {function}  fn
   * @param {boolean}   force 是否强制覆盖方法池中同一个方法
   */
  onUpdate(fn, force = false) {
    const key = fn.toString();
    if (!this._updatePoll[ key ] || force) {
      this._updatePoll[ key ] = fn;
    }
  }

  /**
   * 移除主调度中的某个方法
   *
   * @example
   *
   * var app = new Tiny.Application();
   * var fn = function() {
   *  console.log('update.');
   * }
   *
   * app.onUpdate(fn);
   *
   * // 5秒后移除fn
   * Tiny.ticker.shared.countDown({
   *  duration: 1e3,
   *  times: 5,
   *  complete: function () {
   *    app.offUpdate(fn);
   *  }
   * });
   *
   * @version 1.0.2
   * @param fn
   */
  offUpdate(fn) {
    const key = fn.toString();
    delete this._updatePoll[ key ];
  }

  /**
   *
   * @private
   * @param conf
   */
  setup(conf) {
    Object.assign(config, conf);

    if (Device.tablet || Device.phone) {
      //style设置
      const fontStyle = document.createElement('style');
      fontStyle.type = 'text/css';
      document.body.appendChild(fontStyle);

      fontStyle.textContent = 'body,canvas,div{ -moz-user-select: none;-webkit-user-select: none;-ms-user-select: none;-khtml-user-select: none;-webkit-tap-highlight-color:rgba(0,0,0,0);}';
    }
    let view = document.getElementById(config.canvasId);
    if (!view) {
      view = document.createElement('canvas');
      view.setAttribute('tabindex', 99);
      view.id = config.canvasId;
      view.style.outline = 'none';
      document.body.appendChild(view);
    }

    /**
     * 就是那个用于渲染故事的普通 `<canvas>` 画布对象
     *
     * @property view
     * @type {HTMLCanvasElement}
     */
    this.view = view;
    this.resize();
  }

  /**
   * @private
   */
  resize() {
    let multiplier;
    const winH = config.fixSize ? config.height : window.innerHeight;
    const winW = config.fixSize ? config.width : window.innerWidth;
    let cWidth = config.fixSize ? (config.width || winW) : config.referWidth;
    let radio = +config.orientation ? winH / cWidth : winW / cWidth;
    const isFullScreen = (config.width === winW && config.height === winH);
    isFullScreen && (radio = 1);
    const width = config.width * radio || winW;
    const height = config.height * radio || winH;
    let cHeight = cWidth * (+config.orientation ? width / height : height / width);

    if (+config.orientation) {
      const w = cWidth;
      cWidth = cHeight;
      cHeight = w;
    } else {
      // 竖屏
    }

    cWidth = cWidth * config.dpi;
    cHeight = cHeight * config.dpi;

    multiplier = Math.min((height / cHeight), (width / cWidth));
    multiplier = Number(multiplier.toFixed(4));

    config.renderOptions.resolution = Number((1 / multiplier).toFixed(4));
    config.renderOptions.autoResize = true;
    config.newWidth = Math.round(cWidth * multiplier);
    config.newHeight = Math.round(cHeight * multiplier);
    this.stage.setScale(multiplier);
  }

  /**
   *
   * @method _createStatsLabel
   * @return {HTMLElement}
   * @private
   */
  _createStatsLabel() {
    this._label = new Text('SPF: -\nFPS: -', {
      fontSize: 18,
      fontFamily: 'Helvetica',
      fill: '#ffffff',
      stroke: '#666666',
      strokeThickness: 0.2,
    });

    this._label.position.set(10, config.newHeight - this._label.height - 10);
    this.camera.addChild(this._label);
  }

  /**
   * 获取当前场景
   *
   * @version 1.1.7
   * @return {Tiny.DisplayObject}
   */
  getCurrentScene() {
    return this._currentScene;
  }

  /**
   * 获取当前帧率
   *
   * @version 1.1.7
   * @returns {number}
   */
  getCurrentFPS() {
    return this._fps;
  }

  /**
   * 设置或获取已设置的帧率
   *
   * @version 1.1.7
   * @member {number}
   */
  static get FPS() {
    return config.fps;
  }

  static set FPS(fps) {
    config.fps = fps;
  }

  /**
   * Destroy and don't use after this.
   * @param {Boolean} [removeView=false] Automatically remove canvas from DOM.
   */
  destroy(removeView) {
    TWEEN.removeAll();

    this.stage.destroy();
    this.stage = null;

    this.camera.destroy();
    this.camera = null;

    this.renderer.destroy(removeView);
    this.renderer = null;

    shared.stop();
  }
}
Documentation generated by JSDoc 3.4.3 on Thu May 31 2018 14:40:21 GMT+0800 (CST)