Source: Sprite_Animation.js

Sprite_Animation.js

//-----------------------------------------------------------------------------
// Sprite_Animation
//
// The sprite for displaying an animation.
/**
 * The sprite for displaying an animation.
 *
 * @class
 * @extends Sprite
 */
function Sprite_Animation() {
    this.initialize(...arguments);
}

Sprite_Animation.prototype = Object.create(Sprite.prototype);
Sprite_Animation.prototype.constructor = Sprite_Animation;

Sprite_Animation.prototype.initialize = function() {
    Sprite.prototype.initialize.call(this);
    this.initMembers();
};

/**
 * Initialize sprite variables
 */
Sprite_Animation.prototype.initMembers = function() {
    this._targets = [];
    this._animation = null;
    this._mirror = false;
    this._delay = 0;
    this._previous = null;
    this._effect = null;
    this._handle = null;
    this._playing = false;
    this._started = false;
    this._frameIndex = 0;
    this._maxTimingFrames = 0;
    this._flashColor = [0, 0, 0, 0];
    this._flashDuration = 0;
    this._viewportSize = 4096;
    this.z = 8;
};

Sprite_Animation.prototype.destroy = function(options) {
    Sprite.prototype.destroy.call(this, options);
    if (this._handle) {
        this._handle.stop();
    }
    this._effect = null;
    this._handle = null;
    this._playing = false;
    this._started = false;
};

// prettier-ignore
/**
 * Set up the animation sprite
 *
 * @param {Array] targets - The targets of the animation
 * @param {Object} animation - The animation object to play
 * @param {boolean} mirror - If the animation should mirror
 * @param {number} delay - Frame delay for the animation
 * @param {Sprite_Animation|Sprite_AnimationMV|null} previous - The previous animation sprite
 */
Sprite_Animation.prototype.setup = function(
    targets, animation, mirror, delay, previous
) {
    this._targets = targets;
    this._animation = animation;
    this._mirror = mirror;
    this._delay = delay;
    this._previous = previous;
    this._effect = EffectManager.load(animation.effectName);
    this._playing = true;
    const timings = animation.soundTimings.concat(animation.flashTimings);
    for (const timing of timings) {
        if (timing.frame > this._maxTimingFrames) {
            this._maxTimingFrames = timing.frame;
        }
    }
};

Sprite_Animation.prototype.update = function() {
    Sprite.prototype.update.call(this);
    if (this._delay > 0) {
        this._delay--;
    } else if (this._playing) {
        if (!this._started && this.canStart()) {
            if (this._effect) {
                if (this._effect.isLoaded) {
                    this._handle = Graphics.effekseer.play(this._effect);
                    this._started = true;
                } else {
                    EffectManager.checkErrors();
                }
            } else {
                this._started = true;
            }
        }
        if (this._started) {
            this.updateEffectGeometry();
            this.updateMain();
            this.updateFlash();
        }
    }
};

/**
 * Check if the animation can start playing
 *
 * @return {boolean} True if the animation can start
 */
Sprite_Animation.prototype.canStart = function() {
    if (this._previous && this.shouldWaitForPrevious()) {
        return !this._previous.isPlaying();
    } else {
        return true;
    }
};

/**
 * Check if the animation should wait for the previous animation to finish
 *
 * @return {boolean} True if animation should wait for the previous animation to finish
 */
Sprite_Animation.prototype.shouldWaitForPrevious = function() {
    // [Note] Older versions of Effekseer were very heavy on some mobile
    //   devices. We don't need this anymore.
    return false;
};

/**
 * Update for effect geometry
 */
Sprite_Animation.prototype.updateEffectGeometry = function() {
    const scale = this._animation.scale / 100;
    const r = Math.PI / 180;
    const rx = this._animation.rotation.x * r;
    const ry = this._animation.rotation.y * r;
    const rz = this._animation.rotation.z * r;
    if (this._handle) {
        this._handle.setLocation(0, 0, 0);
        this._handle.setRotation(rx, ry, rz);
        this._handle.setScale(scale, scale, scale);
        this._handle.setSpeed(this._animation.speed / 100);
    }
};

/**
 * Update for main effects
 */
Sprite_Animation.prototype.updateMain = function() {
    this.processSoundTimings();
    this.processFlashTimings();
    this._frameIndex++;
    this.checkEnd();
};

/**
 * Handles playing sound effects at the right frame
 */
Sprite_Animation.prototype.processSoundTimings = function() {
    for (const timing of this._animation.soundTimings) {
        if (timing.frame === this._frameIndex) {
            AudioManager.playSe(timing.se);
        }
    }
};

/**
 * Handles showing flashes at the right frame
 */
Sprite_Animation.prototype.processFlashTimings = function() {
    for (const timing of this._animation.flashTimings) {
        if (timing.frame === this._frameIndex) {
            this._flashColor = timing.color.clone();
            this._flashDuration = timing.duration;
        }
    }
};

/**
 * Check if the animation has ended. Sets playing to false if ended
 */
