Source: scene/Scene.js

//------------------------------------------------------------------------------
// Constructor scope
//------------------------------------------------------------------------------

/** 
 * Creates a new "blank" scene.
 * 
 * @constructor
 * @param {string} [name] The scene name.
 * 
 * @class
 * @classdesc
 * 
 * Represents a scene within the current application. A scene is a combination 
 * of visual (and logical) objects that are updated and rendered within a 
 * controlled scope of an application. An application usually consists of 
 * several scenes; one for menu system, one for gameplay, etc..
 */
rune.scene.Scene = function(name) {
    
    //--------------------------------------------------------------------------
    // Public properties
    //--------------------------------------------------------------------------

    /**
     * Determines whether the current scene should be updated even if it is not 
     * selected. Usually, the update loop is disabled for scenes that are 
     * disabled, ie. are not selected.
     *
     * @type {boolean}
     * @default false
     */
    this.persistent = false;
    
    //--------------------------------------------------------------------------
    // Protected properties
    //--------------------------------------------------------------------------
    
    /**
     * The camera system connected to this Scene.
     *
     * @type {rune.camera.Cameras}
     * @protected
     * @ignore
     */
    this.m_cameras = null;
    
    /**
     * Group manager if the scene objects need to be divided into 
     * groups.
     *
     * @type {rune.display.DisplayGroups}
     * @protected
     * @ignore
     */
    this.m_groups = null;
    
    /**
     * Name of current scene. This string can be used to easily find a 
     * specific scene in a batch of scenes.
     *
     * @type {string}
     * @protected
     * @ignore
     */
    this.m_name = name || "";
    
    /**
     * Represents the main drawing area.
     *
     * @type {rune.display.Stage}
     * @protected
     * @ignore
     */
    this.m_stage = null;
    
    /**
     * A finite-state machine.
     *
     * @type {rune.state.States}
     * @protected
     * @ignore
     */
    this.m_states = null;
    
    /**
     * Timer manager that can be used to create Timer objects for the purpose 
     * of counting down time.
     *
     * @type {rune.timer.Timers}
     * @protected
     * @ignore
     */
    this.m_timers = null;
    
    /**
     * The scene's own subsystem to handle interpolation.
     *
     * @type {rune.tween.Tweens}
     * @private
     */
    this.m_tweens = null;
    
    //--------------------------------------------------------------------------
    // Constructor call
    //--------------------------------------------------------------------------

    /**
     * Call for dedicated constructor method.
     */
    this.m_construct();
};

//------------------------------------------------------------------------------
// Public prototype getter and setter methods
//------------------------------------------------------------------------------

/**
 * Reference to the application's entry point class, ie. the main class of the 
 * application. Useful for accessing the application's subsystem.
 *
 * @member {rune.system.Main} application
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "application", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return rune.system.Application['instance'];
    }
});

/**
 * The camera system connected to this Scene.
 *
 * @member {rune.camera.Cameras} cameras
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "cameras", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_cameras;
    }
});

/**
 * Reference to the developer console. This can be used during the development 
 * stage to execute tests and debugging commands during runtime.
 *
 * @member {rune.console.ConsoleManager} console
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "console", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this['application']['screen']['console'];
    }
});

/**
 * Reference to the application's subsystem for connected gamepad devices.
 *
 * @member {rune.input.Gamepads} gamepads
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "gamepads", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return rune.system.Application["instance"]['inputs']['gamepads'];
    }
});

/**
 * Reference to the scene's built-in group manager. Useful when objects that 
 * appear within the scene need to be divided into groups.
 *
 * @member {rune.display.DisplayGroups} groups
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "groups", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_groups;
    }
});

/**
 * Reference to the keyboard manager. Use this reference to read the state of 
 * any keyboard key.
 *
 * @member {rune.input.Keyboard} keyboard
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "keyboard", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return rune.system.Application["instance"]['inputs']['keyboard'];
    }
});

/**
 * Name of current scene. This string can be used to easily find a 
 * specific scene in a batch of scenes.
 *
 * @member {string} name
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "name", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_name;
    }
});

/**
 * Represents the main drawing area. Display objects added to the Stage are 
 * rendered by the camera system.
 *
 * @member {rune.display.Stage} stage
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "stage", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_stage;
    }
});

/**
 * A finite-state machine.
 *
 * @member {rune.state.States} states
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "states", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_states;
    }
});

/**
 * Timer manager that can be used to create Timer objects for the purpose 
 * of counting down time. Useful if you want to add or remove an object after n 
 * seconds.
 *
 * @member {rune.timer.Timers} timers
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "timers", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_timers;
    }
});

/**
 * Subsystem for handling interpolation within the current scene.
 *
 * @member {rune.tween.Tweens} tweens
 * @memberof rune.scene.Scene
 * @instance
 * @readonly
 */
