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) {}