UNPKG

@rpgjs/physic

Version:

A deterministic 2D top-down physics library for RPG, sandbox and MMO games

622 lines 18.8 kB
import { Vector2 } from '../core/math/Vector2'; import { UUID, EntityState } from '../core/types'; import { CollisionInfo } from '../collision/Collider'; export type CardinalDirection = 'idle' | 'up' | 'down' | 'left' | 'right'; /** * Configuration options for creating an entity */ export interface EntityConfig { /** Initial position */ position?: Vector2 | { x: number; y: number; }; /** Initial velocity */ velocity?: Vector2 | { x: number; y: number; }; /** Initial rotation in radians */ rotation?: number; /** Initial angular velocity in radians per second */ angularVelocity?: number; /** Mass of the entity (0 or Infinity for static/immovable entities) */ mass?: number; /** Radius for circular collider */ radius?: number; /** Width for AABB collider */ width?: number; /** Height for AABB collider */ height?: number; /** Capsule collider configuration (if used) */ capsule?: { radius: number; height: number; }; /** Enable continuous collision detection (CCD) */ continuous?: boolean; /** Entity state flags */ state?: EntityState; /** Restitution (bounciness) coefficient (0-1) */ restitution?: number; /** Friction coefficient */ friction?: number; /** Linear damping (0-1, higher = more damping) */ linearDamping?: number; /** Angular damping (0-1, higher = more damping) */ angularDamping?: number; /** Maximum linear velocity */ maxLinearVelocity?: number; /** Maximum angular velocity */ maxAngularVelocity?: number; /** Custom UUID (auto-generated if not provided) */ uuid?: UUID; /** Collision mask (bitmask for collision filtering) */ collisionMask?: number; /** Collision category (bitmask) */ collisionCategory?: number; } /** * Physical entity in the physics world * * Represents a dynamic or static object that can be affected by forces, * collisions, and other physical interactions. * * ## Creating Static Obstacles * * To create immovable obstacles (walls, decorations), set `mass` to `0` or `Infinity`. * This makes the entity static - it will block other entities but cannot be pushed. * * @example * ```typescript * // Dynamic entity (player, movable object) * const player = new Entity({ * position: { x: 0, y: 0 }, * radius: 10, * mass: 1, * velocity: { x: 5, y: 0 } * }); * * // Static obstacle (wall, tree, decoration) * const wall = new Entity({ * position: { x: 100, y: 0 }, * width: 20, * height: 100, * mass: Infinity // or mass: 0 * }); * * player.applyForce(new Vector2(10, 0)); * ``` */ export declare class Entity { /** * Unique identifier (UUID) */ readonly uuid: UUID; /** * Position in world space */ position: Vector2; /** * Linear velocity */ velocity: Vector2; /** * Rotation in radians */ rotation: number; /** * Angular velocity in radians per second */ angularVelocity: number; /** * Mass (0 or Infinity means infinite mass / static) */ mass: number; /** * Inverse mass (cached for performance, 0 if mass is 0 or Infinity) */ invMass: number; /** * Radius for circular collider (if used) */ radius: number; /** * Width for AABB collider (if used) */ width: number; /** * Height for AABB collider (if used) */ height: number; /** * Capsule collider configuration (if used) */ capsule?: { radius: number; height: number; }; /** * Enable continuous collision detection (CCD) */ continuous: boolean; /** * Entity state flags */ state: EntityState; /** * Restitution (bounciness) coefficient (0-1) */ restitution: number; /** * Friction coefficient */ friction: number; /** * Linear damping (0-1) */ linearDamping: number; /** * Angular damping (0-1) */ angularDamping: number; /** * Maximum linear velocity */ maxLinearVelocity: number; /** * Maximum angular velocity */ maxAngularVelocity: number; /** * Accumulated force for this frame */ force: Vector2; /** * Accumulated torque for this frame */ torque: number; /** * Collision mask (bitmask) */ collisionMask: number; /** * Collision category (bitmask) */ collisionCategory: number; /** * Time since last movement (for sleep detection) */ timeSinceMovement: number; /** * Threshold for sleep detection (seconds of inactivity) */ sleepThreshold: number; /** * Current tile coordinates (x, y) */ currentTile: Vector2; private collisionEnterHandlers; private collisionExitHandlers; private positionSyncHandlers; private directionSyncHandlers; private movementChangeHandlers; private enterTileHandlers; private leaveTileHandlers; private canEnterTileHandlers; private wasMoving; private lastCardinalDirection; /** * Creates a new entity * * @param config - Entity configuration */ constructor(config?: EntityConfig); /** * Registers a handler fired when this entity starts colliding with another one. * * - **Purpose:** offer per-entity collision hooks without subscribing to the global event system. * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low. * * @param handler - Collision enter listener * @returns Unsubscribe closure * @example * ```typescript * const unsubscribe = entity.onCollisionEnter(({ other }) => { * console.log('Started colliding with', other.uuid); * }); * ``` */ onCollisionEnter(handler: EntityCollisionHandler): () => void; /** * Registers a handler fired when this entity stops colliding with another one. * * - **Purpose:** detect collision separation at the entity level for local gameplay reactions. * - **Design:** mirrors `onCollisionEnter` with identical lifecycle management semantics. * * @param handler - Collision exit listener * @returns Unsubscribe closure * @example * ```typescript * const unsubscribe = entity.onCollisionExit(({ other }) => { * console.log('Stopped colliding with', other.uuid); * }); * ``` */ onCollisionExit(handler: EntityCollisionHandler): () => void; /** * Registers a handler fired when the entity position changes (x, y). * * - **Purpose:** synchronize position changes for logging, rendering, network sync, etc. * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low. * * @param handler - Position change listener * @returns Unsubscribe closure * @example * ```typescript * const unsubscribe = entity.onPositionChange(({ x, y }) => { * console.log('Position changed to', x, y); * // Update rendering, sync network, etc. * }); * ``` */ onPositionChange(handler: EntityPositionSyncHandler): () => void; /** * Registers a handler fired when the entity direction changes. * * - **Purpose:** synchronize direction changes for logging, rendering, network sync, etc. * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low. * * @param handler - Direction change listener * @returns Unsubscribe closure * @example * ```typescript * const unsubscribe = entity.onDirectionChange(({ direction, cardinalDirection }) => { * console.log('Direction changed to', cardinalDirection); * // Update rendering, sync network, etc. * }); * ``` */ onDirectionChange(handler: EntityDirectionSyncHandler): () => void; /** * Manually notifies that the position has changed. * * - **Purpose:** allow external code to trigger position sync hooks when position is modified directly. * - **Design:** can be called after direct position modifications (e.g., `entity.position.set()`). * * @example * ```typescript * entity.position.set(100, 200); * entity.notifyPositionChange(); // Trigger sync hooks * ``` */ notifyPositionChange(): void; /** * Manually notifies that the direction has changed. * * - **Purpose:** allow external code to trigger direction sync hooks when direction is modified directly. * - **Design:** computes direction from velocity and cardinal direction. * * @example * ```typescript * entity.velocity.set(5, 0); * entity.notifyDirectionChange(); // Trigger sync hooks * ``` */ notifyDirectionChange(): void; /** * Gets the current cardinal direction. * * This value is updated whenever `notifyDirectionChange()` is called (e.g. by `setVelocity`). * It includes hysteresis logic to prevent rapid direction flipping during collisions. * * @returns The current cardinal direction ('up', 'down', 'left', 'right', 'idle') * * @example * ```typescript * const dir = entity.cardinalDirection; * if (dir === 'left') { * // Render left-facing sprite * } * ``` */ get cardinalDirection(): CardinalDirection; /** * Registers a handler fired when the entity movement state changes (moving/stopped). * * - **Purpose:** detect when an entity starts or stops moving for gameplay reactions, animations, or network sync. * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low. * - **Movement detection:** uses `MOVEMENT_EPSILON` threshold to determine if entity is moving. * - **Intensity:** provides the movement speed magnitude to allow fine-grained animation control (e.g., walk vs run). * * @param handler - Movement state change listener * @returns Unsubscribe closure * @example * ```typescript * const unsubscribe = entity.onMovementChange(({ isMoving, intensity }) => { * console.log('Entity is', isMoving ? 'moving' : 'stopped', 'at speed', intensity); * // Update animations based on intensity * if (isMoving && intensity > 100) { * // Fast movement - use run animation * } else if (isMoving) { * // Slow movement - use walk animation * } * }); * ``` */ onMovementChange(handler: EntityMovementChangeHandler): () => void; /** * Manually notifies that the movement state has changed. * * - **Purpose:** allow external code to trigger movement state sync hooks when velocity is modified directly. * - **Design:** checks if movement state (moving/stopped) has changed and notifies handlers with movement intensity. * - **Intensity:** calculated as the magnitude of the velocity vector (speed in pixels per second). * * @example * ```typescript * entity.velocity.set(5, 0); * entity.notifyMovementChange(); // Trigger sync hooks if state changed * ``` */ notifyMovementChange(): void; /** * Registers a handler fired when the entity enters a new tile. * * @param handler - Tile enter listener * @returns Unsubscribe closure */ onEnterTile(handler: EntityTileHandler): () => void; /** * Registers a handler fired when the entity leaves a tile. * * @param handler - Tile leave listener * @returns Unsubscribe closure */ onLeaveTile(handler: EntityTileHandler): () => void; /** * Registers a handler to check if the entity can enter a tile. * If any handler returns false, the entity cannot enter. * * @param handler - Can enter tile listener * @returns Unsubscribe closure */ canEnterTile(handler: EntityCanEnterTileHandler): () => void; /** * @internal * Notifies that the entity has entered a tile. */ notifyEnterTile(x: number, y: number): void; /** * @internal * Notifies that the entity has left a tile. */ notifyLeaveTile(x: number, y: number): void; /** * @internal * Checks if the entity can enter a tile. */ checkCanEnterTile(x: number, y: number): boolean; /** * Applies a force to the entity * * Force is accumulated and applied during integration. * * @param force - Force vector to apply * @returns This entity for chaining * * @example * ```typescript * entity.applyForce(new Vector2(10, 0)); // Push right * ``` */ applyForce(force: Vector2): Entity; /** * Applies a force at a specific point (creates torque) * * @param force - Force vector to apply * @param point - Point of application in world space * @returns This entity for chaining */ applyForceAtPoint(force: Vector2, point: Vector2): Entity; /** * Applies an impulse (instantaneous change in velocity) * * @param impulse - Impulse vector * @returns This entity for chaining * * @example * ```typescript * entity.applyImpulse(new Vector2(5, 0)); // Instant push * ``` */ applyImpulse(impulse: Vector2): Entity; /** * Applies an angular impulse (instantaneous change in angular velocity) * * @param impulse - Angular impulse value * @returns This entity for chaining */ applyAngularImpulse(impulse: number): Entity; /** * Teleports the entity to a new position * * @param position - New position * @returns This entity for chaining */ teleport(position: Vector2 | { x: number; y: number; }): Entity; /** * Sets the velocity directly * * @param velocity - New velocity * @returns This entity for chaining */ setVelocity(velocity: Vector2 | { x: number; y: number; }): Entity; /** * Freezes the entity (makes it static) * * @returns This entity for chaining */ freeze(): Entity; /** * Unfreezes the entity (makes it dynamic) * * @returns This entity for chaining */ unfreeze(): Entity; /** * Puts the entity to sleep (stops updating) * * @returns This entity for chaining */ sleep(): Entity; /** * Wakes up the entity (resumes updating) * * @returns This entity for chaining */ wakeUp(): Entity; /** * Checks if the entity is static * * An entity is considered static if: * - It has the Static state flag, OR * - It has infinite mass (mass = Infinity), OR * - It has zero inverse mass (invMass = 0) * * @returns True if static */ isStatic(): boolean; /** * Checks if the entity is dynamic * * @returns True if dynamic */ isDynamic(): boolean; /** * Checks if the entity is sleeping * * @returns True if sleeping */ isSleeping(): boolean; /** * Checks if the entity is kinematic * * @returns True if kinematic */ isKinematic(): boolean; /** * Resets accumulated forces and torques * * Called at the start of each physics step. */ clearForces(): void; /** * Stops all movement immediately * * Completely stops the entity's movement by: * - Setting velocity to zero * - Setting angular velocity to zero * - Clearing accumulated forces and torques * - Waking up the entity if it was sleeping * - Notifying movement state change * * Unlike `freeze()`, this method keeps the entity dynamic and does not * change its state. It's useful for stopping movement when changing maps, * teleporting, or when you need to halt an entity without making it static. * * @returns This entity for chaining * * @example * ```ts * // Stop movement when changing maps * if (mapChanged) { * entity.stopMovement(); * } * * // Stop movement after teleporting * entity.position.set(100, 200); * entity.stopMovement(); * * // Stop movement when player dies * if (player.isDead()) { * playerEntity.stopMovement(); * } * ``` */ stopMovement(): Entity; /** * Clamps velocities to maximum values */ clampVelocities(): void; /** * Checks if this entity can collide with another entity * * @param other - Other entity to check * @returns True if collision is possible */ canCollideWith(other: Entity): boolean; /** * @internal * * Notifies the entity that a collision has started. * * @param collision - Collision information shared by the world * @param other - The counterpart entity */ notifyCollisionEnter(collision: CollisionInfo, other: Entity): void; /** * @internal * * Notifies the entity that a collision has ended. * * @param collision - Collision information stored before separation * @param other - The counterpart entity */ notifyCollisionExit(collision: CollisionInfo, other: Entity): void; private computeCardinalDirection; /** * Creates a copy of this entity * * @returns New entity with copied properties */ clone(): Entity; } export interface EntityCollisionEvent { entity: Entity; other: Entity; collision: CollisionInfo; } export type EntityCollisionHandler = (event: EntityCollisionEvent) => void; export interface EntityPositionSyncEvent { entity: Entity; x: number; y: number; } export type EntityPositionSyncHandler = (event: EntityPositionSyncEvent) => void; export interface EntityDirectionSyncEvent { entity: Entity; direction: Vector2; cardinalDirection: CardinalDirection; } export type EntityDirectionSyncHandler = (event: EntityDirectionSyncEvent) => void; export interface EntityMovementChangeEvent { entity: Entity; isMoving: boolean; /** Movement intensity (speed magnitude) */ intensity: number; } export type EntityMovementChangeHandler = (event: EntityMovementChangeEvent) => void; export interface EntityTileEvent { entity: Entity; x: number; y: number; } export type EntityTileHandler = (event: EntityTileEvent) => void; export type EntityCanEnterTileHandler = (event: EntityTileEvent) => boolean; //# sourceMappingURL=Entity.d.ts.map