File: src\core\StateManager.ts
/**
*
* @module Kiwi
*
*/
module Kiwi {
/**
* The State Manager handles the starting, parsing, looping and swapping of game States within a Kiwi Game
* There is only ever one State Manager per game, but a single Game can contain multiple States.
*
* @class StateManager
* @namespace Kiwi
* @constructor
* @param game {Kiwi.Game} The game that this statemanager belongs to.
* @return {Kiwi.StateMananger}
*
*/
export class StateManager {
constructor(game: Kiwi.Game) {
this._game = game;
this._states = [];
}
/**
* The type of object this is.
* @method objType
* @return {string} "StateManager"
* @public
*/
public objType() {
return "StateManager";
}
/**
* The game that this manager belongs to.
* @property _game
* @type Kiwi.Game
* @private
*/
private _game: Kiwi.Game;
/**
* An array of all of the states that are contained within this manager.
* @property _states
* @type Array
* @private
*/
private _states: Kiwi.State[];
/**
* The current State that the game is at.
* @property current
* @type Kiwi.State
* @default null
* @public
*/
public current: Kiwi.State = null;
/**
* The name of the new State that is to be switched to.
* @property _newStateKey
* @type string
* @default null
* @private
*/
private _newStateKey: string = null;
/**
* Checks to see if a key exists. Internal use only.
* @method checkKeyExists
* @param key {String}
* @return {boolean}
* @private
*/
private checkKeyExists(key: string): boolean {
for (var i = 0; i < this._states.length; i++)
{
if (this._states[i].config.name === key)
{
return true;
}
}
return false;
}
/**
* Checks to see if the State passed is valid or not.
* @method checkValidState
* @param state {Kiwi.State}
* @return {boolean}
* @private
*/
private checkValidState(state: Kiwi.State): boolean {
if (!state['game'] || !state['config']) {
return false;
}
return true;
}
/**
* Adds the given State to the StateManager.
* The State must have a unique key set on it, or it will fail to be added to the manager.
* Returns true if added successfully, otherwise false (can happen if State is already in the StateManager)
*
* @method addState
* @param state {Any} The Kiwi.State instance to add.
* @param [switchTo=false] {boolean} If set to true automatically switch to the given state after adding it
* @return {boolean} true if the State was added successfully, otherwise false
* @public
*/
public addState(state: any, switchTo:boolean = false): boolean {
Kiwi.Log.log('Kiwi.StateManager: Adding state.', '#state');
var tempState;
//What type is the state that was passed.
if (typeof state === 'function') {
tempState = new state();
} else if (typeof state === 'string') {
tempState = window[state];
} else {
tempState = state;
}
//Does a state with that name already exist?
if (tempState.config.name && this.checkKeyExists(tempState.config.name) === true) {
Kiwi.Log.error(' Kiwi.StateManager: Could not add ' + tempState.config.name + ' as a State with that name already exists.', '#state');
return false;
}
tempState.game = this._game;
//Is it a valid state?
if (this.checkValidState(tempState) === false) {
Kiwi.Log.error(' Kiwi.StateManager: ' + tempState.config.name + ' isn\'t a valid state. Make sure you are using the Kiwi.State class!', '#state');
return false;
} else {
this._states.push(tempState);
Kiwi.Log.log(' Kiwi.StateManager: ' + tempState.config.name + ' was successfully added.', '#state');
if (switchTo === true) {
this.switchState( tempState.config.name );
}
return true;
}
}
/**
* Is executed once the DOM has finished loading.
* This is an INTERNAL Kiwi method.
* @method boot
* @public
*/
boot() {
}
/**
* Switches to the name (key) of the state that you pass.
* Does not work if the state you are switching to is already the current state OR if that state does not exist yet.
* @method setCurrentState
* @param key {String}
* @return {boolean}
* @private
*/
private setCurrentState(key: string): boolean {
// Bail out if they are trying to switch to the already current state
if (this.current !== null && this.current.config.name === key || this.checkKeyExists(key) === false) {
return false;
}
Kiwi.Log.log('Kiwi.StateManager: Switching to "' + key + '" State.', '#state');
this._newStateKey = key;
return true;
}
/**
* Actually switches to a state that is stored in the 'newStateKey' property. This method is executed after the update loops have been executed to help prevent developer errors.
* @method bootNewState
* @private
*/
private bootNewState() {
// Destroy the current if there is one.
if (this.current !== null) {
this.current.shutDown();
this._game.input.reset(); //Reset the input component
this.current.destroy(true); //Destroy ALL IChildren ever created on that state.
this._game.fileStore.removeStateFiles(this.current); //Clear the fileStore of not global files.
this.current.config.reset(); //Reset the config setting
this._game.cameras.zeroAllCameras(); // Reset cameras
}
//Set the current state, reset the key
this.current = this.getState(this._newStateKey);
this._newStateKey = null;
//Initalise the state and execute the preload method?
this.checkInit();
this.checkPreload();
}
/**
* Swaps the current state.
* If the state has already been loaded (via addState) then you can just pass the key.
* Otherwise you can pass the state object as well and it will load it then swap to it.
*
* @method switchState
* @param key {String} The name/key of the state you would like to switch to.
* @param [state=null] {Any} The state that you want to switch to. This is only used to create the state if it doesn't exist already.
* @param [initParams=null] {Object} Any parameters that you would like to pass to the init method of that new state.
* @param [createParams=null] {Object} Any parameters that you would like to pass to the create method of that new state.
* @param [preloadParams=null] {Object} Any parameters that you would like to pass to the preload method. Since 1.3.0 of Kiwi.JS
* @return {boolean} Whether the State is going to be switched to or not.
* @public
*/
public switchState(key: string, state: any = null, initParams = null, createParams = null, preloadParams=null): boolean {
// If we have a current state that isn't yet ready (preload hasn't finished) then abort now
if (this.current !== null && this.current.config.isReady === false) {
Kiwi.Log.error('Kiwi.StateManager: Cannot change to a new state till the current state has finished loading!', '#state');
return false;
}
// If state key doesn't exist then lets add it.
if (this.checkKeyExists(key) === false && state !== null) {
if (this.addState(state, false) === false) {
return false;
}
}
// Store the parameters (if any)
if (initParams !== null || createParams !== null || preloadParams !== null) {
var newState = this.getState(key);
newState.config.initParams = [];
newState.config.createParams = [];
newState.config.preloadParams = [];
for (var initParameter in initParams) {
newState.config.initParams.push(initParams[initParameter]);
}
for (var createParameter in createParams) {
newState.config.createParams.push(createParams[createParameter]);
}
for (var preloadParameter in preloadParams) {
newState.config.preloadParams.push(preloadParams[preloadParameter]);
}
}
return this.setCurrentState(key);
}
/**
* Gets a state by the key that is passed.
* @method getState
* @param key {String}
* @return {Kiwi.State}
* @private
*/
private getState(key: string): Kiwi.State {
for (var i = 0; i < this._states.length; i++)
{
if (this._states[i].config.name === key)
{
return this._states[i];
}
}
return null;
}
/*
*----------------
* Check Methods
*----------------
*/
/**
* Checks to see if the state that is being switched to needs to load some files or not.
* If it does it loads the file, if it does not it runs the create method.
* @method checkPreload
* @private
*/
private checkPreload() {
//Rebuild the Libraries before the preload is executed
this.rebuildLibraries();
this._game.loader.onQueueProgress.add(this.onLoadProgress, this);
this._game.loader.onQueueComplete.add(this.onLoadComplete, this);
this.current.preload.apply(this.current, this.current.config.preloadParams);
this._game.loader.start();
}
/**
* Checks to see if the state being switched to contains a create method.
* If it does then it calls the create method.
* @method callCreate
* @private
*/
private callCreate() {
Kiwi.Log.log("Kiwi.StateManager: Calling " + this.current.name + ":Create", '#state');
this.current.create.apply(this.current, this.current.config.createParams);
this.current.config.runCount++;
this.current.config.isCreated = true;
}
/**
* Checks to see if the state has a init method and then executes that method if it is found.
* @method checkInit
* @private
*/
private checkInit() {
//Has the state already been initialised?
if (this.current.config.isInitialised === false) {
//Boot the state.
this.current.boot();
//Execute the Init method with params
this.current.init.apply(this.current, this.current.config.initParams);
this.current.config.isInitialised = true;
}
}
/**
* Is execute whilst files are being loaded by the state.
* @method onLoadProgress
* @param percent {Number} The current percentage of files that have been loaded. Ranging from 0 - 1.
* @param bytesLoaded {Number} The number of bytes that have been loaded so far.
* @param file {Kiwi.Files.File} The last file that has been loaded.
* @private
*/
private onLoadProgress(percent: number, bytesLoaded: number, file: Kiwi.Files.File) {
this.current.loadProgress(percent, bytesLoaded, file);
}
/**
* Executed when the preloading has completed. Then executes the loadComplete and create methods of the new State.
* @method onLoadComplete
* @private
*/
private onLoadComplete() {
this.current.loadComplete();
this._game.loader.onQueueProgress.remove(this.onLoadProgress, this);
this._game.loader.onQueueComplete.remove(this.onLoadComplete, this);
//Rebuild the Libraries again to have access the new files that were loaded.
this.rebuildLibraries();
this.current.config.isReady = true;
this.callCreate();
}
/**
* Rebuilds the texture, audio and data libraries that are on the current state. Thus updating what files the user has access to.
* @method rebuildLibraries
* @public
*/
public rebuildLibraries() {
this.current.audioLibrary.rebuild(this._game.fileStore, this.current);
this.current.dataLibrary.rebuild(this._game.fileStore, this.current);
this.current.textureLibrary.rebuild(this._game.fileStore, this.current);
if (this._game.renderOption == Kiwi.RENDERER_WEBGL) {
this._game.renderer.initState(this.current);
}
}
/**
* The update loop that is accessible on the StateManager.
* @method update
* @public
*/
public update() {
if (this.current !== null)
{
//Is the state ready?
if (this.current.config.isReady === true)
{
this.current.preUpdate();
this.current.update();
this.current.postUpdate();
}
else
{
this.current.loadUpdate();
}
}
//Do we need to switch states?
if (this._newStateKey !== null) {
this.bootNewState();
}
}
/**
* PostRender - Called after all of the rendering has been executed in a frame.
* @method postRender
* @public
*/
public postRender() {
if (this.current !== null)
{
if (this.current.config.isReady === true)
{
this.current.postRender();
}
}
}
}
}