ecspresso
Version:
A minimal Entity-Component-System library for typescript and javascript.
167 lines (166 loc) • 6.35 kB
TypeScript
/**
* 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 {};