Source: gfx/index.js

const core = require('engine/core');
const System = require('engine/system');
const Vector = require('engine/Vector');
const { removeItems } = require('engine/utils/array');
const WebGLRenderer = require('./core/renderers/webgl/WebGLRenderer');
const CanvasRenderer = require('./core/renderers/canvas/CanvasRenderer');
const { isWebGLSupported } = require('./core/utils');
const CONST = require('./const');
const Node = require('./Node');
const config = require('game/config');

// General asset middlewares (including texture support)
const loader = require('engine/loader');
const { Resource } = loader;
const blobMiddlewareFactory = require('engine/loader/middlewares/parsing/blob').blobMiddlewareFactory;
const textureParser = require('./loaders/textureParser');
const spritesheetParser = require('./loaders/spritesheetParser');
const bitmapFontParser = require('./loaders/bitmapFontParser');
Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT);

// - parse any blob into more usable objects (e.g. Image)
loader.use(blobMiddlewareFactory());
// - parse any Image objects into textures
loader.use(textureParser());
// - parse any spritesheet data into multiple textures
loader.use(spritesheetParser());
// - parse any spritesheet data into multiple textures
loader.use(bitmapFontParser());

// System
let sharedRenderer = null;
const Zero = Vector.create(0, 0);

/**
 * Graphic system.
 */
class SystemGfx extends System {
  /**
   * @constructor
   */
  constructor() {
    super();

    /**
     * Name of this system.
     * @memberof SystemGfx#
     * @type {String}
     */
    this.name = 'Gfx';

    if (!sharedRenderer) {
      let options = {
        view: core.view,
        transparent: config.gfx.transparent,
        antialias: config.gfx.antialias,
        preserveDrawingBuffer: config.gfx.preserveDrawingBuffer,
        resolution: core.resolution,
        roundPixels: true,
      };

      if (config.gfx.webgl && isWebGLSupported()) {
        sharedRenderer = new WebGLRenderer(config.width || 320, config.height || 200, options);
      }
      else {
        sharedRenderer = new CanvasRenderer(config.width || 320, config.height || 200, options);
      }

      // Setup default scale mode
      CONST.SCALE_MODES.DEFAULT = CONST.SCALE_MODES[config.gfx.scaleMode.toUpperCase()];
    }

    /**
     * Shared renderer instance.
     * @memberof SystemGfx#
     * @type {Renderer}
     */
    this.renderer = sharedRenderer;

    /**
     * Root drawing element.
     * @memberof SystemGfx#
     * @type {Node}
     */
    this.root = Node();
    this.root.system = this;

    /**
     * Map of layer containers.
     * @memberof SystemGfx#
     * @type {Object}
     */
    this.layers = {};

    /**
     * Background color cache
     * @type {Number|String}
     * @private
     */
    this._backgroundColor = 0x000000;

    this.timestamp = 0;
    this.delta = 0;

    this.animList = [];
  }

  /**
   * Background color setter
   * @param  {Number} c Color to set
   */
  set backgroundColor(c) {
    this.renderer.backgroundColor = this._backgroundColor = c;
  }
  /**
   * Background color getter
   * @return {Number} Background color as number
   */
  get backgroundColor() {
    return this._backgroundColor;
  }

  /**
   * Mouse position getter. Require the `engine/gfx/interaction` before
   * using.
   * @return {Vector} Mouse position
   */
  get mouse() {
    if (this.renderer.plugins.interaction) {
      return this.renderer.plugins.interaction.mouse.global;
    }
    else {
      return Zero.set(0, 0);
    }
  }

  /**
   * Request animating an item
   * @param  {Node} item Item to be animated
   * @private
   */
  requestAnimate(item) {
    let idx = this.animList.indexOf(item);
    if (idx < 0) {
      this.animList.push(item);
    }
  }
  /**
   * Cancel the request of an animating
   * @param  {Node} item Item to be canceled
   * @private
   */
  cancelAnimate(item) {
    let idx = this.animList.indexOf(item);
    if (idx >= 0) {
      removeItems(this.animList, idx, 1);
    }
  }

  /**
   * Awake callback
   */
  awake() {
    this.renderer.backgroundColor = this._backgroundColor;
    this.timestamp = performance.now();
  }
  /**
   * Update callback
   */
  update() {
    this.renderer.render(this.root);
  }
  /**
   * Fixed update callback
   * @param {Number} delta Delta time in ms
   */
  fixedUpdate(delta) {
    this.delta = delta;

    for (let i = this.animList.length - 1; i >= 0; i--) {
      this.animList[i].update(this.delta);
    }
  }

  /**
   * Create a layer
   * @param  {String} name   Name of this layer
   * @param  {String} parent Which layer to add this into
   * @return {SystemGfx}     Self for chaining
   */
  createLayer(name, parent) {
    if (this.layers[name]) {
      console.log(`Layer "${name}" already exist!`);
      return this;
    }

    let c;
    if (!parent) {
      c = this.root;
    }
    else if (this.layers.hasOwnProperty(parent)) {
      c = this.layers[parent];
    }
    else {
      console.log(`Parent layer "${parent}" does not exist!`);
      return this;
    }

    this.layers[name] = Node().addTo(c);

    return this;
  }

  /**
   * Entity spawn callback
   * @param {Entity} ent Entity to be spawned
   */
  onEntitySpawn(ent) {
    let name = ent.layer;
    if (ent.gfx) {
      ent.gfx.entity = ent;
      // Default layer is the root
      if (!name) {
        this.root.addChild(ent.gfx);
      }
      // Find the layer and add this entitiy into it
      else if (this.layers.hasOwnProperty(name)) {
        this.layers[name].addChild(ent.gfx);
      }
      // Override gfx's position with the entity's
      ent.gfx.position = ent.position;
    }
  }
  /**
   * Entity remove callback
   * @param {Entity} ent Entity to be removed
   */
  onEntityRemove(ent) {
    if (ent.gfx) {
      ent.gfx.remove();
      ent.gfx.entity = null;
    }
  }
}

module.exports = SystemGfx;