Sprite_Animation.prototype.checkEnd = function() {
    if (
        this._frameIndex > this._maxTimingFrames &&
        this._flashDuration === 0 &&
        !(this._handle && this._handle.exists)
    ) {
        this._playing = false;
    }
};

/**
 * Update for flash effects
 */
Sprite_Animation.prototype.updateFlash = function() {
    if (this._flashDuration > 0) {
        const d = this._flashDuration--;
        this._flashColor[3] *= (d - 1) / d;
        for (const target of this._targets) {
            target.setBlendColor(this._flashColor);
        }
    }
};

/**
 * Check if the animation is playing
 *
 * @return {boolean} True if animation is currently playing
 */
Sprite_Animation.prototype.isPlaying = function() {
    return this._playing;
};

/**
 * Sets the rotation of the animation
 *
 * @param {number} x - The x value of the rotation
 * @param {number} y - The y value of the rotation
 * @param {number} z - The z value of the rotation
 */
Sprite_Animation.prototype.setRotation = function(x, y, z) {
    if (this._handle) {
        this._handle.setRotation(x, y, z);
    }
};

Sprite_Animation.prototype._render = function(renderer) {
    if (this._targets.length > 0 && this._handle && this._handle.exists) {
        this.onBeforeRender(renderer);
        this.setProjectionMatrix(renderer);
        this.setCameraMatrix(renderer);
        this.setViewport(renderer);
        Graphics.effekseer.beginDraw();
        Graphics.effekseer.drawHandle(this._handle);
        Graphics.effekseer.endDraw();
        this.resetViewport(renderer);
        this.onAfterRender(renderer);
    }
};

/**
 * Sets the projection matrix for the animation
 *
 * @param {PIXI.Renderer} The renderer for the game
 */
Sprite_Animation.prototype.setProjectionMatrix = function(renderer) {
    const x = this._mirror ? -1 : 1;
    const y = -1;
    const p = -(this._viewportSize / renderer.view.height);
    // prettier-ignore
    Graphics.effekseer.setProjectionMatrix([
        x, 0, 0, 0,
        0, y, 0, 0,
        0, 0, 1, p,
        0, 0, 0, 1,
    ]);
};

/**
 * Sets the camera matrix for the animation
 */
Sprite_Animation.prototype.setCameraMatrix = function(/*renderer*/) {
    // prettier-ignore
    Graphics.effekseer.setCameraMatrix([
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, -10, 1
    ]);
};

/**
 * Sets the viewport for the animation
 *
 * @param {PIXI.Renderer} The renderer for the game
 */
Sprite_Animation.prototype.setViewport = function(renderer) {
    const vw = this._viewportSize;
    const vh = this._viewportSize;
    const vx = this._animation.offsetX - vw / 2;
    const vy = this._animation.offsetY - vh / 2;
    const pos = this.targetPosition(renderer);
    renderer.gl.viewport(vx + pos.x, vy + pos.y, vw, vh);
};

/**
 * Sets the target position for the animation
 *
 * @param {PIXI.Renderer} The renderer for the game
 * @return {Point} The point representing the x and y coordinates of the animation
 */
Sprite_Animation.prototype.targetPosition = function(renderer) {
    const pos = new Point();
    if (this._animation.displayType === 2) {
        pos.x = renderer.view.width / 2;
        pos.y = renderer.view.height / 2;
    } else {
        for (const target of this._targets) {
            const tpos = this.targetSpritePosition(target);
            pos.x += tpos.x;
            pos.y += tpos.y;
        }
        pos.x /= this._targets.length;
        pos.y /= this._targets.length;
    }
    return pos;
};

/**
 * Gets the target sprite's position
 *
 * @param {Sprite} The sprite to show the animation on
 * @return {Point} A point representing the x/y coordinates where the sprite is
 */
Sprite_Animation.prototype.targetSpritePosition = function(sprite) {
    const point = new Point(0, -sprite.height / 2);
    if (this._animation.alignBottom) {
        point.y = 0;
    }
    sprite.updateTransform();
    return sprite.worldTransform.apply(point);
};

/**
 * Resets the viewport
 *
 * @param {PIXI.Renderer} The renderer for the game
 */
Sprite_Animation.prototype.resetViewport = function(renderer) {
    renderer.gl.viewport(0, 0, renderer.view.width, renderer.view.height);
};

/**
 * Handling before render occurs
 *
 * @param {PIXI.Renderer} The renderer for the game
 */
Sprite_Animation.prototype.onBeforeRender = function(renderer) {
    renderer.batch.flush();
    renderer.geometry.reset();
};

/**
 * Handling after render occurs
 *
 * @param {PIXI.Renderer} The renderer for the game
 */
Sprite_Animation.prototype.onAfterRender = function(renderer) {
    renderer.texture.reset();
    renderer.geometry.reset();
    renderer.state.reset();
    renderer.shader.reset();
    renderer.framebuffer.reset();
};