UNPKG

ecspresso

Version:

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

167 lines (166 loc) 6.35 kB
/** * Physics 2D Plugin for ECSpresso * * Provides ECS-native arcade physics: gravity, forces, drag, semi-implicit Euler * integration, and impulse-based collision response with friction. * * Reuses collider types from the collision plugin for shape definitions. * Has its own collision detection in fixedUpdate for physics response; * the existing collision plugin can still run in postUpdate for game logic events. */ import type { SystemPhase } from 'ecspresso'; import type { TransformComponentTypes, TransformWorldConfig } from '../spatial/transform'; import type { CollisionComponentTypes, LayerFactories } from './collision'; import type { Vector2D } from 'ecspresso'; /** * Rigid body types for physics simulation. * - 'dynamic': Fully simulated (gravity, forces, collisions) * - 'kinematic': Moves via velocity only (ignores gravity/forces, immovable in collisions) * - 'static': Immovable (ignores gravity, forces, and velocity) */ export type BodyType = 'dynamic' | 'kinematic' | 'static'; /** * Rigid body component controlling physics behavior. */ export interface RigidBody { type: BodyType; /** Mass in arbitrary units. Affects force→acceleration. Infinity = immovable. */ mass: number; /** Linear velocity damping coefficient (units/sec, 0 = none) */ drag: number; /** Bounciness 0–1 (0 = no bounce, 1 = perfectly elastic) */ restitution: number; /** Surface friction coefficient 0–1 */ friction: number; /** Per-entity gravity multiplier (0 = no gravity) */ gravityScale: number; } /** * Component types directly provided by the physics plugin. */ export interface Physics2DOwnComponentTypes { rigidBody: RigidBody; velocity: Vector2D; force: Vector2D; } /** * Full component types available when using the physics plugin * (own components + transform + collision dependencies). * Convenience alias for consumer code. */ export interface Physics2DComponentTypes<L extends string = never> extends TransformComponentTypes, CollisionComponentTypes<L>, Physics2DOwnComponentTypes { } /** * Physics configuration resource. */ export interface Physics2DConfig { gravity: Vector2D; } export interface Physics2DResourceTypes { physicsConfig: Physics2DConfig; } /** * Event emitted for each physics collision pair. * * Normal components are flattened (`normalX`/`normalY`) rather than nested * in a `Vector2D` to avoid a per-event allocation in the physics hot path. */ export interface Physics2DCollisionEvent { entityA: number; entityB: number; /** Unit normal X, pointing from A toward B */ normalX: number; /** Unit normal Y, pointing from A toward B */ normalY: number; /** Penetration depth (positive) */ depth: number; } export interface Physics2DEventTypes { physicsCollision: Physics2DCollisionEvent; } export interface Physics2DPluginOptions<G extends string = 'physics2D', CG extends string = never> { /** World gravity vector (default: {x: 0, y: 0}) */ gravity?: Vector2D; /** System group name (default: 'physics2D') */ systemGroup?: G; /** Additional group for the collision system only (default: none). * When set, the collision system belongs to both `systemGroup` and this group, * allowing independent enable/disable of collision detection. */ collisionSystemGroup?: CG; /** Priority for integration system (default: 1000) */ integrationPriority?: number; /** Priority for collision system (default: 900) */ collisionPriority?: number; /** Execution phase (default: 'fixedUpdate') */ phase?: SystemPhase; } export interface RigidBodyOptions { mass?: number; drag?: number; restitution?: number; friction?: number; gravityScale?: number; } /** * Create a rigid body + force component pair. * Static bodies automatically get mass=Infinity. */ export declare function createRigidBody(type: BodyType, options?: RigidBodyOptions): { rigidBody: RigidBody; force: Vector2D; }; /** * Create a force component with initial values. */ export declare function createForce(x: number, y: number): { force: Vector2D; }; /** * Accumulate a force onto an entity's force component. */ export declare function applyForce(ecs: { getComponent(id: number, name: 'force'): Vector2D | undefined; }, entityId: number, fx: number, fy: number): void; /** * Apply an instantaneous impulse: velocity += impulse / mass. */ export declare function applyImpulse(ecs: { getComponent(id: number, name: 'velocity'): Vector2D | undefined; getComponent(id: number, name: 'rigidBody'): RigidBody | undefined; }, entityId: number, ix: number, iy: number): void; /** * Directly set an entity's velocity. */ export declare function setVelocity(ecs: { getComponent(id: number, name: 'velocity'): Vector2D | undefined; }, entityId: number, vx: number, vy: number): void; /** * Create a 2D physics plugin for ECSpresso. * * Provides: * - Semi-implicit Euler integration (gravity, forces, drag → velocity → position) * - Impulse-based collision response with restitution and friction * - physicsCollision events with contact normal and depth * * @example * ```typescript * const ecs = ECSpresso.create() * .withPlugin(createTransformPlugin()) * .withPlugin(createPhysics2DPlugin({ gravity: { x: 0, y: 980 } })) * .withFixedTimestep(1/60) * .build(); * * ecs.spawn({ * ...createTransform(100, 200), * ...createRigidBody('dynamic', { mass: 1, restitution: 0.5 }), * velocity: { x: 0, y: 0 }, * ...createAABBCollider(32, 32), * ...createCollisionLayer('player', ['ground']), * }); * ``` */ type Physics2DProvides<L extends string = never> = Physics2DOwnComponentTypes & CollisionComponentTypes<L>; export declare function createPhysics2DPlugin<L extends string = never, G extends string = 'physics2D', CG extends string = never>(options?: Physics2DPluginOptions<G, CG> & { layers?: LayerFactories<Record<L, readonly string[]>>; }): import("ecspresso").Plugin<import("ecspresso").WithResources<import("ecspresso").WithEvents<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, Physics2DProvides<L>>, Physics2DEventTypes>, Physics2DResourceTypes>, TransformWorldConfig, "physics2D-integration" | "physics2D-collision", G | CG, never, never>; export {};