refactor: animated trait uses Animation
This commit is contained in:
parent
042e6ae071
commit
72459bc0a9
|
@ -1,10 +1,14 @@
|
|||
import {compose} from '@avocado/core';
|
||||
import {Image, Sprite} from '@avocado/graphics';
|
||||
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,
|
||||
}),
|
||||
|
@ -14,80 +18,132 @@ class AnimatedBase extends Trait {
|
|||
|
||||
static defaultParams() {
|
||||
return {
|
||||
frameCount: 0,
|
||||
frameRate: 0,
|
||||
frameSize: [0, 0],
|
||||
image: '',
|
||||
animations: {},
|
||||
};
|
||||
}
|
||||
|
||||
static defaultState() {
|
||||
return {
|
||||
currentAnimation: 'idle',
|
||||
currentFrame: 0,
|
||||
};
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.sprite = undefined;
|
||||
this.frameCount = this.params.get('frameCount');
|
||||
this.frameRate = this.params.get('frameRate');
|
||||
this.frameSize = this.params.get('frameSize');
|
||||
this.image = this.params.get('image');
|
||||
this.frameCaret = this.frameRate;
|
||||
this.loadSpriteImageIfPossible();
|
||||
this.animations = {};
|
||||
this.animationViews = undefined;
|
||||
this.animationsPromise = undefined;
|
||||
}
|
||||
|
||||
loadSpriteImageIfPossible() {
|
||||
if (!this.entity.container) {
|
||||
loadAnimations() {
|
||||
if (this.animationsPromise) {
|
||||
return;
|
||||
}
|
||||
if (!this.image) {
|
||||
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);
|
||||
}
|
||||
Image.load(this.image).then((image) => {
|
||||
this.sprite = new Sprite(image);
|
||||
this.entity.container.addChild(this.sprite);
|
||||
this.updateFrameRect();
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateFrameRect() {
|
||||
if (!this.sprite) {
|
||||
loadAnimationImagesIfPossible() {
|
||||
if (!this.entity.container) {
|
||||
return;
|
||||
}
|
||||
const direction = ('direction' in this.entity) ? this.entity.direction : 0;
|
||||
this.sprite.sourceRectangle = [
|
||||
this.entity.currentFrame * this.frameSize[0],
|
||||
direction * this.frameSize[1],
|
||||
this.frameSize[0],
|
||||
this.frameSize[1],
|
||||
];
|
||||
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: () => {
|
||||
this.updateFrameRect();
|
||||
// Animation index from current entity frame.
|
||||
const animation = this.animations[this.entity.currentAnimation];
|
||||
if (!animation) {
|
||||
return;
|
||||
}
|
||||
animation.index = this.entity.currentFrame;
|
||||
},
|
||||
directionChanged: () => {
|
||||
this.updateFrameRect();
|
||||
// All animations track direction.
|
||||
for (const key in this.animations) {
|
||||
const animation = this.animations[key];
|
||||
animation.direction = this.entity.direction;
|
||||
}
|
||||
},
|
||||
tick: (elapsed) => {
|
||||
this.frameCaret -= elapsed;
|
||||
if (this.frameCaret > 0) {
|
||||
// Only tick current animation.
|
||||
const animation = this.animations[this.entity.currentAnimation];
|
||||
if (!animation) {
|
||||
return;
|
||||
}
|
||||
const newFrame = (this.entity.currentFrame + 1) % this.frameCount;
|
||||
this.entity.currentFrame = newFrame;
|
||||
if (this.frameCaret < 0) {
|
||||
this.frameCaret = this.frameRate - this.frameCaret;
|
||||
}
|
||||
animation.tick(elapsed);
|
||||
},
|
||||
traitAdded: (trait) => {
|
||||
if ('graphical' !== trait.constructor.type()) {
|
||||
if (-1 === [
|
||||
'animated',
|
||||
'graphical'
|
||||
].indexOf(trait.constructor.type())) {
|
||||
return;
|
||||
}
|
||||
this.loadSpriteImageIfPossible();
|
||||
this.loadAnimations();
|
||||
this.loadAnimationImagesIfPossible();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user