flow
This commit is contained in:
parent
455a7dacf3
commit
89278f1d9c
|
@ -1,6 +1,9 @@
|
||||||
export {
|
export {
|
||||||
default as fastApply,
|
default as fastApply,
|
||||||
} from './fast-apply';
|
} from './fast-apply';
|
||||||
|
export {
|
||||||
|
default as mapValuesAsync,
|
||||||
|
} from './map-values-async';
|
||||||
export {
|
export {
|
||||||
mergeDiff,
|
mergeDiff,
|
||||||
mergeDiffArray,
|
mergeDiffArray,
|
||||||
|
|
3
packages/core/src/map-values-async.js
Normal file
3
packages/core/src/map-values-async.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default async (o, f) => Object.fromEntries(
|
||||||
|
await Promise.all(Object.entries(o).map(async ([key, v]) => [key, await f(v)])),
|
||||||
|
);
|
|
@ -29,6 +29,9 @@ export default class Container extends Renderable {
|
||||||
if (this.container.isFake) {
|
if (this.container.isFake) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (-1 !== this._children.indexOf(child)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
child.parent = this;
|
child.parent = this;
|
||||||
this.isDirty = true;
|
this.isDirty = true;
|
||||||
|
|
91
packages/sound/src/traits/audible.js
Normal file
91
packages/sound/src/traits/audible.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import {Trait} from '@avocado/traits';
|
||||||
|
|
||||||
|
import Sound from '../sound';
|
||||||
|
|
||||||
|
const {
|
||||||
|
SIDE,
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
export default class Audible extends Trait {
|
||||||
|
|
||||||
|
#sounds;
|
||||||
|
|
||||||
|
constructor(json) {
|
||||||
|
super(json);
|
||||||
|
this.#sounds = json.sounds || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static behaviorTypes() {
|
||||||
|
return {
|
||||||
|
hasSound: {
|
||||||
|
type: 'bool',
|
||||||
|
label: 'Has $1 sound',
|
||||||
|
args: [
|
||||||
|
['key', {
|
||||||
|
type: 'string',
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
playSound: {
|
||||||
|
type: 'void',
|
||||||
|
label: 'Play the $1 sound.',
|
||||||
|
args: [
|
||||||
|
['key', {
|
||||||
|
type: 'string',
|
||||||
|
options: (entity) => this.optionsForSounds(entity),
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultParams() {
|
||||||
|
return {
|
||||||
|
sounds: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeParams() {
|
||||||
|
return {
|
||||||
|
sounds: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Sounds',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
Object.values(this.#sounds).forEach((sound) => {
|
||||||
|
sound.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async extendJson(json) {
|
||||||
|
const extended = await super.extendJson(json);
|
||||||
|
if (this.params.sounds && 'client' === SIDE) {
|
||||||
|
extended.sounds = Object.fromEntries(
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(this.params.sounds)
|
||||||
|
.map(async ([key, sound]) => [key, await Sound.load(sound.uri)]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
methods() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
hasSound: (key) => !!this.#sounds[key],
|
||||||
|
|
||||||
|
playSound: (key) => this.hasSound(key) && this.#sounds[key].play(),
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static optionsForSounds(entity) {
|
||||||
|
return Object.keys(entity.trait('Audible').params.sounds)
|
||||||
|
.reduce((r, key) => ({...r, [key]: key}), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
import {Trait} from '@avocado/traits';
|
|
||||||
|
|
||||||
import Sound from '../sound';
|
|
||||||
|
|
||||||
export default class Audible extends Trait {
|
|
||||||
|
|
||||||
static behaviorTypes() {
|
|
||||||
return {
|
|
||||||
hasSound: {
|
|
||||||
type: 'bool',
|
|
||||||
label: 'Has $1 sound',
|
|
||||||
args: [
|
|
||||||
['key', {
|
|
||||||
type: 'string',
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
playSound: {
|
|
||||||
type: 'void',
|
|
||||||
label: 'Play the $1 sound.',
|
|
||||||
args: [
|
|
||||||
['key', {
|
|
||||||
type: 'string',
|
|
||||||
options: (entity) => this.optionsForSounds(entity),
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultParams() {
|
|
||||||
return {
|
|
||||||
sounds: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static describeParams() {
|
|
||||||
return {
|
|
||||||
sounds: {
|
|
||||||
type: 'object',
|
|
||||||
label: 'Sounds',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
hydrate() {
|
|
||||||
if (AVOCADO_CLIENT) {
|
|
||||||
this.loadSounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(entity, params, state) {
|
|
||||||
super(entity, params, state);
|
|
||||||
this._sounds = this.params.sounds;
|
|
||||||
this.sounds = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
const sounds = Object.values(this.sounds);
|
|
||||||
for (let i = 0; i < sounds.length; i++) {
|
|
||||||
sounds[i].destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSounds() {
|
|
||||||
const keys = Object.keys(this._sounds);
|
|
||||||
const soundPromises = [];
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
const key = keys[i];
|
|
||||||
const soundJSON = this._sounds[key];
|
|
||||||
soundPromises.push(Sound.load(soundJSON.uri));
|
|
||||||
}
|
|
||||||
const soundsPromise = Promise.all(soundPromises);
|
|
||||||
soundsPromise.then((sounds) => {
|
|
||||||
for (let i = 0; i < sounds.length; i++) {
|
|
||||||
const key = keys[i];
|
|
||||||
const sound = sounds[i];
|
|
||||||
this.sounds[key] = sound;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
methods() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
hasSound: (key) => !!this.sounds[key],
|
|
||||||
|
|
||||||
playSound: (key) => {
|
|
||||||
const sound = this.sounds[key];
|
|
||||||
if (!sound) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sound.play();
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static optionsForSounds(entity) {
|
|
||||||
return Object.keys(entity.trait('Audible').params.sounds)
|
|
||||||
.reduce((r, key) => ({...r, [key]: key}), {});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -27,7 +27,8 @@
|
||||||
"@avocado/traits": "^2.0.0",
|
"@avocado/traits": "^2.0.0",
|
||||||
"@latus/core": "2.0.0",
|
"@latus/core": "2.0.0",
|
||||||
"@latus/socket": "2.0.0",
|
"@latus/socket": "2.0.0",
|
||||||
"debug": "4.3.1"
|
"debug": "4.3.1",
|
||||||
|
"lodash.mapvalues": "^4.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutrinojs/airbnb-base": "^9.4.0",
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
|
|
@ -9,4 +9,4 @@ export {default as Animation} from './animation';
|
||||||
export {default as Lfo} from './lfo';
|
export {default as Lfo} from './lfo';
|
||||||
export {default as Ticker} from './ticker';
|
export {default as Ticker} from './ticker';
|
||||||
export {default as TimedIndex} from './timed-index';
|
export {default as TimedIndex} from './timed-index';
|
||||||
export {TransitionMixin as Transition, TransitionResult} from './transition';
|
export {default as Transition, TransitionResult} from './transition';
|
||||||
|
|
|
@ -2,13 +2,10 @@ import {Packet} from '@latus/socket';
|
||||||
|
|
||||||
export default class TraitUpdateAnimatedPacket extends Packet {
|
export default class TraitUpdateAnimatedPacket extends Packet {
|
||||||
|
|
||||||
static get schema() {
|
static get data() {
|
||||||
return {
|
return {
|
||||||
...super.schema,
|
currentAnimation: 'string',
|
||||||
data: {
|
isAnimating: 'bool',
|
||||||
currentAnimation: 'string',
|
|
||||||
isAnimating: 'bool',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import {mapValuesAsync} from '@avocado/core';
|
||||||
import {StateProperty, Trait} from '@avocado/traits';
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
import {Rectangle, Vector} from '@avocado/math';
|
import {Rectangle, Vector} from '@avocado/math';
|
||||||
import {compose} from '@latus/core';
|
import {compose} from '@latus/core';
|
||||||
|
import mapValues from 'lodash.mapvalues';
|
||||||
|
|
||||||
import Animation from '../animation';
|
import Animation from '../animation';
|
||||||
import AnimationView from '../animation-view';
|
import AnimationView from '../animation-view';
|
||||||
|
@ -16,6 +18,34 @@ const decorate = compose(
|
||||||
|
|
||||||
export default class Animated extends decorate(Trait) {
|
export default class Animated extends decorate(Trait) {
|
||||||
|
|
||||||
|
#animations = {};
|
||||||
|
|
||||||
|
#animationViews = {};
|
||||||
|
|
||||||
|
#cachedAabbs = {};
|
||||||
|
|
||||||
|
#currentAnimation;
|
||||||
|
|
||||||
|
constructor(json) {
|
||||||
|
super(json);
|
||||||
|
if (json.animations) {
|
||||||
|
Object.values(json.animations).forEach((animation) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
animation.direction = this.entity.direction;
|
||||||
|
});
|
||||||
|
this.#animations = json.animations;
|
||||||
|
this.#animationViews = json.animationViews;
|
||||||
|
}
|
||||||
|
this.#currentAnimation = this.state.currentAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptPacket(packet) {
|
||||||
|
if ('TraitUpdateAnimated' === packet.constructor.type) {
|
||||||
|
this.entity.currentAnimation = packet.data.currentAnimation;
|
||||||
|
this.entity.isAnimating = packet.data.isAnimating;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static defaultParams() {
|
static defaultParams() {
|
||||||
return {
|
return {
|
||||||
animations: {},
|
animations: {},
|
||||||
|
@ -55,134 +85,153 @@ export default class Animated extends decorate(Trait) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(entity, params, state) {
|
|
||||||
super(entity, params, state);
|
|
||||||
this._animations = this.params.animations;
|
|
||||||
this.animations = {};
|
|
||||||
this.animationViews = undefined;
|
|
||||||
this.animationsPromise = undefined;
|
|
||||||
this._cachedAabbs = {};
|
|
||||||
this._currentAnimation = this.state.currentAnimation;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.animationViews) {
|
if (this.#animationViews) {
|
||||||
const animationViews = Object.entries(this.animationViews);
|
const animationViews = Object.entries(this.#animationViews);
|
||||||
for (let i = 0; i < animationViews.length; i++) {
|
for (let i = 0; i < animationViews.length; i++) {
|
||||||
const [key, animationView] = animationViews[i];
|
const [key, animationView] = animationViews[i];
|
||||||
this.hideAnimation(key);
|
this.hideAnimation(key);
|
||||||
animationView.destroy();
|
animationView.destroy();
|
||||||
}
|
}
|
||||||
this.animationViews = undefined;
|
this.#animationViews = undefined;
|
||||||
}
|
}
|
||||||
const animations = Object.values(this.animations);
|
const animations = Object.values(this.#animations);
|
||||||
for (let i = 0; i < animations.length; i++) {
|
for (let i = 0; i < animations.length; i++) {
|
||||||
animations[i].destroy();
|
animations[i].destroy();
|
||||||
}
|
}
|
||||||
this.animations = {};
|
this.#animations = {};
|
||||||
this.animationsPromise = undefined;
|
this.#cachedAabbs = {};
|
||||||
this._cachedAabbs = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptPacket(packet) {
|
static async extendJson(json) {
|
||||||
if ('TraitUpdateAnimated' === packet.constructor.type) {
|
const extended = await super.extendJson(json);
|
||||||
this.entity.currentAnimation = packet.data.currentAnimation;
|
if (Object.keys(this.params.animations).length > 0) {
|
||||||
this.entity.isAnimating = packet.data.isAnimating;
|
extended.animations = await mapValuesAsync(
|
||||||
|
this.params.animations,
|
||||||
|
(json) => Animation.load(json),
|
||||||
|
);
|
||||||
|
extended.animationViews = mapValues(
|
||||||
|
extended.animations,
|
||||||
|
(animation) => new AnimationView(animation),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
hideAnimation(key) {
|
hideAnimation(key) {
|
||||||
if (!this.animationViews) {
|
if (!this.#animationViews) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const animationView = this.animationViews[key];
|
const animationView = this.#animationViews[key];
|
||||||
if (!animationView) {
|
if (!animationView) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.entity.container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.entity.container.removeChild(animationView);
|
this.entity.container.removeChild(animationView);
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate() {
|
hooks() {
|
||||||
return Promise.all([
|
return {
|
||||||
this.loadAnimations(),
|
|
||||||
this.loadAnimationImagesIfPossible(),
|
visibleAabbs: () => {
|
||||||
]);
|
const key = this.#currentAnimation;
|
||||||
|
if (key in this.#cachedAabbs) {
|
||||||
|
return this.#cachedAabbs[key];
|
||||||
|
}
|
||||||
|
if (!(key in this.#animations)) {
|
||||||
|
return [0, 0, 0, 0];
|
||||||
|
}
|
||||||
|
const {frameSize} = this.#animations[key];
|
||||||
|
const scaledSize = Vector.mul(frameSize, this.entity.rawVisibleScale);
|
||||||
|
const viewPosition = Vector.sub(
|
||||||
|
this.offsetFor(key),
|
||||||
|
Vector.scale(scaledSize, 0.5),
|
||||||
|
);
|
||||||
|
const rectangle = Rectangle.compose(viewPosition, frameSize);
|
||||||
|
const expanded = Rectangle.expand(
|
||||||
|
rectangle,
|
||||||
|
Vector.sub(scaledSize, frameSize),
|
||||||
|
);
|
||||||
|
this.#cachedAabbs[key] = expanded;
|
||||||
|
return expanded;
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
jitterFor(key) {
|
jitterFor(key) {
|
||||||
if (!(key in this._animations) || !this._animations[key].jitter) {
|
if (!(key in this.params.animations) || !this.params.animations[key].jitter) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return this._animations[key].jitter;
|
return this.params.animations[key].jitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAnimations() {
|
listeners() {
|
||||||
if (this.animationsPromise) {
|
return {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.animationsPromise) {
|
|
||||||
const animationPromises = [];
|
|
||||||
// Load all animations.
|
|
||||||
const animations = Object.entries(this._animations);
|
|
||||||
for (let i = 0; i < animations.length; i++) {
|
|
||||||
const [key, {uri}] = animations[i];
|
|
||||||
const promise = Animation.load(uri).then((animation) => ({animation, key}));
|
|
||||||
animationPromises.push(promise);
|
|
||||||
}
|
|
||||||
this.animationsPromise = Promise.all(animationPromises);
|
|
||||||
}
|
|
||||||
// Store keyed animations.
|
|
||||||
this.animations = {};
|
|
||||||
const animations = await this.animationsPromise;
|
|
||||||
animations.forEach(({animation, key}) => {
|
|
||||||
this.animations[key] = animation;
|
|
||||||
// Set direction upfront.
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
animation.direction = this.entity.direction;
|
|
||||||
});
|
|
||||||
// Bounding box update.
|
|
||||||
this.entity.updateVisibleBoundingBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadAnimationImagesIfPossible() {
|
currentAnimationChanged: (oldKey, currentAnimation) => {
|
||||||
if (!this.entity.container) {
|
this.#currentAnimation = currentAnimation;
|
||||||
return;
|
// Reset old animation.
|
||||||
}
|
if (oldKey in this.#animations) {
|
||||||
if (!this.animationsPromise) {
|
const oldAnimation = this.#animations[oldKey];
|
||||||
return;
|
oldAnimation.reset();
|
||||||
}
|
}
|
||||||
if (this.animationViews) {
|
// Bounding box update.
|
||||||
return;
|
this.entity.updateVisibleBoundingBox();
|
||||||
}
|
// Only client/graphics.
|
||||||
// Store keyed animation views.
|
if (!this.#animationViews) {
|
||||||
this.animationViews = {};
|
return;
|
||||||
const animations = await this.animationsPromise;
|
}
|
||||||
animations.forEach(({animation, key}) => {
|
// Swap the animation.
|
||||||
this.animationViews[key] = new AnimationView(animation);
|
this.hideAnimation(oldKey);
|
||||||
// Calculate any offset.
|
this.showAnimation(this.#currentAnimation);
|
||||||
const animationView = this.animationViews[key];
|
},
|
||||||
animationView.position = this.offsetFor(key);
|
|
||||||
// Ensure animation is made visible upfront.
|
directionChanged: () => {
|
||||||
const isCurrentAnimation = key === this._currentAnimation;
|
// All animations track direction.
|
||||||
if (isCurrentAnimation) {
|
const animations = Object.values(this.#animations);
|
||||||
this.showAnimation(key);
|
for (let i = 0; i < animations.length; i++) {
|
||||||
}
|
animations[i].direction = this.entity.direction;
|
||||||
});
|
}
|
||||||
this.setSpriteScale();
|
},
|
||||||
|
|
||||||
|
isDyingChanged: (_, isDying) => {
|
||||||
|
this.isAnimating = !isDying;
|
||||||
|
},
|
||||||
|
|
||||||
|
traitAdded: () => {
|
||||||
|
this.entity.updateVisibleBoundingBox();
|
||||||
|
this.setSpriteScale();
|
||||||
|
Object.keys(this.#animations).forEach((key) => {
|
||||||
|
const method = key === this.#currentAnimation
|
||||||
|
? 'showAnimation'
|
||||||
|
: 'hideAnimation';
|
||||||
|
this[method](key);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
visibleScaleChanged: () => {
|
||||||
|
this.#cachedAabbs = {};
|
||||||
|
this.setSpriteScale();
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetFor(key) {
|
offsetFor(key) {
|
||||||
if (!(key in this._animations) || !this._animations[key].offset) {
|
if (!(key in this.params.animations) || !this.params.animations[key].offset) {
|
||||||
return [0, 0];
|
return [0, 0];
|
||||||
}
|
}
|
||||||
return this._animations[key].offset;
|
return this.params.animations[key].offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
packets() {
|
packets() {
|
||||||
const {currentAnimation, isAnimating} = this.stateDifferences();
|
const {currentAnimation, isAnimating} = this.stateDifferences();
|
||||||
if (currentAnimation || isAnimating) {
|
if (currentAnimation || isAnimating) {
|
||||||
return [
|
return [
|
||||||
'TraitUpdateAnimatedPacket',
|
'TraitUpdateAnimated',
|
||||||
{
|
{
|
||||||
currentAnimation: this.state.currentAnimation,
|
currentAnimation: this.state.currentAnimation,
|
||||||
isAnimating: this.state.isAnimating,
|
isAnimating: this.state.isAnimating,
|
||||||
|
@ -193,107 +242,39 @@ export default class Animated extends decorate(Trait) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setSpriteScale() {
|
setSpriteScale() {
|
||||||
if (!this.animationViews) {
|
if (!this.#animationViews) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const animationViews = Object.values(this.animationViews);
|
const animationViews = Object.values(this.#animationViews);
|
||||||
for (let i = 0; i < animationViews.length; i++) {
|
for (let i = 0; i < animationViews.length; i++) {
|
||||||
animationViews[i].scale = this.entity.rawVisibleScale;
|
animationViews[i].scale = this.entity.rawVisibleScale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showAnimation(key) {
|
showAnimation(key) {
|
||||||
if (!this.animationViews) {
|
if (!this.#animationViews) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(key in this.animationViews)) {
|
const animationView = this.#animationViews[key];
|
||||||
|
if (!animationView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.entity.container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const animationView = this.animationViews[key];
|
|
||||||
this.entity.container.addChild(animationView);
|
this.entity.container.addChild(animationView);
|
||||||
}
|
}
|
||||||
|
|
||||||
hooks() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
visibleAabbs: () => {
|
|
||||||
const key = this._currentAnimation;
|
|
||||||
if (key in this._cachedAabbs) {
|
|
||||||
return this._cachedAabbs[key];
|
|
||||||
}
|
|
||||||
if (!(key in this.animations)) {
|
|
||||||
return [0, 0, 0, 0];
|
|
||||||
}
|
|
||||||
const animation = this.animations[key];
|
|
||||||
const size = animation.frameSize;
|
|
||||||
const scaledSize = Vector.mul(size, this.entity.rawVisibleScale);
|
|
||||||
const viewPosition = Vector.sub(
|
|
||||||
this.offsetFor(key),
|
|
||||||
Vector.scale(scaledSize, 0.5),
|
|
||||||
);
|
|
||||||
const rectangle = Rectangle.compose(viewPosition, size);
|
|
||||||
const expanded = Rectangle.expand(
|
|
||||||
rectangle,
|
|
||||||
Vector.sub(scaledSize, size),
|
|
||||||
);
|
|
||||||
this._cachedAabbs[key] = expanded;
|
|
||||||
return expanded;
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
listeners() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
currentAnimationChanged: (oldKey, currentAnimation) => {
|
|
||||||
this._currentAnimation = currentAnimation;
|
|
||||||
// Reset old animation.
|
|
||||||
if (oldKey in this.animations) {
|
|
||||||
const oldAnimation = this.animations[oldKey];
|
|
||||||
oldAnimation.reset();
|
|
||||||
}
|
|
||||||
// Bounding box update.
|
|
||||||
this.entity.updateVisibleBoundingBox();
|
|
||||||
// Only client/graphics.
|
|
||||||
if (!this.animationViews) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Swap the animation.
|
|
||||||
this.hideAnimation(oldKey);
|
|
||||||
this.showAnimation(this._currentAnimation);
|
|
||||||
},
|
|
||||||
|
|
||||||
directionChanged: () => {
|
|
||||||
// All animations track direction.
|
|
||||||
const animations = Object.values(this.animations);
|
|
||||||
for (let i = 0; i < animations.length; i++) {
|
|
||||||
animations[i].direction = this.entity.direction;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isDyingChanged: (_, isDying) => {
|
|
||||||
this.isAnimating = !isDying;
|
|
||||||
},
|
|
||||||
|
|
||||||
visibleScaleChanged: () => {
|
|
||||||
this._cachedAabbs = {};
|
|
||||||
this.setSpriteScale();
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
tick(elapsed) {
|
tick(elapsed) {
|
||||||
if (!this.isAnimating) {
|
if (!this.isAnimating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only tick current animation.
|
// Only tick current animation.
|
||||||
const currentAnimation = this._currentAnimation;
|
const currentAnimation = this.#currentAnimation;
|
||||||
if (!(currentAnimation in this.animations)) {
|
if (!(currentAnimation in this.#animations)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const animation = this.animations[currentAnimation];
|
const animation = this.#animations[currentAnimation];
|
||||||
const jitterAmount = this.jitterFor(currentAnimation);
|
const jitterAmount = this.jitterFor(currentAnimation);
|
||||||
if (jitterAmount > 0) {
|
if (jitterAmount > 0) {
|
||||||
const jitter = Math.random() * jitterAmount;
|
const jitter = Math.random() * jitterAmount;
|
|
@ -31,7 +31,7 @@ import TransitionResult from './result';
|
||||||
|
|
||||||
export {TransitionResult};
|
export {TransitionResult};
|
||||||
|
|
||||||
export function TransitionMixin(Superclass) {
|
export default function TransitionMixin(Superclass) {
|
||||||
return class Transition extends Superclass {
|
return class Transition extends Superclass {
|
||||||
|
|
||||||
transition(props, duration, easing) {
|
transition(props, duration, easing) {
|
||||||
|
|
|
@ -9,20 +9,18 @@ const decorate = compose(
|
||||||
export default class TransitionResult extends decorate(Class) {
|
export default class TransitionResult extends decorate(Class) {
|
||||||
|
|
||||||
constructor(subject, props, duration, easing) {
|
constructor(subject, props, duration, easing) {
|
||||||
super(subject, props, duration, easing);
|
super();
|
||||||
// Speed might not get passed. If it doesn't, default to 100
|
// Speed might not get passed. If it doesn't, default to 100 milliseconds.
|
||||||
// milliseconds.
|
|
||||||
this.duration = parseFloat(duration || 0.1);
|
this.duration = parseFloat(duration || 0.1);
|
||||||
this.elapsed = 0;
|
this.elapsed = 0;
|
||||||
this._isEmittingProgress = false;
|
this.isEmittingProgress = false;
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.subject = subject;
|
this.subject = subject;
|
||||||
if ('function' === typeof easing) {
|
if ('function' === typeof easing) {
|
||||||
this.easing = easing;
|
this.easing = easing;
|
||||||
}
|
}
|
||||||
// If easing isn't passed in as a function, attempt to look it up
|
// If easing isn't passed in as a function, attempt to look it up as a string key into
|
||||||
// as a string key into Transition.easing. If that fails, then
|
// Transition.easing. If that fails, then default to 'easeOutQuad'.
|
||||||
// default to 'easeOutQuad'.
|
|
||||||
else {
|
else {
|
||||||
this.easing = easingFunctions[easing] || easingFunctions.easeOutQuad;
|
this.easing = easingFunctions[easing] || easingFunctions.easeOutQuad;
|
||||||
}
|
}
|
||||||
|
@ -36,44 +34,33 @@ export default class TransitionResult extends decorate(Class) {
|
||||||
this.change[key] = prop - value;
|
this.change[key] = prop - value;
|
||||||
}
|
}
|
||||||
this.promise = new Promise((resolve) => {
|
this.promise = new Promise((resolve) => {
|
||||||
this.once('stopped', () => resolve());
|
this.once('stopped', resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEmittingProgress() {
|
// Immediately finish the transition. This will leave the object in the fully transitioned state.
|
||||||
return this._isEmittingProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
set isEmittingProgress(isEmittingProgress) {
|
|
||||||
this._isEmittingProgress = isEmittingProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Immediately finish the transition. This will leave the object
|
|
||||||
// in the fully transitioned state.
|
|
||||||
skipTransition() {
|
skipTransition() {
|
||||||
// Just trick it into thinking the time passed and do one last
|
// Just trick it into thinking the time passed and do one last tick.
|
||||||
// tick.
|
|
||||||
this.elapsed = this.duration;
|
this.elapsed = this.duration;
|
||||||
this.tick(0);
|
this.tick(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Immediately stop the transition. This will leave the object in
|
// Immediately stop the transition. This will leave the object in its current state;
|
||||||
// its current state; potentially partially transitioned.
|
// potentially partially transitioned.
|
||||||
stopTransition() {
|
stopTransition() {
|
||||||
// Let any listeners know that the transition is complete.
|
// Let any listeners know that the transition is complete.
|
||||||
if (this._isEmittingProgress) {
|
if (this.isEmittingProgress) {
|
||||||
this.emit('progress', [this.elapsed, this.duration]);
|
this.emit('progress', [this.elapsed, this.duration]);
|
||||||
}
|
}
|
||||||
this.emit('stopped');
|
this.emit('stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick callback. Called repeatedly while this transition is
|
// Tick callback. Called repeatedly while this transition is running.
|
||||||
// running.
|
|
||||||
tick(elapsed) {
|
tick(elapsed) {
|
||||||
// Update the transition's elapsed time.
|
// Update the transition's elapsed time.
|
||||||
this.elapsed += elapsed;
|
this.elapsed += elapsed;
|
||||||
// If we've overshot the duration, we'll fix it up here, so
|
// If we've overshot the duration, we'll fix it up here, so things never transition too far
|
||||||
// things never transition too far (through the end point).
|
// (through the end point).
|
||||||
if (this.elapsed >= this.duration) {
|
if (this.elapsed >= this.duration) {
|
||||||
this.elapsed = this.duration;
|
this.elapsed = this.duration;
|
||||||
const changes = Object.entries(this.change);
|
const changes = Object.entries(this.change);
|
||||||
|
@ -103,7 +90,7 @@ export default class TransitionResult extends decorate(Class) {
|
||||||
if (this.elapsed === this.duration) {
|
if (this.elapsed === this.duration) {
|
||||||
this.stopTransition();
|
this.stopTransition();
|
||||||
}
|
}
|
||||||
if (this._isEmittingProgress) {
|
if (this.isEmittingProgress) {
|
||||||
this.emit('progress', [this.elapsed, this.duration]);
|
this.emit('progress', [this.elapsed, this.duration]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
"@avocado/core@2.0.0", "@avocado/core@^2.0.0":
|
"@avocado/core@2.0.0", "@avocado/core@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@avocado%2fcore/-/core-2.0.0.tgz#636ea3c3b54a38538c59485080f6a0e48f1798e7"
|
resolved "https://npm.i12e.cha0s.io/@avocado%2fcore/-/core-2.0.0.tgz#ee56331fb389deb196f0184d798d72b5d0fe6015"
|
||||||
integrity sha512-VW+ygRHaQQwaL5rKZGm0n0DNfvj+H89qQx+67veCUmUuRav3XAeE0iYs8Lgfc3CJLPz/alqt/dVPMXd5QDR+Mg==
|
integrity sha512-AynteHSxM7TTzzGGRWrn3qvHi1Cru2Yhg1z4ZgJgh0FE2yxXRefIVe2PPRCTeYoRXvIpE7c+QtJjrJIvuApViQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "4.3.1"
|
debug "4.3.1"
|
||||||
|
|
||||||
|
@ -4796,6 +4796,11 @@ lodash.flatten@^4.4.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
resolved "https://npm.i12e.cha0s.io/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||||
|
|
||||||
|
lodash.mapvalues@^4.6.0:
|
||||||
|
version "4.6.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
|
||||||
|
integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=
|
||||||
|
|
||||||
lodash.omit@^4.5.0:
|
lodash.omit@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
|
resolved "https://npm.i12e.cha0s.io/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
|
||||||
|
|
|
@ -89,9 +89,6 @@ export default class Trait extends decorate(JsonResource) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-empty-function
|
|
||||||
async hydrate() {}
|
|
||||||
|
|
||||||
get isDirty() {
|
get isDirty() {
|
||||||
const keys = Object.keys(this.state);
|
const keys = Object.keys(this.state);
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user