Source: animation/Animations.js

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

/**
 * Creates a new instance of the Animations class.
 *
 * @constructor
 *
 * @param {rune.display.Sprite} target The Sprite object to which the animation is linked.
 *
 * @class
 * @classdesc
 * 
 * The Animations class represents a subsystem for managing keyframe-based 
 * animation within a Sprite object.
 */
rune.animation.Animations = function(target) {
    
    //--------------------------------------------------------------------------
    // Private properties
    //--------------------------------------------------------------------------
    
    /**
     * Current animation.
     *
     * @type {rune.animation.Animation}
     * @private
     */
    this.m_animation = null;
    
    /**
     * List of available animations.
     *
     * @type {Array.<rune.animation.Animation>}
     * @private
     */
    this.m_animations = [];
    
    /**
     * Frame data that describes which part of the Sprite object's texture 
     * data is to be used.
     *
     * @type {rune.geom.Rectangle}
     * @private
     */
    this.m_frame = new rune.geom.Rectangle(0, 0, target['canvas']['width'], target['canvas']['height']);
    
    /**
     * Atlas index.
     *
     * @type {number}
     * @private
     */
    this.m_atlasIndex = 0;
    
    /**
     * Target Sprite object.
     *
     * @type {rune.display.Sprite}
     * @private
     */
    this.m_target = target;
};

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

/**
 * Current animation. If this reference is null, no animation has been added.
 *
 * @member {rune.animation.Animation} current
 * @memberof rune.animation.Animations
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animations.prototype, "current", {
    /**
     * @this rune.animation.Animations
     * @ignore
     */
    get : function() {
        return this.m_animation;
    }
});

/**
 * Frame data. This is information that describes which part of the Sprite 
 * object's texture data is to be used for the current animation's current 
 * frame. This reference is primarily intended for internal use, but may be 
 * used for specific purposes.
 *
 * @member {rune.geom.Rectangle} frame
 * @memberof rune.animation.Animations
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animations.prototype, "frame", {
    /**
     * @this rune.animation.Animations
     * @ignore
     */
    get : function() {
        return this.m_frame;
    }
});

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

/**
 * Adds an animation. An animation needs to use a unique ID (name) to be added. 
 * If an animation with the same name already exists, the new animation will 
 * not be added.
 *
 * @param {rune.animation.Animation} animation Animation to be added.
 *
 * @throws {TypeError} If incorrect data type is used.
 *
 * @returns {boolean}
 */
rune.animation.Animations.prototype.add = function(animation) {
	if (animation instanceof rune.animation.Animation) {
        if (this.find(animation['name']) == null) {
            if (this.m_animations.push(animation) === 1) {
                this.m_animation = this.m_animations[0];
            }
            
            return true;
        }
        
        return false;
    } else throw new TypeError();
};

/**
 * Removes all animations.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.clear = function() {
    this.m_animation = null;
    while (this.m_animations.length) {
        this.m_animations[0].dispose();
        this.m_animations[0] = null;
        this.m_animations.splice(0, 1);
    }
};

/**
 * Creates a new animation. After an animation is created, it is automatically 
 * added to the animation system.
 *
 * @param {string} name The name of the animation.
 * @param {Array.<number>} frames Frame sequence.
 * @param {number} framerate Playback speed specified in frames per second.
 * @param {boolean} looped Whether or not to loop the animation sequence.
 *
 * @returns {boolean}
 */
rune.animation.Animations.prototype.create = function(name, frames, framerate, looped) {
	var animation = new rune.animation.Animation(
        name.toLowerCase(),
        frames,
        framerate,
        looped
    );
    
    return this.add(animation);
};

/**
 * Search for an animation in the animation system by its name. If no animation 
 * is found, null is returned.
 *
 * @param {string} name The name of the animation requested.
 *
 * @returns {rune.animation.Animation}
 */
rune.animation.Animations.prototype.find = function(name) {
    var i = this.m_animations.length;
    while (i--) {
        if (this.m_animations[i]['name'] == name) {
            return this.m_animations[i];
        }
    }
    
    return null;
};

