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;