src/component/Animation.js
import Component from './Component';
/**
* Animation component to be added to a GameObject to create animations inline with
* the global game loop. Animation Components currently only offer transform interpolation
* frames. Using buildInterpolation can be used to make custom animations which last n frames.
*/
export default class Animation extends Component {
/**
* Constructor for the animation component. A GameObject may have more than one Animation component.
*/
constructor() {
super(false);
this.className = 'Animation';
this.interpolationData = {};
this.animationFrames = 0;
}
/**
* Returns the frames left until all animations are complete.
*/
animationFramesLeft() {
return this.animationFrames;
}
/**
* Checks to see if an animation, defined by the interpol animation key,
* still has frames left to complete the animation.
*
* @param {string} interpol Animation interpolation key.
*/
isAnimating(interpol = null) {
if (interpol == null)
return Object.keys(this.interpolationData).length > 0;
return this.interpolationData[interpol] != null;
}
/**
* Adds an animation to the Animation component. The animation component will keep track
* and trigger the animation frames once per game tick until there are no more frames left.
* On the final frame, the finalFrame callback will be called instead.
*
* @param {string} key Animation interpolation key.
* @param {number} frames How many game ticks will this animation run.
* @param {function} frameChange Callback function executed once per animation frame.
* @param {function} finalFrame Callback function execured as the last frame. Defaults to frameChange.
*/
buildInterpolation(key, frames, frameChange, finalFrame = frameChange) {
this.interpolationData[key] = {
frameChange,
finalFrame,
framesLeft: frames,
};
}
/**
* Clears an animation from executing any more frames. The animation to remove is defined
* by the key parameter.
*
* @param {string} key Animation interpolation key.
*/
clearAnimation(key) {
delete this.interpolationData[key];
}
/**
* Clear all animations on this Animation component.
*/
clearAnimations() {
this.interpolationData = {};
this.animationFrames = 0;
}
/**
* Wrapper function for creating animations of moving from one point to another (translating).
*
* @param {number} posX X position move per frame.
* @param {number} posY Y position move per frame.
* @param {number} posZ Z position move per frame.
* @param {number} frames Amount of frames in this animation.
* @param {number} cycles Amount of times to repeat this animation.
* @param {boolean} reset Reset the position on animation finish.
*/
interpolatePosition(posX = 0, posY = 0, posZ = 0, frames = 1, cycles = 1, reset = false) {
let start = this.gameObject.transform.getPosition();
this.buildInterpolation('position', frames, () => {
this.gameObject.transform.translate(posX, posY, posZ);
}, () => {
this.gameObject.transform.setPosition(start.x + (reset ? 0 : posX * frames), start.y + (reset ? 0 : posY * frames), start.z + (reset ? 0 : posZ * frames));
if (--cycles > 0)
this.interpolatePosition(posX, posY, posZ, frames, cycles, reset);
})
}
/**
* Wrapper function for creating animations to change the scale of the GameObject.
*
* @param {number} posX X scale change per frame.
* @param {number} posY Y scale change per frame.
* @param {number} posZ Z scale change per frame.
* @param {number} frames Amount of frames in this animation.
* @param {number} cycles Amount of times to repeat this animation.
* @param {boolean} reset Reset the position on animation finish.
*/
interpolateScale(scaleX = 0, scaleY = 0, scaleZ = 0, frames = 1, cycles = 1, reset = false) {
let start = this.gameObject.transform.getScale();
this.buildInterpolation('scale', frames, () => {
this.gameObject.transform.scale(scaleX, scaleY, scaleZ);
},() => {
this.gameObject.transform.setScale(start.x + (reset ? 0 : scaleX * frames), start.y + (reset ? 0 : scaleY * frames), start.z + (reset ? 0 : scaleZ * frames));
if (--cycles > 0)
this.interpolateScale(scaleX, scaleY, scaleZ, frames, cycles, reset);
});
}
/**
* Wrapper function for creating animations to rotate the GameObject.
*
* @param {number} posX X scale change per frame.
* @param {number} posY Y scale change per frame.
* @param {number} posZ Z scale change per frame.
* @param {number} frames Amount of frames in this animation.
* @param {number} cycles Amount of times to repeat this animation.
* @param {boolean} reset Reset the position on animation finish.
*/
interpolateRotation(rotate, frames = 1, cycles = 1, reset = false) {
let start = this.gameObject.transform.getRotation();
this.buildInterpolation('rotation', frames, () => {
this.gameObject.transform.rotate(rotate);
},() => {
this.gameObject.transform.setRotation(start + (reset ? 0 : rotate * frames));
if (--cycles > 0)
this.interpolateRotation(rotate, frames, cycles, reset);
});
}
/**
* onUpdate is called automatically as a Component. Executes a frame for each animation.
*/
onUpdate() {
let newFrameCount = --this.animationFrames;
let interpols = Object.keys(this.interpolationData);
for (let i = 0; i < interpols.length; i++) {
let interpol = this.interpolationData[interpols[i]];
newFrameCount = Math.max(this.animationFrames, interpol.framesLeft - 1);
if (interpol.framesLeft > 1) {
interpol.frameChange();
interpol.framesLeft--;
} else if (interpol.framesLeft >= 1) {
interpol.finalFrame();
interpol.framesLeft--;
} else if (interpol.framesLeft < 1) {
delete this.interpolationData[interpols[i]];
}
}
this.animationFrames = newFrameCount;
}
}