gibbon.js
Version:
Actor/Component system for use with pixi.js.
335 lines (259 loc) • 8.24 kB
text/typescript
import { Assets } from 'pixi.js';
import { DisplayObject, Container, Point, Application, Ticker, Rectangle } from 'pixi.js';
import { LayerManager, LayerOptions } from './layerManager';
import { Engine } from './engine';
import { Actor } from './core/actor';
import { Camera } from './components/camera';
import { Group } from './core/group';
import { Library } from './library';
import { Tween } from 'tweedle.js';
import { tweenOf } from './utils/tweens';
import { contains } from './utils/array-utils';
import EventEmitter from 'eventemitter3';
import type { IUpdater } from './engine';
import { EngineEvent } from './events/engine-events';
import { Constructor } from './utils/types';
import { IPoint } from './data/geom';
/**
* Extendable Game class.
*/
export class Game {
static current: Game;
/**
* @property {Application} app
*/
get app(): Application { return this._app; }
/**
* renderer for application.
* Convenience accessor. Cache for quick access.
*/
get renderer() { return this._app.renderer; }
/**
* @property {PIXI.Container} stage
*/
get stage() { return this._app.stage; }
/**
* @property loader
*/
get loader() { return this._loader; }
set loader(v) { this._loader = v; }
/**
* @property {PIXI.Rectangle} screen - Screen/View Rectangle.
*/
get screen() { return this._app.screen; }
get camera() { return this._camera; }
/**
* @property {Actor} root - Actor containing the main Camera component
* and base objectLayer.
* Basic game systems can also be added to root as Components.
*/
get root(): Actor { return this._root; }
get objectLayer(): Container { return this.engine.objectLayer!; }
get uiLayer(): Container { return this.layerManager._uiLayer!; }
/**
* @property {Container} backgroundLayer
*/
get backgroundLayer() { return this._layerManager?.background; }
/**
* @property ticker - Game Ticker.
*/
get ticker() { return this.engine.ticker; }
/**
* @property sharedTicker - Shared non-game ticker. (UI Elements, nonpausing effects.)
*/
get sharedTicker() { return Ticker.shared; }
/**
* @property emitter - Game-level Emitter. By default, the PIXI shared EventEmitter.
*/
get emitter() { return this._emitter; }
/**
* @property {InteractionData} mouseInfo - convenience accessor for global mouse information.
*/
get mouseInfo() { return this.renderer.plugins.interaction.mouse; }
get groups() { return this._groups; }
/**
* @property {Engine} engine
*/
get engine() { return this._engine; }
/**
* @property {LayerManager} layerManager
*/
get layerManager(): LayerManager { return this._layerManager!; }
private readonly _app: Application;
private _loader: typeof Assets;
private readonly _groups: Group[] = [];
/**
* Root object. Not accessible before init() is called.
*/
private _root!: Actor;
private _emitter: EventEmitter = new EventEmitter();
private _engine: Engine;
readonly library: Library = new Library();
private _layerManager?: LayerManager;
private _camera?: Camera;
/**
*
* @param app - The pixi application, or options object.
*/
constructor(app: Application) {
this._app = app;
this.app.stage.interactive = true;
this.app.stage.hitArea = this._app.screen;
Game.current = this;
this._loader = Assets;
this._engine = new Engine(new Ticker());
}
/**
* After init(), layerManager and game layers are available for use.
*/
init(layerOptions?: LayerOptions) {
const layerManager = new LayerManager(this, layerOptions);
this._layerManager = layerManager;
this._engine.objectLayer = layerManager.objectLayer;
this._root = new Actor(layerManager.objectLayer);
this._camera = this.root.add(Camera, this._app.screen);
this.addActor(this._root);
}
/**
* Start the game object ticker and engine ticker.
*/
start() {
this.engine.start();
}
pause() { this._engine.stop(); }
unpause() { this._engine.start(); }
/**
* Size the game to the full browser window.
* @returns {function} The resize event listener, so it can be removed later.
*/
fullscreen() {
const w = window.visualViewport ? Math.ceil(window.visualViewport.width) : document.body.clientWidth;
const h = window.visualViewport ? Math.ceil(window.visualViewport.height) : document.body.clientHeight;
this.app.renderer.resize(w, h);
const resizer = () => {
const w = window.visualViewport ? Math.ceil(window.visualViewport.width) : document.body.clientWidth;
const h = window.visualViewport ? Math.ceil(window.visualViewport.height) : document.body.clientHeight;
this.app.renderer.resize(
w,
h);
this._emitter.emit(EngineEvent.ScreenResized, { x: 0, y: 0, width: w, height: h });
};
window.addEventListener('resize', resizer);
return resizer;
}
/**
* Wrapper for default game event emitter.
* @param {string} event
* @param {Function} func
* @param {*} [context=null]
* @returns {PIXI.utils.EventEmitter}
*/
on(event: string, func: (...args: any[]) => void, context?: any) {
return this._emitter.on(event, func, context);
}
/**
* Emit event with game emitter.
* @param {...any} args
*/
emit(evt: string, ...args: any[]) {
this._emitter.emit(evt, ...args);
}
off(evt: string, fn?: (...args: any[]) => void, context?: any) {
return this._emitter.off(evt, fn, context);
}
getGroup<G extends Group>(type: Constructor<G>): G | undefined {
for (let i = this._groups.length - 1; i >= 0; i--) {
if (this._groups[i] instanceof type) {
return this._groups[i] as G;
}
}
}
findGroup(name: string): Group | undefined {
return this._groups.find((g) => g.name === name);
}
/**
* Add group to game. Attempting to add the same group multiple
* times to a game will be ignored.
* @param g
*/
addGroup(g: Group) {
if (!contains(this._groups, g)) {
this._groups.push(g);
g._onAdded(this);
}
}
/**
* Remove group from game.
* @param {Group} g
* @returns {boolean} True if g was found and removed.
*/
removeGroup(g: Group): boolean {
const ind = this._groups.indexOf(g);
if (ind >= 0) {
this._groups.splice(ind, 1);
g._onRemoved();
return true;
}
return false;
}
/**
* Wrapper for Engine.add(actor)
* @param {Actor} actor
*/
addActor(actor: Actor) {
this._engine.add(actor);
}
/**
*
* @param {*} sys
*/
addUpdater(sys: IUpdater) { this._engine.addUpdater(sys); }
/**
*
* @param {*} sys
*/
removeUpdater(sys: IUpdater) { this._engine.removeUpdater(sys); }
/**
* Wraps engine.Instantiate()
* Instantiate a Actor with a clip or a named clonable object from the library.
*/
instantiate(clip?: DisplayObject | null | string, loc?: IPoint) {
const view = (typeof clip === 'string') ? this.library.instance<DisplayObject>(clip) : clip;
const go = new Actor(view ?? undefined, loc);
this.engine.add(go);
return go;
}
/**
* Create an empty game object with a Container clip.
*/
makeContainer(loc?: IPoint) {
return this.instantiate(new Container(), loc);
}
/**
* Create empty game object with no clip.
*/
makeEmpty(loc?: IPoint) {
return this.instantiate(null, loc);
}
/**
* Replace existing tweens on the target with a newly created one.
* Convenience accesor for setting config data.
* @param {*} target
* @param {object} config
* @param {?number} time
* @returns {Tween} - The tween created.
*/
replaceTween<T extends Object>(target: T, props: Record<string, any>, time?: number): Tween<T> {
return tweenOf(target).to(props, time);
}
/**
* Convenience function for creating new tween.
* @param {*} target - target of the Tween.
* @param {Object} config - configuration object for gsap tween.
* @param {?number} time - tween time.
* @returns {Tween}
*/
createTween<T extends Object>(target: T, props: any, time?: number) {
return new Tween(target).to(props, time);
}
}