/** * @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'); var State = require('./State'); var Settings = require('./Settings'); var Systems = require('./Systems'); var GetObjectValue = require('../utils/object/GetObjectValue'); var EventDispatcher = require('../events/EventDispatcher'); /** * 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 () { 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 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; } // console.log('StateManager.add', key, stateConfig, autoStart); key = this.getKey(key, stateConfig); var newState; if (stateConfig instanceof 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); if (autoStart || newState.settings.active) { if (this.game.isBooted) { this.start(key); } else { this._start.push(key); } } return newState; }, createStateFromInstance: function (key, newState) { newState.settings.key = key; newState.sys.init(this.game); this.createStateDisplay(newState); return newState; }, createStateFromObject: function (key, stateConfig) { var newState = new State(stateConfig); newState.sys.init(this.game); this.createStateDisplay(newState); return this.setupCallbacks(newState, stateConfig); }, createStateFromFunction: function (key, state) { var newState = new state(); if (newState instanceof State) { return this.createStateFromInstance(key, newState); } else { newState.sys = new Systems(newState); newState.sys.init(this.game); this.createStateDisplay(newState); // Default required functions if (!newState.init) { newState.init = NOOP; } if (!newState.preload) { newState.preload = NOOP; } if (!newState.create) { newState.create = NOOP; } if (!newState.shutdown) { newState.shutdown = NOOP; } if (!newState.update) { newState.update = NOOP; } if (!newState.render) { newState.render = NOOP; } return newState; } }, setupCallbacks: function (newState, stateConfig) { if (stateConfig === undefined) { stateConfig = newState; } // Extract callbacks or set NOOP newState.init = GetObjectValue(stateConfig, 'init', NOOP); newState.preload = GetObjectValue(stateConfig, 'preload', NOOP); newState.create = GetObjectValue(stateConfig, 'create', NOOP); newState.shutdown = GetObjectValue(stateConfig, 'shutdown', NOOP); // Game Loop level callbacks newState.update = GetObjectValue(stateConfig, 'update', NOOP); newState.render = GetObjectValue(stateConfig, 'render', NOOP); return newState; }, createStateDisplay: function (newState) { return; /* var settings = newState.sys.settings; var x = settings.x; var y = settings.y; // Too late to do all this? if (settings.width === -1) { settings.width = this.game.config.width; } if (settings.height === -1) { settings.height = this.game.config.height; } if (this.game.config.renderType === CONST.WEBGL) { var width = settings.width; var height = 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) { state.sys.load.reset(); state.preload.call(state, this.game); // Is the loader empty? if (state.sys.load.list.size === 0) { this.startCreate(state); } else { // Start the loader going as we have something in the queue state.sys.load.events.once('LOADER_COMPLETE_EVENT', this.loadComplete.bind(this)); state.sys.load.start(); } } else { // No preload? Then there was nothing to load either this.startCreate(state); } } }, loadComplete: function (event) { var state = event.loader.state; // Make sure to do load-update one last time before state is set to _created // Stop doing this ... 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; }, 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; } } }; module.exports = StateManager;