Object.defineProperty(rune.scene.Scene.prototype, "tweens", {
    /**
     * @this rune.scene.Scene
     * @ignore
     */
    get : function() {
        return this.m_tweens;
    }
});

//------------------------------------------------------------------------------
// Public prototype methods (API)
//------------------------------------------------------------------------------

/**
 * This method is automatically activated by the scene manager (Scenes) when 
 * the current scene is deactivated.
 *
 * @param {rune.scene.Scene} scene Reference to the scene object that replaces the existing scene.
 *
 * @returns {undefined}
 */
rune.scene.Scene.prototype.onDeselect = function(scene) {
    // NOTHING, AS DEFAULT.
};

/**
 * This method is activated automatically by the scene manager (Scenes) when 
 * the current object is activated, ie. is selected.
 *
 * @param {rune.scene.Scene} scene Reference to the previous scene object.
 *
 * @returns {undefined}
 */
rune.scene.Scene.prototype.onSelect = function(scene) {
    // NOTHING, AS DEFAULT.
};

//------------------------------------------------------------------------------
// Public prototype methods (ENGINE)
//------------------------------------------------------------------------------

/**
 * This method is automatically activated by the scene manager (Scenes) as 
 * soon as the current scene is initiated by the game engine. Use this method 
 * to populate the current scene.
 *
 * @returns {undefined}
 */
rune.scene.Scene.prototype.init = function() {
    this.m_initCamera();
};

/**
 * This method is automatically activated by the scene manager for each tick 
 * within the application. Apply scene logic to this method.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 */
rune.scene.Scene.prototype.update = function(step) {
    this.m_updateStage(step);
    this.m_updateCameras(step);
    this.m_updateTimers(step);
    this.m_updateTweens(step);
    this.m_updateGroups(step);
    this.m_updateStates(step);
};

/**
 * Goes through the content of the scene and renders graphics.
 *
 * @returns {undefined}
 */
rune.scene.Scene.prototype.render = function() {
    this.m_renderStates();
};

/**
 * This method is activated automatically by the scene manager (Scenes) when 
 * the current scene ends, ie. is removed and deallocated.
 *
 * @returns {undefined}
 */
rune.scene.Scene.prototype.dispose = function() {
    this.m_disposeStates();
    this.m_disposeGroups();
    this.m_disposeTweens();
    this.m_disposeTimers();
    this.m_disposeCameras();
    this.m_disposeStage();
};

//------------------------------------------------------------------------------
// Protected prototype methods
//------------------------------------------------------------------------------

/**
 * Method that represents the class constructor.
 * 
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_construct = function() {
    this.m_constructStage();
    this.m_constructCameras();
    this.m_constructTimers();
    this.m_constructTweens();
    this.m_constructGroups();
    this.m_constructStates();
};

/**
 * Creates the stage.
 *
 * @throws {Error} If an object reference already exists.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_constructStage = function() {
    this.m_disposeStage();
    if (this.m_stage == null) {
        this.m_stage = new rune.display.Stage();
        this.m_stage['backgroundColor'] = "#000000";
    } else throw new Error();
};

/**
 * Creates and activates the camera system.
 *
 * @throws {Error} If an object reference already exists.
 *
 * @returns {undefined}
 * @private
 */
rune.scene.Scene.prototype.m_constructCameras = function() {
    this.m_disposeCameras();
    if (this.m_cameras == null && this.m_stage != null) {
        this.m_cameras = new rune.camera.Cameras(this.m_stage);
    } else throw new Error();
};

/**
 * Creates the subsystem for timers.
 *
 * @throws {Error} If an object reference already exists.
 *
 * @returns {undefined}
 * @private
 */
rune.scene.Scene.prototype.m_constructTimers = function() {
    this.m_disposeTimers();
    if (this.m_timers == null) {
        this.m_timers = new rune.timer.Timers(this);
    } else throw new Error();
};

