Source: animation/Animation.js

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

/**
 * Creates a new instance of the Animation class.
 *
 * @constructor
 *
 * @param {string} name The name of the animation; used as a unique ID.
 * @param {Array} frames List of which frames are included in the animation and in which order.
 * @param {number} [framerate=0] Playback speed specified in frames per second.
 * @param {boolean} [looped=false] Whether or not to loop the animation sequence.
 *
 * @class
 * @classdesc
 * 
 * The Animation class represents a keyframe based animation sequence.
 */
rune.animation.Animation = function(name, frames, framerate, looped) {
	
	//--------------------------------------------------------------------------
	// Public properties
	//--------------------------------------------------------------------------
	
	/**
	 * List of animation key frames. The list dictates which frames are 
	 * included in the animation and in which order the frames are played.
	 *
	 * @type {Array}
	 */
	this.frames = frames || [];
	
	/**
	 * Whether (true) or not (false) to loop the animation sequence.
	 *
	 * @type {boolean}
	 */
	this.looped = looped || false;
	
	//--------------------------------------------------------------------------
	// Private properties
	//--------------------------------------------------------------------------
	
	/**
	 * Delay between keyframes.
	 *
	 * @type {number}
	 * @private
	 */
	this.m_delay = parseInt(framerate, 10) > 0 ? parseInt((1 / framerate) * 1000, 10) : 0;
	
	/**
	 * Elapsed time.
	 *
	 * @type {number}
	 * @private
	 */
	this.m_elapsed = 0;
	
	/**
	 * Index of current keyframe.
	 *
	 * @type {number}
	 * @private
	 */
	this.m_index = 0;
	
	/**
	 * The name of the animation.
	 *
	 * @type {string}
	 * @private
	 */
	this.m_name = name || "unnamed";
	
	/**
	 * Whether the animation is paused or not.
	 *
	 * @type {boolean}
	 * @private
	 */
	this.m_paused = false;
	
	//--------------------------------------------------------------------------
	// Constructor call
	//--------------------------------------------------------------------------
	
	/**
	 * Invokes secondary class constructor.
	 */
	this.m_construct();
};

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

/**
 * Describes the current frame based on the animation's atlas texture.
 *
 * @member {number} atlasIndex
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "atlasIndex", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this.frames[this['index']];
	}
});

/**
 * The display time (in milliseconds) of each keyframe.
 *
 * @member {number} delay
 * @memberof rune.animation.Animation
 * @instance
 */
Object.defineProperty(rune.animation.Animation.prototype, "delay", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this.m_delay;
	},
	
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	set : function(value) {
		this.m_delay = rune.util.Math.clamp(parseInt(value, 10), 0, Number.MAX_SAFE_INTEGER);
	}
});

/**
 * The total length of the animation specified in milliseconds.
 *
 * @member {number} duration
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "duration", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this['delay'] * this.frames.length;
	}
});

/**
 * Index of current keyframe within current animation sequence.
 *
 * @member {number} frameIndex
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "frameIndex", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this['index'];
	}
});

/**
 * Playback speed specified in frames per second.
 *
 * @member {number} framerate
 * @memberof rune.animation.Animation
 * @instance
 */
Object.defineProperty(rune.animation.Animation.prototype, "framerate", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return (this['delay'] > 0) ? parseInt(1000 / this['delay'], 10) : 0;
	},
	
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	set : function(value) {
		this['delay'] = parseInt((1 / value) * 1000, 10);
	}
});

/**
 * Length of animation, indicated in number of frames.
 *
 * @member {number} length
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "length", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this.frames.length;
	}
});

/**
 * The name of the animation. This value is used as a unique ID and thus there 
 * can be no two animations with the same name.
 *
 * @member {string} name
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "name", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this.m_name;
	}
});

/**
 * A Boolean value that indicates whether an animation is currently playing.
 *
 * @member {boolean} isPlaying
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "isPlaying", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return !this.m_paused;
	}
});

/**
 * Refers to a subsystem that enables the inclusion of scripts in frames. 
 * Use this reference to add, or delete, scripts for the current animation.
 *
 * @member {rune.animation.AnimationScripts} scripts
 * @memberof rune.animation.Animation
 * @instance
 * @readonly
 */
