avocado-old/packages/entity/traits/animated.js

196 lines
5.2 KiB
JavaScript
Raw Normal View History

2019-03-18 22:20:03 -05:00
import {compose} from '@avocado/core';
2019-03-20 19:48:31 -05:00
import {AnimationView} from '@avocado/graphics';
2019-03-19 20:58:19 -05:00
import {Rectangle, Vector} from '@avocado/math';
import {Animation} from '@avocado/timing';
2019-03-18 22:20:03 -05:00
import {simpleState, Trait} from '../trait';
const decorate = compose(
simpleState('currentAnimation', {
track: true,
}),
2019-03-18 22:20:03 -05:00
simpleState('currentFrame', {
track: true,
}),
);
class AnimatedBase extends Trait {
static defaultParams() {
return {
animations: {},
2019-03-18 22:20:03 -05:00
};
}
static defaultState() {
return {
currentAnimation: 'idle',
2019-03-18 22:20:03 -05:00
currentFrame: 0,
};
}
2019-03-20 23:23:34 -05:00
initialize() {
this.animations = {};
2019-03-21 00:09:17 -05:00
this.animationListeners = {};
2019-03-20 23:23:34 -05:00
this.animationViews = undefined;
this.animationsPromise = undefined;
}
destroy() {
if (this.animationViews) {
for (const key in this.animationViews) {
this.hideAnimation(key);
const animationView = this.animationViews[key];
animationView.destroy();
}
}
2019-03-21 00:09:17 -05:00
for (const key in this.animations) {
const animation = this.animations[key];
animation.off('indexChanged', this.animationListeners[key]);
animation.destroy();
}
2019-03-20 23:23:34 -05:00
}
2019-03-19 20:58:19 -05:00
hideAnimation(key) {
if (!this.animationViews) {
return;
}
const animationView = this.animationViews[key];
if (!animationView) {
return;
}
this.entity.container.removeChild(animationView);
}
loadAnimations() {
if (this.animationsPromise) {
return;
}
const animations = this.params.get('animations');
const animationPromises = [];
// Load all animations.
for (const key in animations) {
2019-03-19 21:11:04 -05:00
const {uri} = animations[key];
const promise = Animation.load(uri).then((animation) => {
// Zip with key to make populating animations and views trivial.
return {animation, key};
});
animationPromises.push(promise);
2019-03-19 10:46:20 -05:00
}
this.animationsPromise = Promise.all(animationPromises);
// Store keyed animations.
this.animations = {};
this.animationsPromise.then((animations) => {
animations.forEach(({animation, key}) => {
this.animations[key] = animation;
// Set direction upfront.
animation.direction = this.entity.direction;
// Listen for index changes.
2019-03-21 00:09:17 -05:00
this.animationListeners[key] = () => {
if (this.entity.currentAnimation === key) {
this.entity.currentFrame = animation.index;
}
2019-03-21 00:09:17 -05:00
};
animation.on('indexChanged', this.animationListeners[key]);
});
});
}
loadAnimationImagesIfPossible() {
if (!this.entity.container) {
2019-03-19 11:29:07 -05:00
return;
}
if (!this.animationsPromise) {
return;
}
if (this.animationViews) {
return;
}
// Store keyed animation views.
this.animationViews = {};
this.animationsPromise.then((animations) => {
2019-03-20 21:58:14 -05:00
const animationParams = this.params.get('animations');
animations.forEach(({animation, key}) => {
this.animationViews[key] = new AnimationView(animation);
2019-03-20 21:58:14 -05:00
// Calculate any offset.
const animationView = this.animationViews[key];
const size = Rectangle.size(animation.sourceRectangle);
const halfway = Vector.scale(size, -0.5);
const {offset} = animationParams[key];
animationView.position = Vector.add(halfway, offset);
// Ensure animation is made visible upfront.
const isCurrentAnimation = key === this.entity.currentAnimation;
if (isCurrentAnimation) {
2019-03-19 20:58:19 -05:00
this.showAnimation(key);
}
});
});
}
2019-03-19 20:58:19 -05:00
showAnimation(key) {
if (!this.animationViews) {
return;
}
const animationView = this.animationViews[key];
if (!animationView) {
return;
}
this.entity.container.addChild(animationView);
}
2019-03-18 22:20:03 -05:00
listeners() {
return {
currentAnimationChanged: (oldKey) => {
// Reset old animation.
const oldAnimation = this.animations[oldKey];
if (oldAnimation) {
oldAnimation.reset();
}
// Only client/graphics.
if (!this.animationViews) {
return;
}
// Swap the animation.
2019-03-19 20:58:19 -05:00
this.hideAnimation(oldKey);
this.showAnimation(this.entity.currentAnimation);
},
2019-03-18 22:20:03 -05:00
currentFrameChanged: () => {
// Animation index from current entity frame.
const animation = this.animations[this.entity.currentAnimation];
if (!animation) {
return;
}
animation.index = this.entity.currentFrame;
2019-03-18 22:20:03 -05:00
},
directionChanged: () => {
// All animations track direction.
for (const key in this.animations) {
const animation = this.animations[key];
animation.direction = this.entity.direction;
}
2019-03-18 22:20:03 -05:00
},
tick: (elapsed) => {
// Only tick current animation.
const animation = this.animations[this.entity.currentAnimation];
if (!animation) {
2019-03-18 22:20:03 -05:00
return;
}
animation.tick(elapsed);
2019-03-18 22:20:03 -05:00
},
traitAdded: (trait) => {
if (-1 === [
'animated',
2019-03-20 19:48:52 -05:00
'graphical',
].indexOf(trait.constructor.type())) {
2019-03-18 22:20:03 -05:00
return;
}
this.loadAnimations();
this.loadAnimationImagesIfPossible();
2019-03-18 22:20:03 -05:00
},
};
}
}
export class Animated extends decorate(AnimatedBase) {}