@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
202 lines (201 loc) • 5.88 kB
JavaScript
import { Entity } from "./index7.js";
import { EntityMovementBody } from "./index31.js";
class MovementManager {
constructor(resolveEntity) {
this.resolveEntity = resolveEntity;
this.entries = /* @__PURE__ */ new Map();
this.entityWrappers = /* @__PURE__ */ new WeakMap();
}
/**
* Convenience factory that binds the manager to a physics engine.
*
* @param engine - Physics engine whose entities will be controlled
* @returns A movement manager configured with an entity resolver
*/
static forEngine(engine) {
let manager;
manager = new MovementManager((id) => {
const entity = engine.getEntityByUUID(id);
return entity ? manager.wrapEntity(entity) : void 0;
});
return manager;
}
/**
* Adds a movement strategy to an entity.
*
* @param target - Entity instance or entity UUID when a resolver is configured
* @param strategy - Strategy to execute
*/
add(target, strategy) {
const body = this.resolveTarget(target);
const key = body.id;
if (!this.entries.has(key)) {
this.entries.set(key, { body, strategies: [] });
}
this.entries.get(key).strategies.push(strategy);
}
/**
* Removes a specific strategy from an entity.
*
* @param target - Entity instance or identifier
* @param strategy - Strategy instance to remove
* @returns True when the strategy has been removed
*/
remove(target, strategy) {
const body = this.resolveTarget(target);
const entry = this.entries.get(body.id);
if (!entry) {
return false;
}
const index = entry.strategies.indexOf(strategy);
if (index === -1) {
return false;
}
entry.strategies.splice(index, 1);
if (entry.strategies.length === 0) {
this.entries.delete(body.id);
}
return true;
}
/**
* Removes all strategies from an entity.
*
* @param target - Entity or identifier
*/
clear(target) {
const body = this.resolveTarget(target);
this.entries.delete(body.id);
}
/**
* Stops all movement for an entity immediately
*
* This method completely stops an entity's movement by:
* - Removing all active movement strategies (dash, linear moves, etc.)
* - Stopping the entity's velocity and angular velocity
* - Clearing accumulated forces
* - Waking up the entity if it was sleeping
*
* This is useful when changing maps, teleporting, or when you need
* to halt an entity's movement completely without making it static.
*
* @param target - Entity, MovementBody, or identifier
*
* @example
* ```ts
* // Stop movement when changing maps
* if (mapChanged) {
* movement.stopMovement(playerEntity);
* }
*
* // Stop movement after teleporting
* entity.position.set(100, 200);
* movement.stopMovement(entity);
*
* // Stop movement when player dies
* if (player.isDead()) {
* movement.stopMovement(playerEntity);
* }
* ```
*/
stopMovement(target) {
const body = this.resolveTarget(target);
this.clear(target);
if ("getEntity" in body && typeof body.getEntity === "function") {
const entity = body.getEntity();
if (entity && typeof entity.stopMovement === "function") {
entity.stopMovement();
}
}
}
/**
* Checks if an entity has active strategies.
*
* @param target - Entity or identifier
* @returns True when strategies are registered
*/
hasActiveStrategies(target) {
const body = this.resolveTarget(target);
return (this.entries.get(body.id)?.strategies.length ?? 0) > 0;
}
/**
* Returns a snapshot of the strategies assigned to an entity.
*
* @param target - Entity or identifier
* @returns Copy of the strategies array (empty array when none)
*/
getStrategies(target) {
const body = this.resolveTarget(target);
const entry = this.entries.get(body.id);
return entry ? [...entry.strategies] : [];
}
/**
* Updates all registered strategies.
*
* Call this method once per frame before `PhysicsEngine.step()` so that the
* physics simulation integrates the velocities that strategies configure.
*
* @param dt - Time delta in seconds
*/
update(dt) {
for (const [key, entry] of this.entries) {
const { body, strategies } = entry;
if (strategies.length === 0) {
this.entries.delete(key);
continue;
}
for (let i = strategies.length - 1; i >= 0; i -= 1) {
const current = strategies[i];
if (!current) {
continue;
}
current.update(body, dt);
if (current.isFinished?.()) {
strategies.splice(i, 1);
current.onFinished?.();
}
}
if (strategies.length === 0) {
this.entries.delete(key);
}
}
}
/**
* Removes all strategies from all entities.
*/
clearAll() {
this.entries.clear();
}
resolveTarget(target) {
if (this.isMovementBody(target)) {
return target;
}
if (target instanceof Entity) {
return this.wrapEntity(target);
}
if (!this.resolveEntity) {
throw new Error("MovementManager: cannot resolve entity from identifier without a resolver.");
}
const entity = this.resolveEntity(target);
if (!entity) {
throw new Error(`MovementManager: unable to resolve entity for identifier ${target}.`);
}
return entity;
}
wrapEntity(entity) {
let wrapper = this.entityWrappers.get(entity);
if (!wrapper) {
wrapper = new EntityMovementBody(entity);
this.entityWrappers.set(entity, wrapper);
}
return wrapper;
}
isMovementBody(value) {
return Boolean(
value && typeof value === "object" && "id" in value && "setVelocity" in value && typeof value.setVelocity === "function"
);
}
}
export {
MovementManager
};
//# sourceMappingURL=index30.js.map