//-----------------------------------------------------------------------------
// SceneManager
//
// The static class that manages scene transitions.
/**
* The static class that manages scene transitions.
*
* @namespace
*/
function SceneManager() {
throw new Error("This is a static class");
}
SceneManager._scene = null;
SceneManager._nextScene = null;
SceneManager._stack = [];
SceneManager._exiting = false;
SceneManager._previousScene = null;
SceneManager._previousClass = null;
SceneManager._backgroundBitmap = null;
SceneManager._smoothDeltaTime = 1;
SceneManager._elapsedTime = 0;
/**
* Starts the main game loop with the given scene class as the base stage
*
* @static
* @param {Stage} sceneClass - The first scene when game starts, default is Scene_Boot
*/
SceneManager.run = function(sceneClass) {
try {
this.initialize();
this.goto(sceneClass);
Graphics.startGameLoop();
} catch (e) {
this.catchException(e);
}
};
/**
* Initialize the SceneManager
*
* @static
*/
SceneManager.initialize = function() {
this.checkBrowser();
this.checkPluginErrors();
this.initGraphics();
this.initAudio();
this.initVideo();
this.initInput();
this.setupEventHandlers();
};
/**
* Check if the player's browser supports necessary technologies to run an RPG Maker game
*
* @static
* @throws Errors if browser does not support WebGL, Web Audio API, CSS Font Loading, or IndexedDB
*/
SceneManager.checkBrowser = function() {
if (!Utils.canUseWebGL()) {
throw new Error("Your browser does not support WebGL.");
}
if (!Utils.canUseWebAudioAPI()) {
throw new Error("Your browser does not support Web Audio API.");
}
if (!Utils.canUseCssFontLoading()) {
throw new Error("Your browser does not support CSS Font Loading.");
}
if (!Utils.canUseIndexedDB()) {
throw new Error("Your browser does not support IndexedDB.");
}
};
/**
* Check if any plugin errors exist
*
* @static
*/
SceneManager.checkPluginErrors = function() {
PluginManager.checkErrors();
};
/**
* Attempts to initialize Graphics
*
* @static
* @throws Error if Graphics could not be initialized
*/
SceneManager.initGraphics = function() {
if (!Graphics.initialize()) {
throw new Error("Failed to initialize graphics.");
}
Graphics.setTickHandler(this.update.bind(this));
};
/**
* Initializes WebAudio
*
* @static
*/
SceneManager.initAudio = function() {
WebAudio.initialize();
};
/**
* Initializes {@link Video}
*
* @static
*/
SceneManager.initVideo = function() {
Video.initialize(Graphics.width, Graphics.height);
};
/**
* Initializes {@link Input} and {@link TouchInput}
*
* @static
*/
SceneManager.initInput = function() {
Input.initialize();
TouchInput.initialize();
};
/**
* Sets JS event listeners for error, unhandledrejection, unload, and keydown
*
* @static
*/
SceneManager.setupEventHandlers = function() {
window.addEventListener("error", this.onError.bind(this));
window.addEventListener("unhandledrejection", this.onReject.bind(this));
window.addEventListener("unload", this.onUnload.bind(this));
document.addEventListener("keydown", this.onKeyDown.bind(this));
};
/**
* Updates the SceneManager
*
* @static
* @param {number} deltaTime - Scalar time value from last frame to this frame.
*/
SceneManager.update = function(deltaTime) {
try {
const n = this.determineRepeatNumber(deltaTime);
for (let i = 0; i < n; i++) {
this.updateMain();
}
} catch (e) {
this.catchException(e);
}
};
/**
* Check how many times to update based on time since last frame
*
* @static
* @param {number} deltaTime - Scalar time value from last frame to this frame.
*/
SceneManager.determineRepeatNumber = function(deltaTime) {
// [Note] We consider environments where the refresh rate is higher than
// 60Hz, but ignore sudden irregular deltaTime.
this._smoothDeltaTime *= 0.8;
this._smoothDeltaTime += Math.min(deltaTime, 2) * 0.2;
if (this._smoothDeltaTime >= 0.9) {
this._elapsedTime = 0;
return Math.round(this._smoothDeltaTime);
} else {
this._elapsedTime += deltaTime;
if (this._elapsedTime >= 1) {
this._elapsedTime -= 1;
return 1;
}
return 0;
}
};
/**
* Exit the game (if nwjs)
*
* @static
*/
SceneManager.terminate = function() {
if (Utils.isNwjs()) {
nw.App.quit();
}
};
/**
* Handling for errors
*
* @static
* @param {{message: string, filename: string, lineno: string}} event - The error event
*/
SceneManager.onError = function(event) {
console.error(event.message);
console.error(event.filename, event.lineno);
try {
this.stop();
Graphics.printError("Error", event.message, event);
AudioManager.stopAll();
} catch (e) {
//
}
};
/**
* Handling for an uncaught exception in Promise when unhandledrejection event is fired. See {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event Documentation}
*
* @static
* @param {{reason: string, filename: string, lineno: string}} event - The error event
*/
SceneManager.onReject = function(event) {
// Catch uncaught exception in Promise
event.message = event.reason;
this.onError(event);
};
/**
* Handling for when an unload event is fired. See {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event Documentation}
*
* @static
*/
SceneManager.onUnload = function() {
ImageManager.clear();
EffectManager.clear();
AudioManager.stopAll();
};
/**
* Handling for when a keydown event is fired. See {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event Documentation}
*
* @static
* @param {event} event - The keydown event
*/
SceneManager.onKeyDown = function(event) {
if (!event.ctrlKey && !event.altKey) {
switch (event.keyCode) {
case 116: // F5
this.reloadGame();
break;
case 119: // F8
this.showDevTools();
break;
}
}
};
/**
* Reloads the game, if nwjs is available in the environment
*
* @static
*/
SceneManager.reloadGame = function() {
if (Utils.isNwjs()) {
chrome.runtime.reload();
}
};
/**
* Shows the chrome dev tools, if nwjs is available in the environment and is a test play
*
* @static
*/
SceneManager.showDevTools = function() {
if (Utils.isNwjs() && Utils.isOptionValid("test")) {
nw.Window.get().showDevTools();
}
};
/**
* Catching for exceptions
*
* @static
* @param {(Error|Array|*)} e - The error encountered
*/
SceneManager.catchException = function(e) {
if (e instanceof Error) {
this.catchNormalError(e);
} else if (e instanceof Array && e[0] === "LoadError") {
this.catchLoadError(e);
} else {
this.catchUnknownError(e);
}
this.stop();
};
/**
* Catching for normal Error type errors
*
* @static
* @param {Error} e - The error encountered
*/
SceneManager.catchNormalError = function(e) {
Graphics.printError(e.name, e.message, e);
AudioManager.stopAll();
console.error(e.stack);
};
/**
* Catching for load Errors
*
* @static
* @param {Array} e - Array of error information for the error encountered
*/
SceneManager.catchLoadError = function(e) {
const url = e[1];
const retry = e[2];
Graphics.printError("Failed to load", url);
if (retry) {
Graphics.showRetryButton(() => {
retry();
SceneManager.resume();
});
} else {
AudioManager.stopAll();
}
};
/**
* Catching for all other Errors
*
* @static
* @param {*} e - An object of any type with error information
*/
SceneManager.catchUnknownError = function(e) {
Graphics.printError("UnknownError", String(e));
AudioManager.stopAll();
};
/**
* Update the game
*
* @static
*/
SceneManager.updateMain = function() {
this.updateFrameCount();
this.updateInputData();
this.updateEffekseer();
this.changeScene();
this.updateScene();
};
/**
* Add one to the frame count
*
* @static
*/
SceneManager.updateFrameCount = function() {
Graphics.frameCount++;
};
/**
* Update {@link Input} and {@link TouchInput}
*
* @static
*/
SceneManager.updateInputData = function() {
Input.update();
TouchInput.update();
};
/**
* Update Effekseer
*
* @static
*/
SceneManager.updateEffekseer = function() {
if (Graphics.effekseer && this.isGameActive()) {
Graphics.effekseer.update();
}
};
/**
* Changes the scene, if needed
*
* @static
*/
SceneManager.changeScene = function() {
if (this.isSceneChanging() && !this.isCurrentSceneBusy()) {
if (this._scene) {
this._scene.terminate();
this.onSceneTerminate();
}
this._scene = this._nextScene;
this._nextScene = null;
if (this._scene) {
this._scene.create();
this.onSceneCreate();
}
if (this._exiting) {
this.terminate();
}
}
};
/**
* Updates the current scene
*
* @static
*/
SceneManager.updateScene = function() {
if (this._scene) {
if (this._scene.isStarted()) {
if (this.isGameActive()) {
this._scene.update();
}
} else if (this._scene.isReady()) {
this.onBeforeSceneStart();
this._scene.start();
this.onSceneStart();
}
}
};
/**
* Check if the game window is active. Will also return true if this check could not be completed
*
* @static
* @return {boolean} True if game window has focus.
*/
SceneManager.isGameActive = function() {
// [Note] We use "window.top" to support an iframe.
try {
return window.top.document.hasFocus();
} catch (e) {
// SecurityError
return true;
}
};
/**
* Handling for when a scene is terminated
*
* @static
*/
SceneManager.onSceneTerminate = function() {
this._previousScene = this._scene;
this._previousClass = this._scene.constructor;
Graphics.setStage(null);
};
/**
* Handling for when a scene is created
*
* @static
*/
SceneManager.onSceneCreate = function() {
Graphics.startLoading();
};
/**
* Handling before a scene is started
*
* @static
*/
SceneManager.onBeforeSceneStart = function() {
if (this._previousScene) {
this._previousScene.destroy();
this._previousScene = null;
}
if (Graphics.effekseer) {
Graphics.effekseer.stopAll();
}
};
/**
* Handling when a scene is started
*
* @static
*/
SceneManager.onSceneStart = function() {
Graphics.endLoading();
Graphics.setStage(this._scene);
};
/**
* Check if the scene is changing
*
* @static
* @return {boolean} True if game is exiting or there is a next scene to go to
*/
SceneManager.isSceneChanging = function() {
return this._exiting || !!this._nextScene;
};
/**
* Check if the current scene is busy
*
* @static
* @return {boolean} True if the scene is busy
*/
SceneManager.isCurrentSceneBusy = function() {
return this._scene && this._scene.isBusy();
};
/**
* Check if the provided scene is the next scene
*
* @static
* @param {Stage} sceneClass - The scene to check
* @return {boolean} True if the passed scene class is the next scene
*/
SceneManager.isNextScene = function(sceneClass) {
return this._nextScene && this._nextScene.constructor === sceneClass;
};
/**
* Check if the provided scene is the previous scene
*
* @static
* @param {Stage} sceneClass - The scene to check
* @return {boolean} True if the passed scene class is the previous scene
*/
SceneManager.isPreviousScene = function(sceneClass) {
return this._previousClass === sceneClass;
};
/**
* Goes directly to the given scene
*
* @static
* @param {Stage} sceneClass - The scene to go to
*/
SceneManager.goto = function(sceneClass) {
if (sceneClass) {
this._nextScene = new sceneClass();
}
if (this._scene) {
this._scene.stop();
}
};
/**
* Pushes the given scene onto the end of the scene stack
*
* @static
* @param {Stage} sceneClass - The scene to push to the stack
*/
SceneManager.push = function(sceneClass) {
this._stack.push(this._scene.constructor);
this.goto(sceneClass);
};
/**
* Pops the last scene on the end of the scene stack
*
* @static
*/
SceneManager.pop = function() {
if (this._stack.length > 0) {
this.goto(this._stack.pop());
} else {
this.exit();
}
};
/**
* Exits the game
*
* @static
*/
SceneManager.exit = function() {
this.goto(null);
this._exiting = true;
};
/**
* Clears the scene stack
*
* @static
*/
SceneManager.clearStack = function() {
this._stack = [];
};
/**
* Stops the main game loop
*
* @static
*/
SceneManager.stop = function() {
Graphics.stopGameLoop();
};
/**
* Prepares the next scene with the given arguments
*
* @static
* @param {*} arguments - Arguments to be passed to the next scene's prepare function
*/
SceneManager.prepareNextScene = function() {
this._nextScene.prepare(...arguments);
};
/**
* Snaps a bitmap of the current scene
*
* @static
* @return {Bitmap} A bitmap of the current scene as it appears when called
*/
SceneManager.snap = function() {
return Bitmap.snap(this._scene);
};
/**
* Snaps a bitmap of the current scene for use in the background
*
* @static
*/
SceneManager.snapForBackground = function() {
if (this._backgroundBitmap) {
this._backgroundBitmap.destroy();
}
this._backgroundBitmap = this.snap();
};
/**
* Gets the background bitmap
*
* @static
* @return {Bitmap} The background bitmap
*/
SceneManager.backgroundBitmap = function() {
return this._backgroundBitmap;
};
/**
* Resumes the main game loop
*
* @static
*/
SceneManager.resume = function() {
TouchInput.update();
Graphics.startGameLoop();
};