phaser/v3/src/state/StateManager.js

575 lines
14 KiB
JavaScript
Raw Normal View History

2016-11-29 13:01:16 +00:00
/**
* @author Richard Davey <rich@photonstorm.com>
* @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;