import { isHandleEnabled } from '../../core/graph'; import { animationFrameAt } from '../../core/model/animation'; import { textOf } from '../../core/model/Properties'; import { findAssetOf } from '../../core/model/Project'; import type { Entity } from '../Entity'; import type { World } from '../World'; import type { System } from './System'; export interface AnimationFinish { entity: Entity; animationId: string; } /** * Advances every sprite wearing an animation. A different animation starts over; the same one set * again runs on, so a graph may set it every frame. Runs before the BehaviorSystem, which reports * this frame's `finished`. */ export class AnimationSystem implements System { finished: AnimationFinish[] = []; update(world: World, dt: number): void { this.finished = []; for (const entity of world.entities) { if (entity.type !== 'sprite') continue; const id = textOf(entity.props.texture ?? ''); const asset = findAssetOf(world.project, id, 'animation'); if (!asset) { delete entity.animation; continue; } const clock = entity.animation; if (!clock || clock.id !== id) { entity.animation = { id, time: 0, finished: false }; continue; } if (!isHandleEnabled(entity)) continue; clock.time += dt; if (!clock.finished && animationFrameAt(asset, clock.time).finished) { clock.finished = true; this.finished.push({ entity, animationId: id }); } } } stop(): void { this.finished = []; } } /** Seconds into the animation the entity wears – 0 for one it was given since the last tick. */ export function animationTime(entity: Entity): number { const clock = entity.animation; return clock && clock.id === textOf(entity.props.texture ?? '') ? clock.time : 0; }