/**
 * Changes the animation sequence and goes to a specific frame in that sequence.
 *
 * @param {string} name Requested animation.
 * @param {number} frame Frame to go to.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.goto = function(name, frame) {
    if (this.m_animation != null && this.m_animation['name'] != name) {
        this.m_animation = this.find(name);
        if (this.m_animation != null) {
            this.m_animation.goto(frame);
        }
    }
};

/**
 * Switches animation sequence and goes to a specific frame in that sequence 
 * and resumes playback from there.
 *
 * @param {string} name Requested animation.
 * @param {number} frame Frame to go to.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.gotoAndPlay = function(name, frame) {
    this.goto(name, frame);
    if (this.m_animation != null) {
        this.m_animation.play();
    }
};

/**
 * Changes the animation sequence and goes to a specific frame in that sequence 
 * and stops playback on that frame.
 *
 * @param {string} name Requested animation.
 * @param {number} frame Frame to go to.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.gotoAndStop = function(name, frame) {
    this.goto(name, frame);
    if (this.m_animation != null) {
        this.m_animation.stop();
    }
};

/**
 * Goes to the next frame in the current animation sequence.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.gotoNextFrame = function() {
    if (this.m_animation != null) {
        this.m_animation.gotoNextFrame();
    }
};

/**
 * Goes to the previous frame in the current animation sequence.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.gotoPreviousFrame = function() {
    if (this.m_animation != null) {
        this.m_animation.gotoPreviousFrame();
    }
};

/**
 * Goes to a random frame in the current animation sequence.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.gotoRandomFrame = function() {
    if (this.m_animation != null) {
        this.m_animation.gotoRandomFrame();
    }
};

/**
 * Plays the current animation sequence. If the animation is stopped, playback 
 * resumes from the previous position. If the animation is already playing, 
 * playback continues.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.play = function() {
    if (this.m_animation != null) {
        this.m_animation.play();
    } 
};

/**
 * Removes an animation.
 *
 * @param {string} name Animation to remove.
 *
 * @returns {boolean}
 */
rune.animation.Animations.prototype.remove = function(name) {
    var i = this.m_animations.length;
    while (i--) {
        if (this.m_animations[i]['name'].toLowerCase() == name.toLowerCase()) {
            if (this.m_animation == this.m_animations[i]) {
                this.m_animation = null;
            }
            
            this.m_animations.splice(i, 1)[0].dispose();
            return true;
        }
    }
    
    return false;
};

/**
 * Stops playback of current animation sequence.
 *
 * @returns {undefined}
 */
rune.animation.Animations.prototype.stop = function() {
    if (this.m_animation != null) {
        this.m_animation.stop();
    } 
};

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

/**
 * Clears memory allocated by this instance.
 *
 * @returns {undefined}
 * @ignore
 */
rune.animation.Animations.prototype.dispose = function() {
    this.m_target = null;
    this.m_frame  = null;
    
    this.clear();
};

/**
 * Updates the animation system.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @ignore
 */
rune.animation.Animations.prototype.update = function(step) {
	this.m_updateAnimation(step);
    this.m_updateFrame(step);
};

//------------------------------------------------------------------------------
// Private prototype methods
//------------------------------------------------------------------------------

/**
 * Updates current animation.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.animation.Animations.prototype.m_updateAnimation = function(step) {
    if (this.m_animation != null) {
        this.m_animation.update(step);
    }
};

/**
 * Updates which frame to display in the animation sequence.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 * @suppress {accessControls}
 */
rune.animation.Animations.prototype.m_updateFrame = function(step) {
    if (this.m_animation != null && this.m_animation['atlasIndex'] != this.m_atlasIndex) {
        this.m_atlasIndex = this.m_animation['atlasIndex'];
        
        this.m_frame.x = this.m_frame.width * this.m_atlasIndex;
        this.m_frame.y = 0;
        
        while (this.m_frame.x + this.m_frame.width > this.m_target["texture"]["width"]) {
            this.m_frame.y += this.m_frame.height;
            this.m_frame.x -= this.m_target["texture"]["width"];
        }
        
        this.m_target.breakCache();
    }
};