UNPKG

blob2d

Version:

Typed Modular 2D Game Engine for Web

135 lines (115 loc) 3.59 kB
import {EventEmitter} from 'eventemitter3'; import {Container} from 'pixi.js'; import {Element} from './Element'; import {IAddon, IConstructor} from './types'; export type TOwnEvents = | 'mount' | 'unmount' | 'elementAdded' | 'elementRemoved'; export class Scene< TAddons extends {}, TEvents extends string > extends EventEmitter<TEvents | TOwnEvents> { public readonly addons: TAddons; public readonly foreground: Container; public readonly background: Container; public readonly graphics: Container; // processing private _addonsList: IAddon[] = []; private _removeStack: Element<TAddons, TEvents>[] = []; private _removeIndex: number = 0; constructor(BaseContainer: IConstructor<Container>) { super(); this.addons = {} as TAddons; // renderer layers this.foreground = new BaseContainer(); this.background = new BaseContainer(); this.graphics = new BaseContainer(); this.graphics.addChild(this.background, this.foreground); } /** * Should be called in the constructor before * accessing any addons of the current scene. */ public registerAddons(addons: TAddons) { // TODO: better way to initialize addons in constructor // @ts-ignore this.addons = addons; // @ts-ignore this._addonsList = Object.values(addons); } /** * Adds one or many elements. */ public addElement<T extends Element<TAddons, TEvents>[]>(...elems: T) { // bypass loop for one element if (elems.length > 1) { for (let i = 0; i < elems.length; i++) { this.addElement(elems[i]); } } else { const elem = elems[0]; if (elem.scene) { if (elem.scene === this) { const elemName = elem.name || elem.constructor.name; const sceneName = this.constructor.name; return console.error( `The "${elemName}" [Element] is already added to the "${sceneName}" [Scene].` ); } elem.scene.removeElement(elem); } elem.scene = this; this.foreground.addChild(elem.display); this.emit('elementAdded', elem); } } /** * Removes one or many elements. */ public removeElement<T extends Element<TAddons, TEvents>[]>(...elems: T) { // bypass loop for one element if (elems.length > 1) { for (let i = 0; i < elems.length; i++) { this._removeStack[this._removeIndex++] = elems[i]; } } else { this._removeStack[this._removeIndex++] = elems[0]; } } private unsafeRemoveElement<T extends Element<TAddons, TEvents>>(elem: T) { if (this.foreground.removeChild(elem.display)) { this.emit('elementRemoved', elem); elem.scene = null; } } /** * Updates registered addons and perform the actual * removal of garbage collected elements. */ public update(deltaTime: number) { // addons or traits may request to remove an element for (let i = 0; i < this._addonsList.length; i++) { this._addonsList[i].update(deltaTime); } // actual removing phase while (this._removeIndex > 0) { this.unsafeRemoveElement(this._removeStack[--this._removeIndex]); } } /** * Clears all added events and destroys addons * and elements removing them from the renderer. */ public destroy() { this.removeAllListeners(); for (let i = 0; i < this._addonsList.length; i++) { this._addonsList[i].destroy(); } this._addonsList.length = 0; this._removeStack.length = 0; // remove all pixijs dependencies this.graphics.destroy({children: true}); } }