Source: components/renderer/AnimatedSpriteRendererComponent.js

/*global FM*/
/**
 * The animated sprite renderer component is used for drawing sprites with
 * animations, it requires a spritesheet.
 * @class FM.AnimatedSpriteRendererComponent
 * @extends FM.Component
 * @param {FM.ImageAsset} pImage Image of the spritesheet.
 * @param {int} pWidth Width of a frame of the spritesheet.
 * @param {int} pHeight Height of a frame of the spritesheet.
 * @param {FM.GameObject} pOwner Game object owner of the component.
 * @constructor
 * @author Simon Chauvin
 */
FM.AnimatedSpriteRendererComponent = function (pImage, pWidth, pHeight, pOwner) {
    "use strict";
    //Calling the constructor of FM.Component
    FM.Component.call(this, FM.ComponentTypes.RENDERER, pOwner);
    /**
     * Read-only attributes that specifies whether the current animation has
     * finished playing or not.
     * @type boolean
     * @public
     */
    this.finished = false;
    /**
     * Image of the sprite.
     * @type FM.ImageAsset
     * @private
     */
    this.image = pImage.getImage();
    /**
     * Width of the spritesheet.
     * @type int
     * @private
     */
    this.imageWidth = this.image.width;
    /**
     * Height of the spritesheet.
     * @type int
     * @private
     */
    this.imageHeight = this.image.height;
    /**
     * Width of a frame of the spritesheet.
     * @type int
     * @private
     */
    this.frameWidth = pWidth;
    /**
     * Height of a frame of the spritesheet.
     * @type int
     * @private
     */
    this.frameHeight = pHeight;
    /**
     * Width of a resized frame of the spritesheet.
     * @type int
     * @private
     */
    this.changedWidth = pWidth;
    /**
     * Height of a resized frame of the spritesheet.
     * @type int
     * @private
     */
    this.changedHeight = pHeight;
    /**
     * Transparency of the sprite.
     * @type float
     * @private
     */
    this.alpha = 1;
    /**
     * Current animation being played.
     * @type string
     * @private
     */
    this.currentAnim = "";
    /**
     * Whether there is a flipped version of the animation frames or not.
     * @type boolean
     * @private
     */
    this.flipped = false;
    /**
     * Current horizontal offset of position on the spritesheet.
     * @type int
     * @private
     */
    this.xOffset = 0;
    /**
     * Current vertical offset of position on the spritesheet.
     * @type int
     * @private
     */
    this.yOffset = 0;
    /**
     * Frames constituing the animation.
     * @type Array
     * @private
     */
    this.frames = [];
    /**
     * Current frame being displayed.
     * @type int
     * @private
     */
    this.currentFrame = 0;
    /**
     * Current time in seconds.
     * @type float
     * @private
     */
    this.currentTime = 0;
    /**
     * Maximum delay between each frames.
     * @type float
     * @private
     */
    this.delay = 0.1;
    /**
     * Current delay between frames.
     * @type float
     * @private
     */
    this.currentDelay = 0.1;
    /**
     * Whether a specific animation should loop or not.
     * @type Array
     * @private
     */
    this.loop = [];
    /**
     * Spatial component.
     * @type FM.SpatialComponent
     * @private
     */
    this.spatial = pOwner.components[FM.ComponentTypes.SPATIAL];

    //Check if a spatial component is present
    if (!this.spatial && FM.Parameters.debug) {
        console.log("ERROR: No spatial component was added and you need one for rendering.");
    }
    if (!this.image && FM.Parameters.debug) {
        console.log("ERROR: No image was provided and you need one for rendering an animated sprite.");
    }
};
/**
 * FM.AnimatedSpriteRendererComponent inherits from FM.Component.
 */
FM.AnimatedSpriteRendererComponent.prototype = Object.create(FM.Component.prototype);
FM.AnimatedSpriteRendererComponent.prototype.constructor = FM.AnimatedSpriteRendererComponent;
/**
 * Add an animation.
 * @method FM.AnimatedSpriteRendererComponent#addAnimation
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {string} pName Name of the animation.
 * @param {Array} pFrames Frames of the animation.
 * @param {int} pFrameRate Speed of the animation.
 * @param {boolean} pIsLooped Whether the animation should loop or not.
 */
