blob2d
Version:
Typed Modular 2D Game Engine for Web
135 lines (115 loc) • 3.59 kB
text/typescript
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});
}
}