scene.js

import {docs} from './docs.js'

/** Scene class for use in gameify. Usually you'll access this through the gameify object.
 * @example // Use scenes via gameify
 * // This is the most common way
 * import { gameify } from "./gameify/gameify.js"
 * let mainScene = new gameify.Scene(myScreen);
 * @example // Import just scenes
 * import { scenes } from "./gameify/scene.js"
 * let myScene = new scenes.Scene(0, 0);
 * @global
 */
export let scenes = {
    /** Creates a scene in the game. (Eg. a menu or level)
     * @constructor
     * @alias gameify.Scene
     * @example 
     * // Create a Screen that is 600 by 400 and centered
     * let myScreen = new gameify.Screen(document.querySelector("#my-canvas"), 600, 400);
     * myScreen.center();
     * 
     * // Create a Scene and give it update and draw functions
     * let mainMenu = new gameify.Scene(myScreen);
     * mainMenu.onUpdate((delta) => {
     *     // code ...
     * });
     * mainMenu.onDraw(() => {
     *     // code ...
     * });
     * 
     * // Start the game
     * myScreen.setScene(mainMenu);
     * myScreen.startGame();
     * @arg {gameify.Screen} screen - The Screen the Scene should draw to.
     */
    Scene: class {
        constructor (screen) {
            this.parent = screen;
        }

        /** The user-set update function
         * @private
         */
        #updateFunction = null;
        /** The user-set draw function
         * @private
         */
        #drawFunction = null;
        /** If the scene is locked */
        locked = false;
        /** The parent Screen */
        parent;

        /** Creates a object from JSON data
         * @method
         * @arg {Object|Array} data - Serialized object data (from object.toJSON)
         * @arg {Function} ref - A function that returns a name for other objects, so they can be restored later
         * @returns {gameify.Scene}
        */
        static fromJSON = (data, find) => {
            if (Array.isArray(data)) {
                // Be backwards compatible
                console.warn('Save is using the old (de)serialization format for Image.');
                const obj = new scenes.Scene();
                if (data[1]) obj.lock(data[1]);
                return obj;
            }

            const obj = new scenes.Scene(data.parent);
            if (data.locked) obj.lock(data.locked)
            return obj;
        }
        
        /** Convert the object to JSON
         * @method
         * @arg {string} [key] - Key object is stored under (unused, here for consistency with e.g. Date.toJSON, etc.)
         * @arg {function} ref - A function that returns a name for other objects, so they can be restored later
         * @returns {Object}
         */
        toJSON = (key, ref) => {
            return {
                locked: this.locked,
                parent: ref(this.parent)
            };
        }

        /** Set the update function for this scene
         * @method
         * @param {function} callback - The function that is called every update. Optionally, you can include a delta argument for physics and movement calculations.
         */
        onUpdate = (callback) => {
            this.#updateFunction = callback;
        }

        /** Update the scene
         * @method
         */
        update = (delta) => {
            if (this.#updateFunction == null) {
                throw new Error(`You need to specify an update function for this Scene. See ${docs.getDocs("gameify.Scene")} for details`);
            }
            this.parent.keyboard.clearJustPressed();
            this.parent.mouse.clearRecentEvents();
            this.#updateFunction(delta);
        }

        /** Lock the scene, meaning you cannot switch to another scene until it is unlocked with scene.unlock()
         * @method
         * @param {String} [text] - A helpful message to display when you try to switch scenes
         */
        lock = (text) => {
            this.locked = text || "Unlock it to change the scene";
        }
        /** Unlock the scene, meaning you can now switch to another scene. Scenes are unlocked by default
         * @method
         */
        unlock = () => {
            this.locked = false;
        }

        /** Set the draw function for this scene
         * @method
         * @param {function} callback
         */
        onDraw = (callback) => {
            this.#drawFunction = callback;
        }
 
        /** Draw the scene. This is done automatically, you usually shouldn't have to use it yourself.
         * @method
         */
        draw = () => {
            if (this.#drawFunction == null) {
                throw new Error(`You need to specify a draw function for your Scene. See ${docs.getDocs("gameify.Scene")} for details`);
            }
            this.#drawFunction();
        }

        /** Run when the scene is set as a Screen's active scene
         * @method
         * @package
         * @param {gameify.Screen} parent
        */
        onLoad = (parent) => {
            if (this.#loadFunction) this.#loadFunction();
            this.parent = parent;
        }

        #unloadFunction = undefined;
        #loadFunction = undefined;

        /** Defined a callback to be run when the scene is hidden (switched away from)
         * @param {function} callback
         * @method
         */
        onSceneHidden = (func) => {
            this.#unloadFunction = func;
        }
        /** Defined a callback to be run when the scene is shown (switched to)
         * @param {function} callback
         * @method
         */
        onSceneShown = (func) => {
            this.#loadFunction = func;
        }

        /** Run when the scene is set as inactive / replaced by another scene
         * @method
         * @package
        */
        onUnload = () => {
            if (this.#unloadFunction) this.#unloadFunction();
            this.parent = null;
        }
    },
}