FM.AnimatedSpriteRendererComponent.prototype.addAnimation = function (pName, pFrames, pFrameRate, pIsLooped) {
    "use strict";
    this.currentFrame = 0;
    this.currentAnim = pName;
    this.frames[pName] = pFrames;
    this.delay = 1 / pFrameRate;
    //TODO generate flipped version
    //flipped = isReversed;
    this.currentDelay = this.delay;
    this.loop[pName] = pIsLooped;
};
/**
 * Play the given animation.
 * @method FM.AnimatedSpriteRendererComponent#play
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {string} pAnimName Name of the animation to be played.
 */
FM.AnimatedSpriteRendererComponent.prototype.play = function (pAnimName) {
    "use strict";
    //In case the width of the sprite have been modified
    //imageWidth = image.width;
    //imageHeight = image.height;
    this.currentAnim = pAnimName;
    this.finished = false;
    this.currentFrame = 0;
    this.xOffset = this.frames[this.currentAnim][this.currentFrame] * this.frameWidth;
    this.yOffset = Math.floor(this.xOffset / this.imageWidth) * this.frameHeight;
    if (this.xOffset >= this.imageWidth) {
        this.yOffset = Math.floor(this.xOffset / this.imageWidth) * this.frameHeight;
        this.xOffset = (this.xOffset % this.imageWidth);
        this.xOffset = Math.round(this.xOffset);
        this.yOffset = Math.round(this.yOffset);
    }
};
/**
 * Stop the animation.
 * @method FM.AnimatedSpriteRendererComponent#stop
 * @memberOf FM.AnimatedSpriteRendererComponent
 */
FM.AnimatedSpriteRendererComponent.prototype.stop = function () {
    "use strict";
    this.finished = true;
};
/**
 * Draw the sprite.
 * @method FM.AnimatedSpriteRendererComponent#draw
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {CanvasRenderingContext2D} bufferContext Context on which drawing is 
 * done.
 * @param {FM.Vector} newPosition The position of the object to draw.
 */
FM.AnimatedSpriteRendererComponent.prototype.draw = function (bufferContext, newPosition) {
    "use strict";
    var xPosition = newPosition.x, yPosition = newPosition.y,
        newTime = (new Date()).getTime() / 1000;
    xPosition -= bufferContext.xOffset * this.owner.scrollFactor.x;
    yPosition -= bufferContext.yOffset * this.owner.scrollFactor.y;
    xPosition = Math.round(xPosition);
    yPosition = Math.round(yPosition);
    bufferContext.globalAlpha = this.alpha;
    if (this.spatial.angle !== 0) {
        bufferContext.save();
        bufferContext.translate(Math.round(xPosition), Math.round(yPosition));
        bufferContext.translate(Math.round(this.frameWidth / 2), Math.round(this.frameHeight / 2));
        bufferContext.rotate(this.spatial.angle);
        bufferContext.drawImage(this.image, Math.round(this.xOffset), Math.round(this.yOffset), this.frameWidth, this.frameHeight, Math.round(-this.changedWidth / 2), Math.round(-this.changedHeight / 2), this.changedWidth, this.changedHeight);
        bufferContext.restore();
    } else {
        bufferContext.drawImage(this.image, Math.round(this.xOffset), Math.round(this.yOffset), this.frameWidth, this.frameHeight, Math.round(xPosition), Math.round(yPosition), this.changedWidth, this.changedHeight);
    }
    bufferContext.globalAlpha = 1;
    //If the anim is not finished playing
    if (!this.finished) {
        if (this.currentDelay <= 0) {
            this.currentDelay = this.delay;
            if (this.frames[this.currentAnim]) {
                if (this.frames[this.currentAnim].length > 1) {
                    this.currentFrame = this.currentFrame + 1;
                    //If the current anim exists and that the current frame is the last one
                    if (this.frames[this.currentAnim] && (this.currentFrame === this.frames[this.currentAnim].length - 1)) {
                        if (this.loop[this.currentAnim]) {
                            this.currentFrame = 0;
                        } else {
                            this.finished = true;
                        }
                    }
                } else {
                    this.finished = true;
                }
                this.xOffset = this.frames[this.currentAnim][this.currentFrame] * this.frameWidth;
                this.yOffset = Math.floor(this.xOffset / this.imageWidth) * this.frameHeight;
                if (this.xOffset >= this.imageWidth) {
                    this.yOffset = Math.floor(this.xOffset / this.imageWidth) * this.frameHeight;
                    this.xOffset = (this.xOffset % this.imageWidth);
                    this.xOffset = Math.round(this.xOffset);
                    this.yOffset = Math.round(this.yOffset);
                }
            }
        } else {
            this.currentDelay -= newTime - this.currentTime;
        }
    }
    this.currentTime = newTime;
};
/**
 * Get the current animation being played.
 * @method FM.AnimatedSpriteRendererComponent#getCurrentAnim
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @return {string} The name of the current animation.
 */
