@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
622 lines • 18.8 kB
TypeScript
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