/**
 * Creates the subsystem for interpolation.
 *
 * @throws {Error} If an object reference already exists.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_constructTweens = function() {
    this.m_disposeTweens();
    if (this.m_tweens == null) {
        this.m_tweens = new rune.tween.Tweens();
    } else throw new Error();
};

/**
 * Creates the group manager of the scene.
 *
 * @throws {Error} If an object reference already exists.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_constructGroups = function() {
    this.m_disposeGroups();
    if (this.m_groups == null) {
        this.m_groups = new rune.display.DisplayGroups();
    } else throw new Error();
};

/**
 * Creates the scene's finite-state machine.
 *
 * @throws {Error} If an object reference already exists.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_constructStates = function() {
    this.m_disposeStates();
    if (this.m_states == null) {
        this.m_states = new rune.state.States(this);
    } else throw new Error();
};

/**
 * Creates a camera object with the same resolution as the application. 
 * Override this method if, for example, multiple camera objects are to be 
 * created.
 *
 * @returns {undefined}
 * @protected
 */
rune.scene.Scene.prototype.m_initCamera = function() {
    if (this.m_cameras != null) {
        this.m_cameras.addCamera(this.m_cameras.createCamera());
    }
};

/**
 * Updates all stage objects.
 *
 * @param {number} step The current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_updateStage = function(step) {
    if (this.m_stage != null) {
        this.m_stage.update(step);
    }
};

/**
 * Updates all camera objects.
 *
 * @param {number} step The current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_updateCameras = function(step) {
    if (this.m_cameras != null) {
        this.m_cameras.update(step);
        //@note: Should cameras be affected by the scene's time scale?
        //@note: Maybe a flag in the Cameras object that indicates whether scale should be used?
    }
};

/**
 * Updates all timers.
 *
 * @param {number} step The current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_updateTimers = function(step) {
    if (this.m_timers != null) {
        this.m_timers.update(step * this.m_stage.timeScale);
    }
};

/**
 * Updates the interpolation subsystem.
 *
 * @param {number} step The current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_updateTweens = function(step) {
    if (this.m_tweens != null) {
        this.m_tweens.update(step * this.m_stage.timeScale);
    }
};

/**
 * Updating groups.
 *
 * @param {number} step The current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_updateGroups = function(step) {
    if (this.m_groups != null) {
        this.m_groups.update(step * this.m_stage.timeScale);
    }
};

/**
 * Updates the scene's finite-state machine.
 *
 * @param {number} step The current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_updateStates = function(step) {
    if (this.m_states != null) {
        this.m_states.update(step * this.m_stage.timeScale);
    }
};

/**
 * Executes rendering code in current state.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_renderStates = function() {
    if (this.m_states != null) {
        this.m_states.render();
    }
};

/**
 * Removes the scene's finite-state machine.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_disposeStates = function() {
    if (this.m_states instanceof rune.state.States) {
        this.m_states.dispose();
        this.m_states = null;
    }
};

/**
 * Removes all groups.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_disposeGroups = function() {
    if (this.m_groups instanceof rune.display.DisplayGroups) {
        this.m_groups.dispose();
        this.m_groups = null;
    }
};

/**
 * Removes the interpolation subsystem.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_disposeTweens = function() {
    if (this.m_tweens instanceof rune.tween.Tweens) {
        this.m_tweens.dispose();
        this.m_tweens = null;
    }
};

/**
 * Removes the timers subsystem.
 *
 * @returns {undefined}
 * @private
 */
rune.scene.Scene.prototype.m_disposeTimers = function() {
    if (this.m_timers instanceof rune.timer.Timers) {
        this.m_timers.dispose();
        this.m_timers = null;
    }
};

/**
 * Removes the camera system.
 *
 * @throws {TypeError} If the reference to the camera system is invalid.
 *
 * @returns {undefined}
 * @private
 */
rune.scene.Scene.prototype.m_disposeCameras = function() {
    if (this.m_cameras instanceof rune.camera.Cameras) {
        this.m_cameras.dispose();
        this.m_cameras = null;
    }
};

/**
 * Removes the stage.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.scene.Scene.prototype.m_disposeStage = function() {
    if (this.m_stage != null) {
        this.m_stage.dispose();
        this.m_stage = null;
    }
};