Source: Entity.js

const Vector = require('engine/Vector');
const EventEmitter = require('engine/EventEmitter');
const Behavior = require('engine/Behavior');
const { merge } = require('engine/utils/object');

/**
 * Base object that may contain a graphic element(as `gfx`)
 * and a collider instance(as `coll`).
 *
 * The `gfx` and `coll` share the same postion.
 *
 * @class Entity
 */
class Entity {
  /**
   * @constructor
   * @param {Number} x          X coordinate
   * @param {Number} y          Y coordinate
   * @param {Object} settings   Setting object to be merged in
   */
  constructor(x, y, settings) {
    /**
     * Each entity has a unique ID.
     * @memberof Entity#
     */
    this.id = ++Entity.nextId;

    /**
     * Name of this Entity, default is null.
     * @type {string}
     */
    this.name = null;

    /**
     * Real tag field
     * @type {string}
     * @private
     */
    this._tag = null;

    /**
     * Whether this actor is removed from game.
     * @type {boolean}
     */
    this.isRemoved = false;

    /**
     * Want this actor to be updated?
     * @type {boolean}
     * @default false
     */
    this.canEverTick = false;

    /**
     * Want this actor to be fixed-updated?
     * @type {boolean}
     * @default false
     */
    this.canFixedTick = false;

    /**
     * Graphic component.
     * @memberof Entity#
     */
    this.gfx = null;

    /**
     * Name of the layer that `gfx` will be added to while spawning.
     * @memberof Entity#
     * @type {String}
     */
    this.layer = null;

    /**
     * Collider component.
     * @memberof Entity#
     */
    this.coll = null;

    /**
     * Behavior hash map
     * @type {Object}
     */
    this.behaviors = {};

    /**
     * Behavior list
     * @type {Array}
     */
    this.behaviorList = [];

    /**
     * Events dispatcher
     * @type {EventEmitter}
     */
    this.events = new EventEmitter();

    /**
     * Position of this entity.
     * @memberof Entity#
     */
    this.position = new Vector(x, y);

    /**
     * Reference to the game this actor is added to.
     * @type {Game}
     * @default null
     */
    this.game = null;

    /**
     * Reference to the constructor for pooling.
     * @type {class}
     */
    this.CTOR = Entity;

    // Apply settings
    this.setup(settings);
  }

  /**
   * Get tag of this Entity
   * @type {string}
   */
  get tag() { return this._tag; }
  /**
   * Set tag of this Entity
   * @type {string}
   * @param {String} t Tag to set
   */
  set tag(t) {
    if (this.game) {
      this.game.changeEntityTag(this, t);
    }
    else {
      this._tag = t;
    }
  }

  /**
   * Poolable entity initialization (called immediately after picking from the pool)
   * @memberof Entity#
   * @param {Number} x        X coordinate
   * @param {Number} y        Y coordinate
   * @param {String} layer    Name of the layer to added to
   * @param {Object} settings Setting object
   * @return {Entity} Entity instance
   */
  init(x, y, layer, settings) {
    this.position.set(x, y);
    this.layer = layer;

    return this.setup(settings);
  }
  /**
   * Remove self from game
   * @memberof Entity#
   */
  remove() {
    if (this.game) {
      this.game.removeEntity(this);
    }
  }

  /**
   * Setup this entity with settings(deeply merge is used by default)
   * @param {Object} settings Settings
   * @return {Entity} Self for chaining
   */
  setup(settings) {
    merge(this, settings);
    return this;
  }

  /**
   * Will be called after this Entity is added to a game.
   * @method ready
   * @memberof Entity#
   */
  ready() {}
  /**
   * Add a behavior to this entity.
   * @param  {Object} behavior    Behavior to be added
   * @param  {Object} [settings]  Settings passed to this behavior
   * @return {Entity}             Self for chaining
   */
  behave(behavior, settings) {
    var bhv;
    switch (typeof(behavior)) {
      case 'function':
        bhv = new behavior();
        break;
      case 'string':
        bhv = new Behavior.types[behavior]();
        break;
      case 'object':
        bhv = behavior;
        break;
    }

    this.behaviors[bhv.type] = bhv;
    this.behaviorList.push(bhv);

    bhv.init(this, settings);

    return this;
  }
  /**
   * Update method to be called each frame. Set `canEverTick = true` to activate.
   * This method will only update behaviors by default,
   * no need to call `super.update` if you don't have any behaviors.
   *
   * @method update
   * @memberof Entity#
   * @param {Number} dt     Delta time in millisecond
   * @param {Number} dtSec  Delta time in second
   */
  update(dt, dtSec) {
    let i;
    for (i = 0; i < this.behaviorList.length; i++) {
      this.behaviorList[i].update(dt, dtSec);
    }
  }
  /**
   * Update method to be called each fixed step. Set `canFixedTick = true` to activate.
   * This method will only update behaviors by default,
   * no need to call `super.update` if you don't have any behaviors.
   *
   * @method fixedUpdate
   * @memberof Entity#
   * @param {Number} dt     Delta time in millisecond
   * @param {Number} dtSec  Delta time in second
   */
  fixedUpdate(dt, dtSec) {
    let i;
    for (i = 0; i < this.behaviorList.length; i++) {
      this.behaviorList[i].fixedUpdate(dt, dtSec);
    }
  }
}
/**
 * ID of next Entity instance
 * @type {Number}
 * @static
 */
Entity.nextId = 0;

/**
 * Entity class map.
 * @type {Object}
 */
Entity.types = {};
Entity.register = function(type, ctor) {
  if (!Entity.types[type]) {
    Entity.types[type] = ctor;
  }
  else {
    console.log('[WARNING]: "' + type + '" entity is already registered!');
  }
};

/**
 * Entity base class is not poolable.
 * @type {Boolean}
 * @static
 */
Entity.canBePooled = false;

/**
 * @exports engine/Entity
 */
module.exports = Entity;