Object.defineProperty(rune.animation.Animation.prototype, "scripts", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this.m_scripts;
	}
});

//------------------------------------------------------------------------------
// Private getter and setter methods
//------------------------------------------------------------------------------

/**
 * Current frame index.
 *
 * @member {number} index
 * @memberof rune.animation.Animation
 * @instance
 * @private
 */
Object.defineProperty(rune.animation.Animation.prototype, "index", {
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	get : function() {
		return this.m_index;
	},
	
	/**
	 * @this rune.animation.Animation
	 * @ignore
	 */
	set : function(value) {
		if (this.looped === true) {
			this.m_index = rune.util.Math.wrap(value, 0, this.frames.length - 1);
		} else {
			this.m_index = rune.util.Math.clamp(value, 0, this.frames.length - 1);
		}
	}
});

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

/**
 * Jumps to a specific frame within the animation sequence.
 *
 * @param {number} frame Frame to go to.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.goto = function(frame) {
	if (frame > -1) {
		this['index'] = frame;
	}
	
	this.m_elapsed = 0;
};

/**
 * Jumps to a specific frame within the animation sequence and starts playback 
 * from this location.
 *
 * @param {number} frame Frame to go to.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.gotoAndPlay = function(frame) {
	this.goto(frame);
	this.play();
};

/**
 * Jumps to a specific frame within the animation sequence and stops playback 
 * on that frame.
 *
 * @param {number} frame Frame to go to.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.gotoAndStop = function(frame) {
	this.goto(frame);
	this.stop();
};

/**
 * Sends the playhead to the next frame.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.gotoNextFrame = function() {
	this['index']++;
	this.m_elapsed = 0;
};

/**
 * Sends the playhead to the previous frame.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.gotoPreviousFrame = function() {
	this['index']--;
	this.m_elapsed = 0;
};

/**
 * Sends the playhead to a random frame.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.gotoRandomFrame = function() {
	this['index'] = rune.util.Math.randomInt(0, this.frames.length - 1);
	this.m_elapsed = 0;
};

/**
 * Moves the playhead in the timeline of the animation.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.play = function() {
	this.m_paused = false;
};

/**
 * Stops the playhead in the animation.
 *
 * @returns {undefined}
 */
rune.animation.Animation.prototype.stop = function() {
	this.m_paused = true;
};

//------------------------------------------------------------------------------
// Internal prototype methods
//------------------------------------------------------------------------------

/**
 * Clears memory allocated by this instance.
 *
 * @returns {undefined}
 * @package
 * @ignore
 */
rune.animation.Animation.prototype.dispose = function() {
	this.stop();
	this.m_disposeScripts();
};

/**
 * Updates the playback of the animation.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @package
 * @ignore
 */
rune.animation.Animation.prototype.update = function(step) {
	if (this.m_delay > 0 && this.m_paused == false) {
		this.m_elapsed += step;
		
		while (this.m_elapsed  > this.m_delay) {
			   this.m_elapsed -= this.m_delay;
			   this['index']++;
			   
			   this.m_execScript();
		}
	}
};

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

/**
 * The class constructor.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.animation.Animation.prototype.m_construct = function() {
	this.m_constructScripts();
};

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

/**
 * Creates the script subsystem.
 *
 * @returns {undefined}
 * @private
 */
rune.animation.Animation.prototype.m_constructScripts = function() {
	this.m_disposeScripts();
	if (this.m_scripts == null) {
		this.m_scripts = new rune.animation.AnimationScripts();
	} else throw new Error();
};

/**
 * Destroy the subsystem for scripts.
 *
 * @returns {undefined}
 * @private
 */
rune.animation.Animation.prototype.m_disposeScripts = function() {
	if (this.m_scripts instanceof rune.animation.AnimationScripts) {
		this.m_scripts.dispose();
		this.m_scripts = null;
	}
};

/**
 * Executes a script within the subsystem.
 *
 * @returns {undefined}
 * @private
 */
rune.animation.Animation.prototype.m_execScript = function() {
	if (this.m_scripts != null) {
		this.m_scripts.exec(this['index']);	
	}
};