/**
* @module storymode
*/
// PixiJS convenience aliases
window.Sprite = PIXI.Sprite;
window.AnimatedSprite = PIXI.AnimatedSprite;
window.Point = PIXI.Point;
window.Rectangle = PIXI.Rectangle;
window.Text = PIXI.Text
window.Graphics = PIXI.Graphics
window.Container = PIXI.Container;
window.Texture = PIXI.Texture;
window.loader = PIXI.Loader.shared;
window.resources = loader.resources;
window.ticker = PIXI.Ticker.shared;
const appEmitter = new PIXI.utils.EventEmitter();
import * as _ext from './utils/extensions.js';
import * as utils from './utils/utils.js';
import * as mutils from './utils/mutils.js';
import Scene from './core/scene.js';
import * as nav from './core/nav.js';
import * as scaler from './core/scaler.js';
import * as ui from './core/ui.js';
import KB from './utils/kb.js';
let kb = new KB();
import SFX from './utils/sfx.js';
let sfx = new SFX();;
import physics from './utils/physics.js';
import * as store from './utils/store.js';
// Props
let pixiApp; // PIXI app instance
let isProd = process.env.NODE_ENV != 'development'; // Set in package.json: eg. "start:dev": "webpack serve --mode development"
let htmlEle; // The element containing the game
let setupComplete = false;
let pendingDetach = null;
// Load all transitions
/*
let filters = {};
const _filters = utils.requireAll(require.context('./utils/filters', false, /.js$/));
for (let _filter of _filters) {
filters[_filter.id] = _filter.default;
}
*/
/**
* Called after initial assets are loaded.
*
* @callback AppLoadCallback
* @param {PIXI.Application} pixiApp The PIXI Application
*/
/**
* Creates a new PIXI Application, wrapper for `new PIXI.Application(...)`.
* <br>- Ideally the main app script is included in the body immediately after the container HTML element is included. In this scenario the app will be created instantly.
* <br>- Otherwise if the app script is initialised before the container HTML element is attached to the DOM then it will load in the background before continuing.
* @param {string} htmlEleID - The DOM element ID in which to add the Pixi canvas. If not present yet will wait for it to arrive.
* @param {boolean} [fullScreen=false] - If true will base the canvas dimensions on the window size rather than the containing element.
* @param {Object} [pixiOptions=null] - Option object to override defaults sent to PIXI. See: {@link http://pixijs.download/release/docs/PIXI.Application.html#Application}
* @param {Object} [options=null] - Addtional storymode options.
* @param {AppLoadCallback} [onLoadCallback=null] - Called after initial assets are loaded.
*/
export function createApp(_htmlEleID, fullScreen = false, pixiOptions = null, options = null, onLoadCallback = null) {
/*
If true, all tweens of the same targets will be killed immediately regardless
of what properties they affect.
If "auto", when the tween renders for the first time it hunt down any conflicts
in active animations (animating the same properties of the same targets) and kill only those parts of the other tweens.
Non-conflicting parts remain intact.
If false, no overwriting strategies will be employed. Default: false.
*/
gsap.defaults({ overwrite: 'auto' });
setupComplete = false
pendingDetach = null;
let setupConfig = {};
setupConfig.htmlEleID = _htmlEleID;
setupConfig.onLoadCallback = onLoadCallback;
kb.init();
sfx.loadPrefs()
let defaultOptions = {
displayFPS: isProd ? false : true,
waitForImagesToLoad: null, // Supply a single or array of images or image IDs
}
options = utils.extend(defaultOptions, options);
if (options.waitForImagesToLoad){
options.waitForImagesToLoad = Array.isArray(options.waitForImagesToLoad) ? options.waitForImagesToLoad : [options.waitForImagesToLoad]; // Ensure singles are arrays
}
setupConfig.options = utils.cloneObj(options); // A copy is retained for setup.
let defaultPixiOptions = {
autoDensity: true, // Adjusts the canvas using css pixels so it will scale properly (it was the default behavior in v4)
antialias: window.devicePixelRatio == 1, // Only anti alias from non-retina displays
backgroundAlpha: 1.0,
resolution: window.devicePixelRatio, // Resolution controls scaling of content (sprites, etc.)
backgroundColor: 0x000000,
clearBeforeRender: true // This sets if the renderer will clear the canvas or not before the new render pass.
}
if (!pixiOptions){
pixiOptions = {};
}
pixiOptions = utils.extend(defaultPixiOptions, pixiOptions);
// Is the html ele attached to the DOM at this point?
if (utils.e(setupConfig.htmlEleID) && typeof utils.e(setupConfig.htmlEleID)['appendChild'] === 'function'){
htmlEle = utils.e(setupConfig.htmlEleID);
}
// Apply resize target now if element already exists.
// - Otherwise wait until setup to apply target.
if (fullScreen){
// Window is present even if containing DOM element is not at this point.
pixiOptions.resizeTo = window;
} else if (htmlEle){
pixiOptions.resizeTo = htmlEle;
}
setupConfig.pixiOptions = utils.cloneObj(pixiOptions); // A copy is retained for setup.
// Docs: http://pixijs.download/release/docs/PIXI.Application.html#Application
pixiApp = new PIXI.Application(pixiOptions);
ui.autoloadAssets(()=>{
setup(setupConfig);
});
}
function areImagesLoaded(imageList){
if (!imageList){
return true;
}
for (let image of imageList){
if (!utils.isImgLoaded(image)){
return false;
}
}
return true;
}
let fpsAvg;
// All assets are loaded by this point and the stage is empty
function setup(setupConfig){
if (!utils.e(setupConfig.htmlEleID) || typeof utils.e(setupConfig.htmlEleID)['appendChild'] !== 'function' || !areImagesLoaded(setupConfig.options.waitForImagesToLoad)){
// The container element needs to be present and able to appendChild.
utils.wait(1.0/5.0, setup, [setupConfig]);
return;
}
if (setupConfig.options.setupDelay){
let _delay = setupConfig.options.setupDelay;
setupConfig.options.setupDelay = null;
delete setupConfig.options.setupDelay;
utils.wait(_delay, setup, [setupConfig]);
return;
}
// DOM is attached by this point.
if (!htmlEle){
htmlEle = utils.e(setupConfig.htmlEleID);
}
if (!setupConfig.pixiOptions.resizeTo){ // Will already be set if fullscreen
pixiApp.resizeTo = htmlEle;
}
pixiApp.render();
scaler.init();
// Attach canvas to the DOM
htmlEle.appendChild(pixiApp.view);
// Attach core display objects
nav.setupStage(pixiApp.stage, setupConfig.pixiOptions.backgroundAlpha);
// Debug TF
if (isProd){
// Disable right click - this menu may be confusing to user
htmlEle.setAttribute('oncontextmenu', 'return false');
} else if (setupConfig.options.displayFPS !== false){
const debugTf = new PIXI.Text('X', {fontFamily : 'Arial', fontSize: 13, fill : 0xffffff, align : 'left', dropShadow: true,
dropShadowColor: '#000000',
dropShadowBlur: 0.0,
dropShadowDistance: 2.0});
debugTf.x = 3.0;
debugTf.y = 3.0 + 50.0;
debugTf.alpha = 0.5;
pixiApp.stage.addChild(debugTf);
fpsAvg = -1;
ticker.add(function(time){
fpsAvg = fpsAvg < 0 ? PIXI.Ticker.shared.FPS : fpsAvg*0.8 + PIXI.Ticker.shared.FPS*0.2;
debugTf.text = fpsAvg.toFixed(1);
});
}
appEmitter.emit('onapp_predefaultscene', pixiApp)
// Get default scene and load it
if (!nav.openDefaultScene()){
throw new Error('Default scene not found.')
}
// Post setup
if (setupConfig.onLoadCallback){
setupConfig.onLoadCallback(pixiApp);
}
appEmitter.emit('ready', pixiApp.stage)
sfx._enableLoad();
setupComplete = true;
if (pendingDetach){
detachApp(pendingDetach[0],pendingDetach[1],pendingDetach[2])
pendingDetach = null;
}
}
function getPixiApp(){
return pixiApp;
}
function detachApp(callback, debugToConsole, consoleIdPrefix){
if (!setupComplete){
pendingDetach = [callback, debugToConsole, consoleIdPrefix];
return;
}
destroy(true, callback, debugToConsole, consoleIdPrefix);
}
let destroyed = false;
function destroy(reset = false, callback = null, debugToConsole = false, consoleIdPrefix = ''){
if (consoleIdPrefix && !consoleIdPrefix.endsWith(' ')){
consoleIdPrefix += ' ';
}
let logToConsole = debugToConsole ? window['con' + 'sole']['log'] : ()=>{};
appEmitter.emit('pre_destory', pixiApp.stage)
if (!reset){
if (destroyed){
return;
}
destroyed = true;
}
logToConsole(consoleIdPrefix + 'nav.destroy()')
nav.destroy(reset, ()=>{
//loader.reset(); // Required to halt ui lazy loads in progress.
//if (reset){
// window.resources = PIXI.Loader.shared.resources;
//}
logToConsole(consoleIdPrefix + 'nav.destroy() complete.')
logToConsole(consoleIdPrefix + 'Killing gsap animations.')
gsap.killTweensOf('*')
PIXI.Ticker.shared.stop();
logToConsole(consoleIdPrefix + 'scaler.destroy()')
scaler.destroy(reset);
logToConsole(consoleIdPrefix + 'ui.destroy()')
ui.destroy(reset);
logToConsole(consoleIdPrefix + 'kb.destroy()')
kb.destroy(reset);
logToConsole(consoleIdPrefix + 'sfx.destroy()')
sfx.destroy(reset);
if (!reset){
kb = null;
sfx = null;
}
logToConsole(consoleIdPrefix + 'pixiApp.destroy()')
pixiApp.destroy(true, {
children: true, // All the children will have their destroy method called as well. 'stageOptions' will be passed on to those calls.
texture: true, // Should it destroy the texture of the child sprite
baseTexture: true // Should it destroy the base texture of the child sprite
})
// Clean up this module
pixiApp = null;
htmlEle = null;
if (!reset){
filters = null;
}
appEmitter.removeAllListeners();
logToConsole(consoleIdPrefix + 'Removing textures...')
// Remove base textures.
for (let resourceID in PIXI.Loader.shared.resources){
if (resourceID.endsWith('_image')){
if (PIXI.Loader.shared.resources[resourceID].texture){
PIXI.Loader.shared.resources[resourceID].texture.destroy(true);
}
}
}
// Remove resource references.
for (let resourceID in PIXI.Loader.shared.resources){
if (PIXI.Loader.shared.resources[resourceID].texture){
PIXI.Loader.shared.resources[resourceID].texture.destroy(true);
}
if (PIXI.Loader.shared.resources[resourceID].spritesheet){
PIXI.Loader.shared.resources[resourceID].spritesheet.destroy(true);
PIXI.Loader.shared.resources[resourceID].spritesheet = null;
}
if (PIXI.Loader.shared.resources[resourceID].textures){
for (let txName in PIXI.Loader.shared.resources[resourceID].textures){
PIXI.Loader.shared.resources[resourceID].textures[txName].destroy(true);
}
}
}
logToConsole(consoleIdPrefix + 'Reseting shared loader...')
// Reset the shared loader.
PIXI.Loader.shared.onComplete.detachAll();
PIXI.Loader.shared.onLoad.detachAll();
PIXI.Loader.shared.onError.detachAll();
PIXI.Loader.shared.onProgress.detachAll();
PIXI.Loader.shared.onStart.detachAll();
PIXI.Loader.shared.reset(); // This removes the ref to window.resources.
if (reset){
window.resources = PIXI.Loader.shared.resources;
}
// Remove resource references.
for (let resourceID in PIXI.Loader.shared.resources){
PIXI.Loader.shared.resources[resourceID] = null;
delete PIXI.Loader.shared.resources[resourceID];
}
logToConsole(consoleIdPrefix + 'Removing any remaining tx...')
// Remove any remaining textures from the cache.
for (let key in PIXI.utils.TextureCache){
let baseTex = PIXI.utils.TextureCache[key].baseTexture
baseTex.destroy();
PIXI.Texture.removeFromCache(key);
}
for (let key in PIXI.utils.BaseTextureCache){
PIXI.utils.BaseTextureCache[key].destroy();
}
// Remove window references
if (!reset){
delete window.Sprite
delete window.AnimatedSprite
delete window.Point
delete window.Rectangle
delete window.Text
delete window.Graphics
delete window.Container
delete window.Texture
delete window.loader
delete window.resources
delete window.ticker
}
// Check memory
if (debugToConsole){
setTimeout(()=>{
logToConsole(consoleIdPrefix + 'resouces / shared.resources:', window.resources, PIXI.Loader.shared.resources);
logToConsole(consoleIdPrefix + 'TextureCache:', PIXI.utils.TextureCache)
logToConsole(consoleIdPrefix + 'BaseTextureCache:', PIXI.utils.BaseTextureCache)
logToConsole(consoleIdPrefix + 'Done.')
}, 1000);
}
callback();
});
}
export {pixiApp, getPixiApp, htmlEle}; // Internal access to these properties.
export {appEmitter, detachApp}; // Emitter events
export {kb, sfx, store, physics} // Helpers
export {utils, mutils,nav, ui, scaler, Scene}; // Core