UNPKG

ecspresso

Version:

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

226 lines (225 loc) 8.45 kB
/** * Particle System Plugin for ECSpresso * * High-performance particle system where particles live outside the ECS in * pre-allocated pools. Renders via PixiJS v8's ParticleContainer + Particle API. * Renderer2D is a required dependency. * * Follows the established plugin pattern: immutable shared config * (ParticleEffectConfig) + mutable per-entity state (ParticleEmitter) component, * side-storage Map for PixiJS objects, kit pattern for typed helpers. */ import { type BasePluginOptions } from 'ecspresso'; import type { BaseWorld } from 'ecspresso'; import type { ComponentsConfig } from '../../type-utils'; import type { TransformComponentTypes } from 'ecspresso/plugins/spatial/transform'; /** BaseWorld narrowed to particle components for typed access in helpers. */ type ParticleWorld = BaseWorld<ParticleComponentTypes>; /** Fixed value or random range [min, max] */ export type ParticleValue = number | readonly [number, number]; /** Emission geometry */ export type EmissionShape = 'point' | 'circle'; /** Blend modes for particle rendering */ export type ParticleBlendMode = 'normal' | 'add' | 'multiply' | 'screen'; /** * User-facing config input for defining a particle effect. * All properties optional except maxParticles and texture. */ export interface ParticleEffectInput { /** Pool size — maximum simultaneous particles */ maxParticles: number; /** PixiJS Texture for particles */ texture: unknown; /** Particles per second (0 = burst-only, default: 10) */ spawnRate?: number; /** Particles per burst (default: 0) */ burstCount?: number; /** Emitter lifetime in seconds (-1 = infinite, default: -1) */ duration?: number; /** Per-particle lifetime in seconds (default: 1) */ lifetime?: ParticleValue; /** Initial speed in pixels/second (default: 100) */ speed?: ParticleValue; /** Emission direction in radians (default: [0, 2*PI]) */ angle?: ParticleValue; /** Spawn geometry (default: 'point') */ emissionShape?: EmissionShape; /** Radius for 'circle' shape (default: 0) */ emissionRadius?: number; /** Acceleration in pixels/second^2 (default: {x: 0, y: 0}) */ gravity?: { readonly x: number; readonly y: number; }; /** Initial scale (default: 1) */ startSize?: ParticleValue; /** Final scale (default: same as startSize) */ endSize?: ParticleValue; /** Initial opacity (default: 1) */ startAlpha?: ParticleValue; /** Final opacity (default: 0) */ endAlpha?: ParticleValue; /** Initial hex color (default: 0xffffff) */ startTint?: number; /** Final hex color (default: same as startTint) */ endTint?: number; /** Initial rotation in radians (default: 0) */ startRotation?: ParticleValue; /** Rotation velocity in rad/s (default: 0) */ rotationSpeed?: ParticleValue; /** Blend mode (default: 'normal') */ blendMode?: ParticleBlendMode; /** Particles in world coordinates (default: true) */ worldSpace?: boolean; } /** * Frozen, fully-resolved particle effect config. * Output of defineParticleEffect. */ export interface ParticleEffectConfig { readonly maxParticles: number; readonly texture: unknown; readonly spawnRate: number; readonly burstCount: number; readonly duration: number; readonly lifetime: ParticleValue; readonly speed: ParticleValue; readonly angle: ParticleValue; readonly emissionShape: EmissionShape; readonly emissionRadius: number; readonly gravity: { readonly x: number; readonly y: number; }; readonly startSize: ParticleValue; readonly endSize: ParticleValue; readonly startAlpha: ParticleValue; readonly endAlpha: ParticleValue; readonly startTint: number; readonly endTint: number; readonly startRotation: ParticleValue; readonly rotationSpeed: ParticleValue; readonly blendMode: ParticleBlendMode; readonly worldSpace: boolean; } /** * Mutable per-particle state. Pre-allocated, never GC'd. */ export interface ParticleState { active: boolean; x: number; y: number; vx: number; vy: number; life: number; maxLife: number; size: number; startSize: number; endSize: number; alpha: number; startAlpha: number; endAlpha: number; tint: number; rotation: number; rotationSpeed: number; } /** * Per-entity emitter state stored as an ECS component. */ export interface ParticleEmitter { readonly config: ParticleEffectConfig; activeCount: number; spawnAccumulator: number; elapsed: number; playing: boolean; pendingBurst: number; finished: boolean; onComplete?: (data: ParticleEmitterEventData) => void; } /** * Component types provided by the particle plugin. */ export interface ParticleComponentTypes { particleEmitter: ParticleEmitter; } /** * Data published when an emitter completes. */ export interface ParticleEmitterEventData { entityId: number; } export interface ParticlePluginOptions<G extends string = 'particles'> extends BasePluginOptions<G> { } /** * Sample a ParticleValue: returns fixed value or random within [min, max]. */ export declare function sampleRange(value: ParticleValue): number; /** * Linear interpolation between two hex colors (RGB channels). */ export declare function lerpTint(start: number, end: number, t: number): number; /** * Define a particle effect config with defaults applied and frozen. */ export declare function defineParticleEffect(input: ParticleEffectInput): ParticleEffectConfig; /** * Create a particleEmitter component suitable for spreading into spawn(). */ export declare function createParticleEmitter(config: ParticleEffectConfig, options?: { playing?: boolean; onComplete?: (data: ParticleEmitterEventData) => void; }): Pick<ParticleComponentTypes, 'particleEmitter'>; /** * Queue a burst of particles on an emitter. * Returns false if entity has no particleEmitter component. */ export declare function burstParticles(ecs: ParticleWorld, entityId: number, count?: number): boolean; /** * Stop an emitter from spawning new particles. * Existing particles continue their lifecycle. */ export declare function stopEmitter(ecs: ParticleWorld, entityId: number): boolean; /** * Resume a stopped emitter. */ export declare function resumeEmitter(ecs: ParticleWorld, entityId: number): boolean; /** * Runtime data stored outside the ECS, keyed by entity ID. */ export interface EmitterRuntimeData { particles: ParticleState[]; pixiContainer: unknown; pixiParticles: unknown[]; } export declare const particlePresets: { readonly explosion: (texture: unknown, overrides?: Partial<ParticleEffectInput>) => ParticleEffectConfig; readonly smoke: (texture: unknown, overrides?: Partial<ParticleEffectInput>) => ParticleEffectConfig; readonly fire: (texture: unknown, overrides?: Partial<ParticleEffectInput>) => ParticleEffectConfig; readonly sparkle: (texture: unknown, overrides?: Partial<ParticleEffectInput>) => ParticleEffectConfig; readonly trail: (texture: unknown, overrides?: Partial<ParticleEffectInput>) => ParticleEffectConfig; }; type ParticleLabels = 'particle-update' | 'particle-render-sync'; type ParticleRequires = ComponentsConfig<TransformComponentTypes & { renderLayer: string; }>; /** * Create a particle system plugin for ECSpresso. * * Provides: * - Pre-allocated particle pools outside the entity system * - Continuous and burst emission modes * - Velocity, gravity, lifetime, interpolation (size, alpha, tint, rotation) * - World-space and local-space particle emission * - PixiJS ParticleContainer rendering (via renderer2D dependency) * - Presets for common effects (explosion, smoke, fire, sparkle, trail) * * Renderer2D is a required dependency. */ export declare function createParticlePlugin<G extends string = 'particles'>(options?: ParticlePluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, ParticleComponentTypes>, ParticleRequires, ParticleLabels, G, never, "particle-emitters">; /** * Get the runtime data for an emitter entity. * Useful for tests and advanced usage. * @internal Exported for testing only. */ export declare function getEmitterData(emitterDataMap: Map<number, EmitterRuntimeData>, entityId: number): EmitterRuntimeData | undefined; export {};