/** * @author Richard Davey * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ var CONST = require('../const'); var NOOP = require('../utils/NOOP'); /** * The State Manager is responsible for loading, setting up and switching game states. * * @class Phaser.StateManager * @constructor * @param {Phaser.Game} game - A reference to the currently running game. */ var StateManager = function (game, stateConfig) { this.game = game; // Everything kept in here this.keys = {}; this.states = []; // Only active states are kept in here this.active = []; this._pending = []; if (stateConfig) { if (Array.isArray(stateConfig)) { for (var i = 0; i < stateConfig.length; i++) { // The i === 0 part just starts the first State given this._pending.push({ index: i, key: 'default', state: stateConfig[i], autoStart: (i === 0) }); } } else { this._pending.push({ index: 0, key: 'default', state: stateConfig, autoStart: true }); } } }; StateManager.prototype.constructor = StateManager; StateManager.prototype = { /** * The Boot handler is called by Phaser.Game when it first starts up. * The renderer is available by now. * * @method Phaser.StateManager#boot * @private */ boot: function () { // this.game.onPause.add(this.pause, this); // this.game.onResume.add(this.resume, this); console.log('StateManager.boot'); for (var i = 0; i < this._pending.length; i++) { var entry = this._pending[i]; this.add(entry.key, entry.state, entry.autoStart); } // Clear the pending list this._pending = []; }, getKey: function (key, stateConfig) { if (!key) { key = 'default'; } if (stateConfig instanceof Phaser.State) { key = stateConfig.settings.key; } else if (typeof stateConfig === 'object' && stateConfig.hasOwnProperty('key')) { key = stateConfig.key; } // By this point it's either 'default' or extracted from the State if (this.keys.hasOwnProperty(key)) { throw new Error('Cannot add a State with duplicate key: ' + key); } else { return key; } }, /** * Adds a new State into the StateManager. You must give each State a unique key by which you'll identify it. * The State can be either a Phaser.State object (or an object that extends it), a plain JavaScript object or a function. * If a function is given a new state object will be created by calling it. * * @method Phaser.StateManager#add * @param {string} key - A unique key you use to reference this state, i.e. "MainMenu", "Level1". * @param {Phaser.State|object|function} state - The state you want to switch to. * @param {boolean} [autoStart=false] - If true the State will be started immediately after adding it. */ add: function (key, stateConfig, autoStart) { if (autoStart === undefined) { autoStart = false; } // if not booted, then put state into a holding pattern if (!this.game.isBooted) { this._pending.push({ index: this._pending.length, key: key, state: stateConfig, autoStart: autoStart }); console.log('StateManager not yet booted, adding to list', this._pending.length); return; } key = this.getKey(key, stateConfig); var newState; if (stateConfig instanceof Phaser.State) { console.log('StateManager.add from instance', key); newState = this.createStateFromInstance(key, stateConfig); } else if (typeof stateConfig === 'object') { console.log('StateManager.add from object', key); stateConfig.key = key; newState = this.createStateFromObject(key, stateConfig); } else if (typeof stateConfig === 'function') { console.log('StateManager.add from function', key); newState = this.createStateFromFunction(key, stateConfig); } this.keys[key] = newState; this.states.push(newState); // window.console.dir(newState); if (autoStart || newState.settings.active) { if (this.game.isBooted) { this.start(key); } else { this._start.push(key); } } return newState; }, createStateFromInstance: function (key, newState) { newState.game = this.game; newState.settings.key = key; newState.sys.init(); if (this.game.config.renderType === CONST.WEBGL) { this.createStateFrameBuffer(newState); } return newState; }, createStateFromObject: function (key, stateConfig) { var newState = new Phaser.State(stateConfig); newState.game = this.game; newState.sys.init(); if (this.game.config.renderType === CONST.WEBGL) { this.createStateFrameBuffer(newState); } // Extract callbacks or set NOOP if (stateConfig.hasOwnProperty('init')) { newState.init = stateConfig.init; } if (stateConfig.hasOwnProperty('preload')) { newState.preload = stateConfig.preload; } if (stateConfig.hasOwnProperty('create')) { newState.create = stateConfig.create; } if (stateConfig.hasOwnProperty('shutdown')) { newState.shutdown = stateConfig.shutdown; } newState.preUpdate = (stateConfig.hasOwnProperty('preUpdate')) ? stateConfig.preUpdate : NOOP; newState.update = (stateConfig.hasOwnProperty('update')) ? stateConfig.update : NOOP; newState.postUpdate = (stateConfig.hasOwnProperty('postUpdate')) ? stateConfig.postUpdate : NOOP; newState.render = (stateConfig.hasOwnProperty('render')) ? stateConfig.render : NOOP; return newState; }, createStateFromFunction: function (key, state) { var newState = new state(); if (newState instanceof Phaser.State) { return this.createStateFromInstance(key, newState); } else { newState.game = this.game; newState.settings = new Phaser.State.Settings(newState, key); newState.sys = new Phaser.State.Systems(newState); newState.sys.init(); if (this.game.config.renderType === CONST.WEBGL) { this.createStateFrameBuffer(newState); } // Default required functions if (!newState.preUpdate) { newState.preUpdate = NOOP; } if (!newState.update) { newState.update = NOOP; } if (!newState.postUpdate) { newState.postUpdate = NOOP; } if (!newState.render) { newState.render = NOOP; } return newState; } }, createStateFrameBuffer: function (newState) { var x = newState.settings.x; var y = newState.settings.y; if (newState.settings.width === -1) { newState.settings.width = this.game.width; } if (newState.settings.height === -1) { newState.settings.height = this.game.height; } var width = newState.settings.width; var height = newState.settings.height; newState.sys.fbo = this.game.renderer.createFBO(newState, x, y, width, height); }, getState: function (key) { return this.keys[key]; }, getStateIndex: function (state) { return this.states.indexOf(state); }, getActiveStateIndex: function (state) { for (var i = 0; i < this.active.length; i++) { if (this.active[i].state === state) { return this.active[i].index; } } return -1; }, isActive: function (key) { var state = this.getState(key); return (state && state.settings.active && this.active.indexOf(state) !== -1); }, start: function (key) { // if not booted, then put state into a holding pattern if (!this.game.isBooted) { console.log('StateManager not yet booted, setting autoStart on pending list'); for (var i = 0; i < this._pending.length; i++) { var entry = this._pending[i]; if (entry.key === key) { entry.autoStart = true; } } return; } var state = this.getState(key); if (state) { // Already started? Nothing more to do here ... if (this.isActive(key)) { return; } state.settings.active = true; // + arguments if (state.init) { state.init.call(state); } if (state.preload) { state.sys.load.reset(true); state.preload.call(state, this.game); // Is the loader empty? if (state.sys.load.totalQueuedFiles() === 0 && state.sys.load.totalQueuedPacks() === 0) { // console.log('empty queue'); this.startCreate(state); } else { // console.log('load start'); // Start the loader going as we have something in the queue // state.load.onLoadComplete.addOnce(this.loadComplete, this, 0, state); state.sys.load.start(); } } else { // console.log('no preload'); // No preload? Then there was nothing to load either this.startCreate(state); } } }, loadComplete: function (state) { // console.log('loadComplete'); // Make sure to do load-update one last time before state is set to _created if (state.hasOwnProperty('loadUpdate')) { state.loadUpdate.call(state); } this.startCreate(state); }, startCreate: function (state) { if (state.create) { state.create.call(state); } // Insert at the correct index, or it just all goes wrong :) var i = this.getStateIndex(state); this.active.push({ index: i, state: state }); // Sort the 'active' array based on the index property this.active.sort(this.sortStates.bind(this)); state.sys.updates.running = true; state.sys.mainloop.start(); }, pause: function (key) { var index = this.getActiveStateIndex(key); if (index > -1) { var state = this.getState(key); state.settings.active = false; this.active.splice(index, 1); this.active.sort(this.sortStates.bind(this)); } }, sortStates: function (stateA, stateB) { // Sort descending if (stateA.index < stateB.index) { return -1; } else if (stateA.index > stateB.index) { return 1; } else { return 0; } }, // See if we can reduce this down to just update and render step: function (timestamp) { for (var i = 0; i < this.active.length; i++) { var state = this.active[i].state; if (state.sys.mainloop.running) { state.sys.mainloop.step(timestamp); } } }, /* preUpdate: function () { for (var i = 0; i < this.active.length; i++) { var state = this.active[i].state; for (var c = 0; c < state.sys.children.list.length; c++) { state.sys.children.list[c].preUpdate(); } state.preUpdate(); } }, update: function () { for (var i = 0; i < this.active.length; i++) { var state = this.active[i].state; // Invoke State Main Loop here - updating all of its systems (tweens, physics, etc) // This shouldn't be called if the State is still loading // Have a State.STATUS const in the Settings, dictating what is going on for (var c = 0; c < state.sys.children.list.length; c++) { var child = state.sys.children.list[c]; if (child.exists) { child.update(); } } state.update(); } }, postUpdate: function () { for (var i = 0; i < this.active.length; i++) { var state = this.active[i].state; for (var c = 0; c < state.sys.children.list.length; c++) { state.sys.children.list[c].postUpdate(); } state.postUpdate(); } }, render: function () { for (var i = 0; i < this.active.length; i++) { var state = this.active[i].state; // Can put all kinds of other checks in here, like MainLoop, FPS, etc. if (!state.settings.visible || state.sys.color.alpha === 0 || state.sys.children.list.length === 0) { continue; } this.game.renderer.render(state); } }, */ renderChildren: function (renderer, state, interpolationPercentage) { // Populates the display list for (var c = 0; c < state.sys.children.list.length; c++) { var child = state.sys.children.list[c]; child.render(renderer, child, interpolationPercentage); } } }; module.exports = StateManager;