154 lines
4.1 KiB
JavaScript
154 lines
4.1 KiB
JavaScript
import {compose} from '@avocado/core';
|
|
import {hasGraphics, AnimationView} from '@avocado/graphics';
|
|
import {Vector} from '@avocado/math';
|
|
import {Animation} from '@avocado/timing';
|
|
|
|
import {simpleState, Trait} from '../trait';
|
|
|
|
const decorate = compose(
|
|
simpleState('currentAnimation', {
|
|
track: true,
|
|
}),
|
|
simpleState('currentFrame', {
|
|
track: true,
|
|
}),
|
|
);
|
|
|
|
class AnimatedBase extends Trait {
|
|
|
|
static defaultParams() {
|
|
return {
|
|
animations: {},
|
|
};
|
|
}
|
|
|
|
static defaultState() {
|
|
return {
|
|
currentAnimation: 'idle',
|
|
currentFrame: 0,
|
|
};
|
|
}
|
|
|
|
initialize() {
|
|
this.animations = {};
|
|
this.animationViews = undefined;
|
|
this.animationsPromise = undefined;
|
|
}
|
|
|
|
loadAnimations() {
|
|
if (this.animationsPromise) {
|
|
return;
|
|
}
|
|
const animations = this.params.get('animations');
|
|
const animationPromises = [];
|
|
// Load all animations.
|
|
for (const key in animations) {
|
|
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);
|
|
}
|
|
this.animationsPromise = Promise.all(animationPromises);
|
|
// Store keyed animations.
|
|
this.animations = {};
|
|
this.animationsPromise.then((animations) => {
|
|
animations.forEach(({animation, key}) => {
|
|
this.animations[key] = animation;
|
|
// Listen for index changes.
|
|
animation.on('indexChanged', () => {
|
|
if (this.entity.currentAnimation === key) {
|
|
this.entity.currentFrame = animation.index;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
loadAnimationImagesIfPossible() {
|
|
if (!this.entity.container) {
|
|
return;
|
|
}
|
|
if (!this.animationsPromise) {
|
|
return;
|
|
}
|
|
if (this.animationViews) {
|
|
return;
|
|
}
|
|
// Store keyed animation views.
|
|
this.animationViews = {};
|
|
this.animationsPromise.then((animations) => {
|
|
animations.forEach(({animation, key}) => {
|
|
this.animationViews[key] = new AnimationView(animation);
|
|
// Ensure animation is made visible upfront.
|
|
const isCurrentAnimation = key === this.entity.currentAnimation;
|
|
if (isCurrentAnimation) {
|
|
this.entity.container.addChild(this.animationViews[key]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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.
|
|
const oldAnimationView = this.animationViews[oldKey];
|
|
if (oldAnimationView) {
|
|
this.entity.container.removeChild(oldAnimationView);
|
|
}
|
|
const animationView = this.animationViews[this.entity.currentAnimation];
|
|
if (animationView) {
|
|
this.entity.container.addChild(animationView);
|
|
}
|
|
},
|
|
currentFrameChanged: () => {
|
|
// Animation index from current entity frame.
|
|
const animation = this.animations[this.entity.currentAnimation];
|
|
if (!animation) {
|
|
return;
|
|
}
|
|
animation.index = this.entity.currentFrame;
|
|
},
|
|
directionChanged: () => {
|
|
// All animations track direction.
|
|
for (const key in this.animations) {
|
|
const animation = this.animations[key];
|
|
animation.direction = this.entity.direction;
|
|
}
|
|
},
|
|
tick: (elapsed) => {
|
|
// Only tick current animation.
|
|
const animation = this.animations[this.entity.currentAnimation];
|
|
if (!animation) {
|
|
return;
|
|
}
|
|
animation.tick(elapsed);
|
|
},
|
|
traitAdded: (trait) => {
|
|
if (-1 === [
|
|
'animated',
|
|
'graphical'
|
|
].indexOf(trait.constructor.type())) {
|
|
return;
|
|
}
|
|
this.loadAnimations();
|
|
this.loadAnimationImagesIfPossible();
|
|
},
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
export class Animated extends decorate(AnimatedBase) {}
|