gibbon.js
Version:
Actor/Component system for use with pixi.js.
409 lines • 11.8 kB
JavaScript
import { Point, Container } from 'pixi.js';
import * as PIXI from 'pixi.js';
import { quickSplice } from '../utils/array-utils';
import Game from '../game';
import Component from './component';
/**
*
*/
export default class GameObject {
/**
* @property {Game} game
*/
get game() { return Game.current; }
/**
* @property {Group} group - owning group of the gameObject, if any.
*/
get group() { return this._group; }
set group(v) { this._group = v; }
/**
* @property {string} name - Name of the GameObject.
*/
get name() { return this._name; }
set name(v) { this._name = v; }
/**
* @property {boolean} active
*/
get active() { return this._active; }
set active(v) {
if (this._active != v) {
this._active = v;
if (v) {
for (let comp of this._components) {
comp.onActivate?.();
}
}
else {
this._components.every(v => v.onDeactivate?.());
}
}
}
/**
* @property {Point} position - Position of the object and Display clip.
*/
get position() { return this._position; }
set position(v) { this._position.set(v.x, v.y); }
/**
* @property {number} x
*/
get x() { return this._position.x; }
set x(v) { this._position.x = v; }
/**
* @property {number} y
*/
get y() { return this._position.y; }
set y(v) { this._position.y = v; }
/**
* @property {number} rotation - Rotation in radians.
*/
get rotation() { return this._rotation; }
set rotation(v) {
if (v > 2 * Math.PI || v < -2 * Math.PI)
v %= 2 * Math.PI;
this._rotation = v;
if (this.clip != null) {
this.clip.rotation = v;
}
}
get width() { return this.clip?.getBounds().width ?? 0; }
get height() { return this.clip?.getBounds().height ?? 0; }
/**
* @property {boolean} interactive - Set the interactivity for the GameObject.
* The setting is ignored if the GameObject has no clip.
*/
get interactive() { return this.clip?.interactive ?? false; }
set interactive(v) {
if (this.clip) {
this.clip.interactive = v;
}
}
get sleeping() { return this._sleep; }
set sleep(v) { this._sleep = v; }
/**
* @property {Point} orient - returns the normalized orientation vector of the object.
*/
get orient() {
let rads = this.rotation;
return new Point(Math.cos(rads), Math.sin(rads));
}
get visible() { return this.clip?.visible ?? false; }
set visible(v) {
if (this.clip != null) {
this.clip.visible = v;
}
}
/**
* @property {boolean} isAdded - true after GameObject has been added to Engine.
*/
get isAdded() { return this._isAdded; }
/**
* @property {boolean} destroyed
*/
get isDestroyed() { return this._destroyed; }
/**
* @property {DisplayObject} clip - clip of the gameObject.d.
*/
clip;
_components;
/**
* Object was destroyed and should not be used any more.
*/
_destroyed = false;
/**
* Game object was added to engine.
*/
_isAdded = false;
emitter;
_sleep = false;
_name;
_destroyOpts;
_active = false;
_rotation = 0;
_position;
_group = null;
flags = 0;
_compMap;
/**
*
* @param {DisplayObject} [clip=null]
* @param {Point} [pos=null]
*/
constructor(clip, pos) {
this._components = [];
this._compMap = new Map();
this._isAdded = false;
this._name = '';
if (clip != null) {
if (pos) {
clip.position.set(pos.x, pos.y);
}
this._position = clip.position;
this._destroyOpts = {
children: true,
texture: false,
baseTexture: false
};
}
else {
this._position = pos || new Point(0, 0);
}
this._active = true;
this.flags = 0;
this.emitter = clip || new PIXI.utils.EventEmitter();
this.clip = clip;
}
/**
* Override in subclass.
*/
added() { }
pause() { }
unpause() { }
/**
* Called when GameObject is added to engine.
* Calls init() on all components and self.added()
*/
_added() {
this._isAdded = true;
let len = this._components.length;
for (let i = 0; i < len; i++) {
this._components[i]._init(this);
}
if (this._active) {
console.log(`activating components...`);
for (let i = 0; i < len; i++) {
this._components[i].onActivate?.();
}
}
this.added();
}
/**
*
* @param {string} evt
* @param {function} func
* @param {*} [context=null]
* @returns {PIXI.utils.EventEmitter}
*/
on(evt, func, context) {
if (this.clip != null) {
return this.clip.on(evt, func, context);
}
else {
return this.emitter.on(evt, func, context);
}
}
/**
* Emit an event through the underlying gameObject clip. If the gameObject
* does not contain a clip, the event is emitted through a custom emitter.
* @param {*} args - First argument should be the {string} event name.
*/
emit(event, ...args) {
this.emitter.emit.call(this.emitter, event, ...args);
}
/**
* Wrap emitter off()
* @param {...any} args
*/
off(e, fn, context) {
this.emitter.off(e, fn, context);
}
/**
* @deprecated Use addInstance<> instead.
* @param inst
* @param cls
*/
addExisting(inst, cls) {
return this.addInstance(inst, cls);
}
/**
* Add an existing component to the GameObject.
* @param {Component} inst
* @param {?Object} [cls=null]
* @returns {Component} Returns the instance.
*/
addInstance(inst, cls) {
const key = cls ?? inst.constructor ?? Object.getPrototypeOf(inst).constructor ?? inst;
this._components.push(inst);
this._compMap.set(key, inst);
if (this._isAdded) {
inst._init(this);
if (this._active) {
inst.onActivate?.();
}
}
return inst;
}
/**
* Instantiate and add a component to the GameObject.
* @param {class} cls - component class to instantiate.
* @returns {Object}
*/
add(cls) {
if (cls instanceof Component) {
return this.addInstance(cls);
}
else {
return this.addInstance(new cls(), cls);
}
}
/**
* Checks if the Object's clip contains a global point.
* Always false for objects without clips or clip.hitAreas.
* @param {Vector|Object} pt
* @param {number} pt.x
* @param {number} pt.y
* @returns {boolean}
*/
contains(pt) {
let clip = this.clip;
if (clip == null)
return false;
if (!clip.hitArea) {
return clip.getBounds().contains(pt.x, pt.y);
}
pt = clip.toLocal(pt);
return clip.hitArea.contains(pt.x, pt.y);
}
/**
*
* @param {number} x
* @param {number} y
*/
translate(x, y) {
this._position.set(this._position.x + x, this._position.y + y);
}
/**
* Determine if GameObject contains a Component entry
* under class or key cls.
* @param {*} cls - class or key of component.
*/
has(cls) {
return this._compMap.has(cls);
}
/**
*
* @param {*} cls
*/
get(cls) {
let inst = this._compMap.get(cls);
if (inst !== undefined)
return inst;
for (let i = this._components.length - 1; i >= 0; i--) {
if (this._components[i] instanceof cls)
return this._components[i];
}
return undefined;
}
/**
*
* @param {*} cls
*/
require(cls) {
let inst = this._compMap.get(cls);
if (inst !== undefined && inst instanceof cls)
return inst;
for (let i = this._components.length - 1; i >= 0; i--) {
if (this._components[i] instanceof cls)
return this._components[i];
}
return this.add(cls);
}
/**
* Creates a copy of the given component and adds it
* to this GameObject.
* @param {Component} comp
*/
addCopy(comp) {
let copy = Object.assign(Object.create(Object.getPrototypeOf(comp)), comp);
return this.addInstance(copy);
}
/**
*
* @param {number} delta - Tick time in seconds.
*/
update(delta) {
let comps = this._components;
for (let i = comps.length - 1; i >= 0; i--) {
var comp = comps[i];
if (comp._destroyed === true) {
quickSplice(comps, i);
continue;
}
if (comp.update && comp.sleep !== true && comp.enabled === true) {
comp.update(delta);
}
}
}
/**
*
* @param {Component} comp - the component to remove from the game object.
* @param {bool} [destroy=true] - whether the component should be destroyed.
*/
remove(comp, destroy = true) {
if (destroy === true)
comp._destroy();
this._compMap.delete(comp.constructor || comp);
return true;
}
show() {
if (this.clip != null) {
this.clip.visible = true;
}
}
hide() {
if (this.clip != null) {
this.clip.visible = false;
}
}
/**
* Set options for destroying the PIXI DisplayObject when
* the GameObject is destroyed.
* @param {boolean} children
* @param {boolean} texture
* @param {boolean} baseTexture
*/
setDestroyOpts(children, texture, baseTexture) {
if (this._destroyOpts == null) {
this._destroyOpts = {
children: children,
texture: texture,
baseTexture: baseTexture
};
}
else {
this._destroyOpts.children = children;
this._destroyOpts.texture = texture;
this._destroyOpts.baseTexture = baseTexture;
}
}
/**
* Call to destroy the game Object.
* Do not call _destroy() directly.
*/
destroy() {
this._destroyed = true;
let comps = this._components;
for (let i = comps.length - 1; i >= 0; i--) {
this.remove(comps[i]);
}
}
/**
* destroys all components and then the GameObject itself.
*/
_destroy() {
this.emitter.emit('destroy', this);
this.emitter.removeAllListeners();
if (this.clip) {
if (this.clip instanceof Container && this._destroyOpts) {
this.clip.destroy(this._destroyOpts);
}
else {
this.clip.destroy();
}
}
/*this._position = null;
this._emitter = null;
this._compMap = null;
this._components = null;*/
}
}
//# sourceMappingURL=game-object.js.map