UNPKG

@theatrejs/theatrejs

Version:

🎮 A JavaScript 2D Game Engine focused on creating pixel art games.

449 lines (348 loc) • 10.1 kB
import {Loop, MEDIA_TYPES, Stage, SystemActor, SystemAudio, SystemCollision, SystemInput, SystemRender, SystemVibration, UTILS, Vector2, Vector3} from '../index.js'; /** * Creates game engines. * * @example * * const engine = new Engine({$color, $container, $resolution}); * engine.initiate(60); * * await engine.preloadStage(SceneExample); * * engine.createStage(SceneExample); */ class Engine { /** * Stores the rendering background color. * @type {Vector3} * @private */ $color; /** * Stores the container. * @type {HTMLElement} * @private */ $container; /** * Stores the loop. * @type {Loop} * @private */ $loop; /** * Stores the next stage. * @type {typeof Stage} * @private */ $next; /** * Stores the preloaded status of the assets. * @type {Set<string>} * @private */ $preloaded; /** * Stores the rendering resolution. * @type {Vector2} * @private */ $resolution; /** * Stores the current stage. * @type {Stage} * @private */ $stage; /** * Stores the current actor system. * @type {SystemActor} * @private */ $systemActor; /** * Stores the current audio system. * @type {SystemAudio} * @private */ $systemAudio; /** * Stores the current collision system. * @type {SystemCollision} * @private */ $systemCollision; /** * Stores the current input system. * @type {SystemInput} * @private */ $systemInput; /** * Stores the current render system. * @type {SystemRender} * @private */ $systemRender; /** * Stores the current vibration system. * @type {SystemVibration} * @private */ $systemVibration; /** * Stores the uuid. * @type {string} * @private */ $uuid; /** * Gets the container. * @type {HTMLElement} * @public */ get container() { return this.$container; } /** * Gets the current stage. * @type {Stage} * @public */ get stage() { return this.$stage; } /** * Gets the uuid. * @type {string} * @public */ get uuid() { return this.$uuid; } /** * Creates a new game engine. * @param {object} [$parameters] The given parameters. * @param {Vector3} [$parameters.$color] The rendering background color to use. * @param {HTMLElement} [$parameters.$container] The container for the game engine to create. * @param {Vector2} [$parameters.$resolution] The rendering resolution to use. */ constructor({$color = new Vector3(0, 0, 0), $container = document.body, $resolution = new Vector2(320, 240)} = {}) { this.$color = $color; this.$container = $container; this.$resolution = $resolution; this.$uuid = UTILS.uuid(); this.$loop = new Loop(this.tick.bind(this)); this.$preloaded = new Set(); this.$systemActor = new SystemActor(); this.$systemAudio = new SystemAudio(); this.$systemCollision = new SystemCollision(); this.$systemInput = new SystemInput({$container: this.$container}); this.$systemRender = new SystemRender({$color: this.$color, $container: this.$container, $resolution: this.$resolution}); this.$systemVibration = new SystemVibration(); } /** * Creates the given stage. * @param {typeof Stage} $stage The stage to create. * @private */ $createStage($stage) { this.$stage = new $stage(this); this.$stage.onCreate(); } /** * Creates the given stage on the next tick update. * @param {typeof Stage} $stage The stage to create on the next tick update. * @public */ createStage($stage) { this.$next = $stage; } /** * Gets the current input state value of the given digital input. * @param {string} $input The event code of the given digital input. * @returns {boolean} * @public */ getInput($input) { return this.$systemInput.getInput($input); } /** * Gets the current input state value of the given analog input. * @param {string} $input The event code of the given analog input. * @returns {number} * @public */ getInputAnalog($input) { return this.$systemInput.getInputAnalog($input); } /** * Gets the position in the current stage from the given clipped position in the screen. * @param {Vector2} $vector The position in the screen (with values in [-1, 1] ranges). * @returns {Vector2} * @public */ getTranslationFromScreen($vector) { return this.$systemRender.getTranslationFromScreen(this.$stage, $vector); } /** * Checks if the engine has loaded the given asset. * @param {string} $asset The asset source. * @returns {boolean} * @public */ hasAssetLoaded($asset) { return this.$preloaded.has($asset) === true; } /** * Initiates the engine. * @param {number} [$tickrateMinimum] The minimum acceptable number of ticks per virtual second (in ticks/s). * @public */ initiate($tickrateMinimum = 60) { this.$systemInput.initiate(); this.$systemActor.initiate(); this.$systemCollision.initiate(); this.$systemRender.initiate(); this.$systemAudio.initiate(); this.$systemVibration.initiate(); this.$loop.initiate($tickrateMinimum); } /** * Preloads the assets of the given stage. * @param {typeof Stage} $stage The stage to preload the assets from. * @returns {Promise<Array<undefined | AudioBuffer | WebGLTexture>>} * @public */ preloadStage($stage) { /** * @type {Array<Promise<undefined | AudioBuffer | WebGLTexture>>} */ const promises = []; UTILS.deduplicate($stage.preloadables).forEach(($asset) => { if (this.hasAssetLoaded($asset) === true) { return; } this.$preloaded.add($asset); if (this.$systemAudio.hasAssetLoaded($asset) === true) { return; } if (this.$systemRender.hasAssetLoaded($asset) === true) { return; } /** * @type {Promise<undefined | AudioBuffer | WebGLTexture>} */ const promise = new Promise(($resolve) => { fetch($asset) .then(($content) => { const contentType = $content.headers.get('Content-Type'); const [mediaType] = contentType.toLowerCase().split('/'); switch (mediaType) { case MEDIA_TYPES.AUDIO: { this.$systemAudio.loadAudio($content) .then(($bufferAudio) => { $resolve($bufferAudio); }); break; } case MEDIA_TYPES.IMAGE: { this.$systemRender.loadTexture($content) .then(($texture) => { $resolve($texture); }); break; } default: { $resolve(undefined); } } }); }); promises.push(promise); }); return Promise.all(promises); } /** * Removes the native pointer display. * @public */ removePointerNative() { this.$systemRender.removePointerNative(); } /** * Sets the rendering background color. * @param {Vector3} $color The rendering background color to set. * @public */ setColor($color) { this.$systemRender.setColor($color); } /** * Sets the rendering resolution. * @param {Vector2} $resolution The rendering resolution to set. * @public */ setResolution($resolution) { this.$systemRender.setResolution($resolution); } /** * Terminates the engine (immediately) (must be used outside the lifecycle of this engine). * @public */ terminate() { this.$loop.terminate(); this.$createStage(Stage); this.tick(0); this.$systemInput.terminate(); this.$systemActor.terminate(); this.$systemCollision.terminate(); this.$systemRender.terminate(); this.$systemAudio.terminate(); this.$systemVibration.terminate(); this.$preloaded = new Set(); } /** * Updates each system once. * @param {number} $timetick The tick duration (in ms). * @public */ tick($timetick) { if (typeof this.$next === typeof Stage) { const stage = this.$next; this.$next = undefined; this.$createStage(stage); } if (typeof this.$stage === 'undefined') { return; } this.$systemInput.tick({ $stage: this.$stage, $timetick: $timetick }); this.$systemActor.tick({ $stage: this.$stage, $timetick: $timetick }); this.$systemCollision.tick({ $stage: this.$stage, $timetick: $timetick }); this.$systemRender.tick({ $stage: this.$stage, $timetick: $timetick }); this.$systemAudio.tick({ $stage: this.$stage, $timetick: $timetick }); this.$systemVibration.tick({ $stage: this.$stage, $timetick: $timetick }); } } export { Engine }; export default Engine;