FM.AnimatedSpriteRendererComponent.prototype.getCurrentAnim = function () {
    "use strict";
    return this.currentAnim;
};
/**
 * Change the size of the sprite.
 * You will need to change the position of the spatial component of this
 * game object if you need a resize from the center.
 * @method FM.AnimatedSpriteRendererComponent#changeSize
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {float} pFactor Factor by which the size will be changed.
 */
FM.AnimatedSpriteRendererComponent.prototype.changeSize = function (pFactor) {
    "use strict";
    this.changedWidth = pFactor * this.frameWidth;
    this.changedHeight = pFactor * this.frameHeight;
};
/**
 * Set the width of the sprite.
 * You will need to change the position of the spatial component of this
 * game object if you need a resize from the center.
 * @method FM.AnimatedSpriteRendererComponent#setWidth
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {float} pNewWidth New width of the sprite.
 */
FM.AnimatedSpriteRendererComponent.prototype.setWidth = function (pNewWidth) {
    "use strict";
    this.changedWidth = pNewWidth;
};
/**
 * Set the height of the sprite.
 * You will need to change the position of the spatial component of this
 * game object if you need a resize from the center.
 * @method FM.AnimatedSpriteRendererComponent#setHeight
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {float} pNewHeight New height of the sprite.
 */
FM.AnimatedSpriteRendererComponent.prototype.setHeight = function (pNewHeight) {
    "use strict";
    this.changedHeight = pNewHeight;
};
/**
 * Set the transparency of the sprite.
 * @method FM.AnimatedSpriteRendererComponent#setAlpha
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @param {float} pNewAlpha New transparency value desired.
 */
FM.AnimatedSpriteRendererComponent.prototype.setAlpha = function (pNewAlpha) {
    "use strict";
    this.alpha = pNewAlpha;
};
/**
 * Retrieve the height of a frame of the spritesheet.
 * @method FM.AnimatedSpriteRendererComponent#getWidth
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @return {int} The actual width of a frame.
 */
FM.AnimatedSpriteRendererComponent.prototype.getWidth = function () {
    "use strict";
    return this.changedWidth;
};
/**
 * Retrieve the height of a frame of the spritesheet.
 * @method FM.AnimatedSpriteRendererComponent#getHeight
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @return {int} The actual height of a frame.
 */
FM.AnimatedSpriteRendererComponent.prototype.getHeight = function () {
    "use strict";
    return this.changedHeight;
};
/**
 * Retrieve the height of a frame before it was resized.
 * @method FM.AnimatedSpriteRendererComponent#getOriginalWidth
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @return {int} The width of a frame before resizing.
 */
FM.AnimatedSpriteRendererComponent.prototype.getOriginalWidth = function () {
    "use strict";
    return this.frameWidth;
};
/**
 * Retrieve the height of a frame before it was resized.
 * @method FM.AnimatedSpriteRendererComponent#getOriginalHeight
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @return {int} The height of a frame before resizing.
 */
FM.AnimatedSpriteRendererComponent.prototype.getOriginalHeight = function () {
    "use strict";
    return this.frameHeight;
};
/**
 * Retrieve the transparency value of the sprite.
 * @method FM.AnimatedSpriteRendererComponent#getAlpha
 * @memberOf FM.AnimatedSpriteRendererComponent
 * @return {float} Current transparency value.
 */
FM.AnimatedSpriteRendererComponent.prototype.getAlpha = function () {
    "use strict";
    return this.alpha;
};
/**
 * Destroy the component and its objects.
 * @method FM.AnimatedSpriteRendererComponent#destroy
 * @memberOf FM.AnimatedSpriteRendererComponent
 */
FM.AnimatedSpriteRendererComponent.prototype.destroy = function () {
    "use strict";
    this.image = null;
    this.currentAnim = null;
    this.frames = null;
    this.loop = null;
    this.spatial = null;
    this.flipped = null;
    this.changedWidth = null;
    this.changedHeight = null;
    this.delay = null;
    this.currentDelay = null;
    this.currentFrame = null;
    this.finished = null;
    this.xOffset = null;
    this.yOffset = null;
    this.alpha = null;
    this.frameWidth = null;
    this.frameHeight = null;
    this.imageWidth = null;
    this.imageHeight = null;
    FM.Component.prototype.destroy.call(this);
};