plugins_sceneSystem.js

/**
 * LittleJS Scene System Plugin
 * - A game's states, like a title, the game and game over, as scene objects
 * - setScene leaves the current scene, destroys the objects and enters the next
 * - Objects with the persistent flag set outlive every switch
 * - Pause is the engine's paused flag, a scene's update stops with gameUpdate
 * @namespace SceneSystem
 */

'use strict';

///////////////////////////////////////////////////////////////////////////////

/** A scene, any object with the hooks it needs, each optional and called as a method so this is the scene
 *  @typedef {Object} Scene
 *  @property {function():void} [enter] - Called when the scene starts, after the old one left and the objects were destroyed
 *  @property {function():void} [leave] - Called before the next scene starts, while the scene is current and its objects are still there
 *  @property {function():void} [update] - Called each update after gameUpdate, not while paused or at time scale 0
 *  @property {function():void} [render] - Called before gameRender, to draw under the objects
 *  @property {function():void} [renderPost] - Called after gameRenderPost, to draw over the game, while paused too; plugins made after the first setScene draw over it
 *  @memberof SceneSystem */

let sceneCurrent, sceneLeaving = false, scenePluginAdded = false;

/** Leave the current scene, destroy every object that is not persistent, and enter the next scene
 *  - Setting the current scene again restarts it, and no scene leaves the game with none
 *  - The switch happens at once, it can be called from anywhere but a scene's leave
 *  @param {Scene} [scene] - The scene to enter
 *  @memberof SceneSystem */
function setScene(scene)
{
    ASSERT(!sceneLeaving, 'setScene can not be called from a scene leave');
    if (!scenePluginAdded)
    {
        scenePluginAdded = true;
        engineAddPlugin(sceneUpdate, sceneRenderPost, undefined, undefined, sceneRender);
    }

    if (sceneCurrent)
    {
        sceneLeaving = true;
        sceneCurrent.leave?.();
        sceneLeaving = false;
    }
    engineObjectsDestroy();
    sceneCurrent = scene;
    scene?.enter?.();
}

/** Get the current scene
 *  @return {Scene|undefined}
 *  @memberof SceneSystem */
function getScene() { return sceneCurrent; }

// the plugin hooks, update skips the frozen ticks that skip gameUpdate
function sceneUpdate() { paused || !timeScale || sceneCurrent?.update?.(); }
function sceneRender() { sceneCurrent?.render?.(); }
function sceneRenderPost() { sceneCurrent?.renderPost?.(); }