UNPKG

ecspresso

Version:

A minimal Entity-Component-System library for typescript and javascript.

162 lines (161 loc) 6.42 kB
/** * Tween Plugin for ECSpresso * * Declarative property animation within the ECS. Tween any numeric component * field over time with standard easing functions, sequences, and completion events. */ import { type BasePluginOptions } from 'ecspresso'; import type { ComponentsOfWorld, AnyECSpresso } from 'ecspresso'; import { type EasingFn } from '../../utils/easing'; /** * Data structure published when a tween completes. * Use this type when defining tween completion events in your EventTypes interface. */ export interface TweenEventData { /** The entity ID the tween belongs to */ entityId: number; /** Number of steps in the tween */ stepCount: number; } export interface TweenTarget { /** Component name on the entity */ component: string; /** Pre-split field path (e.g., ['position', 'x']) */ path: readonly string[]; /** Starting value. null = resolve from current value on first tick */ from: number | null; /** Target value */ to: number; } export interface TweenStep { targets: TweenTarget[]; duration: number; easing: EasingFn; } export interface Tween { steps: TweenStep[]; currentStep: number; elapsed: number; loop: LoopMode; totalLoops: number; completedLoops: number; direction: 1 | -1; state: 'pending' | 'active' | 'complete'; onComplete?: (data: TweenEventData) => void; justFinished: boolean; } export type LoopMode = 'once' | 'loop' | 'yoyo'; /** * Component types provided by the tween plugin. */ export interface TweenComponentTypes { tween: Tween; } export interface TweenPluginOptions<G extends string = 'tweens'> extends BasePluginOptions<G> { } export interface TweenOptions { /** Explicit starting value (default: captures current value on first tick) */ from?: number; /** Easing function (default: linear) */ easing?: EasingFn; /** Loop mode (default: 'once') */ loop?: LoopMode; /** Number of loops. -1 = infinite (default: 1) */ loops?: number; /** Callback invoked when tween completes */ onComplete?: (data: TweenEventData) => void; } /** * Create a single-target tween component. * * @param component Component name on the entity * @param field Field path (dot-separated for nested, e.g. 'position.x') * @param to Target value * @param duration Duration in seconds * @param options Optional configuration * @returns Component object suitable for spreading into spawn() */ export declare function createTween(component: string, field: string, to: number, duration: number, options?: TweenOptions): Pick<TweenComponentTypes, 'tween'>; export interface TweenSequenceStepInput { targets: ReadonlyArray<{ component: string; field: string; to: number; from?: number; }>; duration: number; easing?: EasingFn; } export interface TweenSequenceOptions { /** Loop mode (default: 'once') */ loop?: LoopMode; /** Number of loops. -1 = infinite (default: 1) */ loops?: number; /** Callback invoked when tween completes */ onComplete?: (data: TweenEventData) => void; } /** * Create a multi-step tween sequence. Each step can have parallel targets. * * @param steps Array of step definitions * @param options Optional configuration * @returns Component object suitable for spreading into spawn() */ export declare function createTweenSequence(steps: ReadonlyArray<TweenSequenceStepInput>, options?: TweenSequenceOptions): Pick<TweenComponentTypes, 'tween'>; /** * Recursively produce a union of dot-separated paths that resolve to `number` * within type T. Depth-limited to 4 levels to prevent TS recursion errors. * * @example * NumericPaths<{ x: number; y: number }> // 'x' | 'y' * NumericPaths<{ position: { x: number }; rotation: number }> // 'position.x' | 'rotation' */ export type NumericPaths<T, Depth extends readonly unknown[] = []> = Depth['length'] extends 4 ? never : T extends readonly unknown[] ? never : T extends Record<string, unknown> ? { [K in keyof T & string]: NonNullable<T[K]> extends number ? K : NonNullable<T[K]> extends readonly unknown[] ? never : NonNullable<T[K]> extends Record<string, unknown> ? `${K}.${NumericPaths<NonNullable<T[K]>, [...Depth, unknown]>}` : never; }[keyof T & string] : never; /** * Discriminated union over component names: each variant constrains `field` * to the numeric paths of that component. TS narrows inline object literals * by `component` discriminant — zero runtime overhead. */ export type TypedTweenTargetInput<C extends Record<string, any>> = { [K in keyof C & string]: { component: K; field: NumericPaths<C[K]>; to: number; from?: number; }; }[keyof C & string]; export interface TypedTweenSequenceStepInput<C extends Record<string, any>> { targets: ReadonlyArray<TypedTweenTargetInput<C>>; duration: number; easing?: EasingFn; } export interface TweenHelpers<W extends AnyECSpresso> { createTween: <K extends keyof ComponentsOfWorld<W> & string>(component: K, field: NumericPaths<ComponentsOfWorld<W>[K]>, to: number, duration: number, options?: { from?: number; easing?: EasingFn; loop?: LoopMode; loops?: number; onComplete?: (data: TweenEventData) => void; }) => Pick<TweenComponentTypes, 'tween'>; createTweenSequence: (steps: ReadonlyArray<TypedTweenSequenceStepInput<ComponentsOfWorld<W>>>, options?: { loop?: LoopMode; loops?: number; onComplete?: (data: TweenEventData) => void; }) => Pick<TweenComponentTypes, 'tween'>; } export declare function createTweenHelpers<W extends AnyECSpresso>(_world?: W): TweenHelpers<W>; /** * Create a tween plugin for ECSpresso. * * This plugin provides: * - Tween system that processes all tween components each frame * - Support for single-field, multi-target, and multi-step sequences * - 31 standard easing functions * - Loop modes: once, loop, yoyo * - `justFinished` flag for one-frame completion detection * - `onComplete` callback on completion * - Change detection via markChanged */ export declare function createTweenPlugin<G extends string = 'tweens'>(options?: TweenPluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, TweenComponentTypes>, import("ecspresso").EmptyConfig, "tween-update", G, never, never>;