@blorkfield/overlay-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +201 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -3
- package/dist/index.d.ts +121 -3
- package/dist/index.js +201 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -899,12 +899,6 @@ function clearFontCache() {
|
|
|
899
899
|
|
|
900
900
|
// src/entity.ts
|
|
901
901
|
var import_matter_js3 = __toESM(require("matter-js"), 1);
|
|
902
|
-
var MOUSE_FORCE = 1e-3;
|
|
903
|
-
function applyMouseForce(entity, mouseX, grounded) {
|
|
904
|
-
if (!grounded) return;
|
|
905
|
-
const direction = Math.sign(mouseX - entity.position.x);
|
|
906
|
-
import_matter_js3.default.Body.applyForce(entity, entity.position, { x: MOUSE_FORCE * direction, y: 0 });
|
|
907
|
-
}
|
|
908
902
|
function wrapHorizontal(entity, bounds) {
|
|
909
903
|
if (entity.position.x < bounds.left) {
|
|
910
904
|
import_matter_js3.default.Body.setPosition(entity, { x: bounds.right, y: entity.position.y });
|
|
@@ -1425,7 +1419,6 @@ var OverlayScene = class {
|
|
|
1425
1419
|
this.objects = /* @__PURE__ */ new Map();
|
|
1426
1420
|
this.boundaries = [];
|
|
1427
1421
|
this.updateCallbacks = [];
|
|
1428
|
-
this.mouseX = 0;
|
|
1429
1422
|
this.animationFrameId = null;
|
|
1430
1423
|
this.mouse = null;
|
|
1431
1424
|
this.mouseConstraint = null;
|
|
@@ -1442,6 +1435,14 @@ var OverlayScene = class {
|
|
|
1442
1435
|
this.floorSegmentPressure = /* @__PURE__ */ new Map();
|
|
1443
1436
|
// segment index -> object IDs
|
|
1444
1437
|
this.collapsedSegments = /* @__PURE__ */ new Set();
|
|
1438
|
+
// Lifecycle event callbacks
|
|
1439
|
+
this.lifecycleCallbacks = {
|
|
1440
|
+
objectSpawned: [],
|
|
1441
|
+
objectRemoved: [],
|
|
1442
|
+
objectCollision: []
|
|
1443
|
+
};
|
|
1444
|
+
// Follow targets for follow-{key} tagged objects
|
|
1445
|
+
this.followTargets = /* @__PURE__ */ new Map();
|
|
1445
1446
|
/** Filter drag events - only allow grabbing objects with 'grabable' tag */
|
|
1446
1447
|
this.handleStartDrag = (event) => {
|
|
1447
1448
|
const body = event.body;
|
|
@@ -1491,17 +1492,48 @@ var OverlayScene = class {
|
|
|
1491
1492
|
this.handleAfterRender = () => {
|
|
1492
1493
|
this.backgroundManager.renderOverlay();
|
|
1493
1494
|
};
|
|
1495
|
+
/**
|
|
1496
|
+
* Handler for Matter.js collision events.
|
|
1497
|
+
* Emits objectCollision lifecycle events.
|
|
1498
|
+
*/
|
|
1499
|
+
this.handleCollisionStart = (event) => {
|
|
1500
|
+
for (const pair of event.pairs) {
|
|
1501
|
+
const entryA = this.findObjectByBody(pair.bodyA);
|
|
1502
|
+
const entryB = this.findObjectByBody(pair.bodyB);
|
|
1503
|
+
if (entryA && entryB) {
|
|
1504
|
+
this.emitLifecycleEvent(
|
|
1505
|
+
"objectCollision",
|
|
1506
|
+
this.toObjectState(entryA),
|
|
1507
|
+
this.toObjectState(entryB)
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1494
1512
|
// ==================== PRIVATE ====================
|
|
1495
1513
|
this.loop = () => {
|
|
1496
1514
|
this.effectManager.update();
|
|
1497
1515
|
this.checkTTLExpiration();
|
|
1498
1516
|
this.checkDespawnBelowFloor();
|
|
1499
1517
|
this.updatePressure();
|
|
1500
|
-
|
|
1518
|
+
if (!this.followTargets.has("mouse") && this.mouse) {
|
|
1519
|
+
this.followTargets.set("mouse", { x: this.mouse.position.x, y: this.mouse.position.y });
|
|
1520
|
+
}
|
|
1501
1521
|
for (const entry of this.objects.values()) {
|
|
1502
1522
|
const isDragging = this.mouseConstraint?.body === entry.body;
|
|
1503
|
-
if (!isDragging
|
|
1504
|
-
|
|
1523
|
+
if (!isDragging) {
|
|
1524
|
+
for (const tag of entry.tags) {
|
|
1525
|
+
const key = tag === "follow" ? "mouse" : tag.startsWith("follow-") ? tag.slice(7) : null;
|
|
1526
|
+
if (key) {
|
|
1527
|
+
const target = this.followTargets.get(key);
|
|
1528
|
+
if (target) {
|
|
1529
|
+
const grounded = this.isGrounded(entry.body);
|
|
1530
|
+
if (grounded) {
|
|
1531
|
+
const direction = Math.sign(target.x - entry.body.position.x);
|
|
1532
|
+
import_matter_js5.default.Body.applyForce(entry.body, entry.body.position, { x: 1e-3 * direction, y: 0 });
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1505
1537
|
}
|
|
1506
1538
|
if (this.config.wrapHorizontal && entry.tags.includes("falling")) {
|
|
1507
1539
|
wrapHorizontal(entry.body, this.config.bounds);
|
|
@@ -1575,6 +1607,7 @@ var OverlayScene = class {
|
|
|
1575
1607
|
}
|
|
1576
1608
|
import_matter_js5.default.Events.on(this.render, "beforeRender", this.handleBeforeRender);
|
|
1577
1609
|
import_matter_js5.default.Events.on(this.render, "afterRender", this.handleAfterRender);
|
|
1610
|
+
import_matter_js5.default.Events.on(this.engine, "collisionStart", this.handleCollisionStart);
|
|
1578
1611
|
}
|
|
1579
1612
|
static createContainer(parent, options = {}) {
|
|
1580
1613
|
const canvas = document.createElement("canvas");
|
|
@@ -1979,6 +2012,7 @@ var OverlayScene = class {
|
|
|
1979
2012
|
this.canvas.removeEventListener("click", this.handleCanvasClick);
|
|
1980
2013
|
import_matter_js5.default.Events.off(this.render, "beforeRender", this.handleBeforeRender);
|
|
1981
2014
|
import_matter_js5.default.Events.off(this.render, "afterRender", this.handleAfterRender);
|
|
2015
|
+
import_matter_js5.default.Events.off(this.engine, "collisionStart", this.handleCollisionStart);
|
|
1982
2016
|
import_matter_js5.default.Engine.clear(this.engine);
|
|
1983
2017
|
this.objects.clear();
|
|
1984
2018
|
this.obstaclePressure.clear();
|
|
@@ -1987,6 +2021,10 @@ var OverlayScene = class {
|
|
|
1987
2021
|
this.floorSegmentPressure.clear();
|
|
1988
2022
|
this.collapsedSegments.clear();
|
|
1989
2023
|
this.updateCallbacks = [];
|
|
2024
|
+
this.lifecycleCallbacks.objectSpawned = [];
|
|
2025
|
+
this.lifecycleCallbacks.objectRemoved = [];
|
|
2026
|
+
this.lifecycleCallbacks.objectCollision = [];
|
|
2027
|
+
this.followTargets.clear();
|
|
1990
2028
|
}
|
|
1991
2029
|
setDebug(enabled) {
|
|
1992
2030
|
this.config.debug = enabled;
|
|
@@ -2101,6 +2139,7 @@ var OverlayScene = class {
|
|
|
2101
2139
|
if (isStatic && pressureThreshold !== void 0) {
|
|
2102
2140
|
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
2103
2141
|
}
|
|
2142
|
+
this.emitLifecycleEvent("objectSpawned", this.toObjectState(entry));
|
|
2104
2143
|
return id;
|
|
2105
2144
|
}
|
|
2106
2145
|
/**
|
|
@@ -2156,6 +2195,7 @@ var OverlayScene = class {
|
|
|
2156
2195
|
if (isStatic && pressureThreshold !== void 0) {
|
|
2157
2196
|
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
2158
2197
|
}
|
|
2198
|
+
this.emitLifecycleEvent("objectSpawned", this.toObjectState(entry));
|
|
2159
2199
|
return id;
|
|
2160
2200
|
}
|
|
2161
2201
|
/**
|
|
@@ -2237,6 +2277,7 @@ var OverlayScene = class {
|
|
|
2237
2277
|
removeObject(id) {
|
|
2238
2278
|
const entry = this.objects.get(id);
|
|
2239
2279
|
if (!entry) return;
|
|
2280
|
+
this.emitLifecycleEvent("objectRemoved", this.toObjectState(entry));
|
|
2240
2281
|
import_matter_js5.default.Composite.remove(this.engine.world, entry.body);
|
|
2241
2282
|
this.objects.delete(id);
|
|
2242
2283
|
}
|
|
@@ -2286,8 +2327,156 @@ var OverlayScene = class {
|
|
|
2286
2327
|
}
|
|
2287
2328
|
return Array.from(tagsSet).sort();
|
|
2288
2329
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2330
|
+
/**
|
|
2331
|
+
* Set the mouse position for follow behavior.
|
|
2332
|
+
* This overrides the browser mouse position for the 'follow' and 'follow-mouse' tags.
|
|
2333
|
+
* @deprecated Use setFollowTarget('mouse', x, y) instead
|
|
2334
|
+
*/
|
|
2335
|
+
setMousePosition(x, y) {
|
|
2336
|
+
this.setFollowTarget("mouse", x, y);
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Set a follow target position. Objects with 'follow-{key}' tag will
|
|
2340
|
+
* automatically move toward this target each frame.
|
|
2341
|
+
* @param key - The target key (e.g., 'absolute' for 'follow-absolute' tag)
|
|
2342
|
+
* @param x - Target X position
|
|
2343
|
+
* @param y - Target Y position
|
|
2344
|
+
*/
|
|
2345
|
+
setFollowTarget(key, x, y) {
|
|
2346
|
+
this.followTargets.set(key, { x, y });
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* Remove a follow target. Objects with the corresponding tag will stop following.
|
|
2350
|
+
* @param key - The target key to remove
|
|
2351
|
+
*/
|
|
2352
|
+
removeFollowTarget(key) {
|
|
2353
|
+
this.followTargets.delete(key);
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Get all registered follow target keys.
|
|
2357
|
+
* @returns Array of follow target keys
|
|
2358
|
+
*/
|
|
2359
|
+
getFollowTargetKeys() {
|
|
2360
|
+
return Array.from(this.followTargets.keys());
|
|
2361
|
+
}
|
|
2362
|
+
// ==================== PHYSICS MANIPULATION METHODS ====================
|
|
2363
|
+
/**
|
|
2364
|
+
* Apply a force to an object.
|
|
2365
|
+
* @param objectId - The ID of the object
|
|
2366
|
+
* @param force - The force vector to apply
|
|
2367
|
+
*/
|
|
2368
|
+
applyForce(objectId, force) {
|
|
2369
|
+
const entry = this.objects.get(objectId);
|
|
2370
|
+
if (!entry) return;
|
|
2371
|
+
import_matter_js5.default.Body.applyForce(entry.body, entry.body.position, force);
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Apply a force to all objects with a specific tag.
|
|
2375
|
+
* @param tag - The tag to match
|
|
2376
|
+
* @param force - The force vector to apply
|
|
2377
|
+
*/
|
|
2378
|
+
applyForceToTag(tag, force) {
|
|
2379
|
+
for (const entry of this.objects.values()) {
|
|
2380
|
+
if (entry.tags.includes(tag)) {
|
|
2381
|
+
import_matter_js5.default.Body.applyForce(entry.body, entry.body.position, force);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Set the velocity of an object.
|
|
2387
|
+
* @param objectId - The ID of the object
|
|
2388
|
+
* @param velocity - The velocity vector to set
|
|
2389
|
+
*/
|
|
2390
|
+
setVelocity(objectId, velocity) {
|
|
2391
|
+
const entry = this.objects.get(objectId);
|
|
2392
|
+
if (!entry) return;
|
|
2393
|
+
import_matter_js5.default.Body.setVelocity(entry.body, velocity);
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Set the position of an object.
|
|
2397
|
+
* @param objectId - The ID of the object
|
|
2398
|
+
* @param position - The position to set
|
|
2399
|
+
*/
|
|
2400
|
+
setPosition(objectId, position) {
|
|
2401
|
+
const entry = this.objects.get(objectId);
|
|
2402
|
+
if (!entry) return;
|
|
2403
|
+
import_matter_js5.default.Body.setPosition(entry.body, position);
|
|
2404
|
+
}
|
|
2405
|
+
// ==================== OBJECT STATE METHODS ====================
|
|
2406
|
+
/**
|
|
2407
|
+
* Get the current state of an object.
|
|
2408
|
+
* @param id - The ID of the object
|
|
2409
|
+
* @returns The object state, or null if not found
|
|
2410
|
+
*/
|
|
2411
|
+
getObject(id) {
|
|
2412
|
+
const entry = this.objects.get(id);
|
|
2413
|
+
if (!entry) return null;
|
|
2414
|
+
return {
|
|
2415
|
+
id: entry.id,
|
|
2416
|
+
x: entry.body.position.x,
|
|
2417
|
+
y: entry.body.position.y,
|
|
2418
|
+
velocity: { x: entry.body.velocity.x, y: entry.body.velocity.y },
|
|
2419
|
+
angle: entry.body.angle,
|
|
2420
|
+
tags: [...entry.tags]
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
/**
|
|
2424
|
+
* Get the current state of all objects with a specific tag.
|
|
2425
|
+
* @param tag - The tag to match
|
|
2426
|
+
* @returns Array of object states
|
|
2427
|
+
*/
|
|
2428
|
+
getObjectsByTag(tag) {
|
|
2429
|
+
const result = [];
|
|
2430
|
+
for (const entry of this.objects.values()) {
|
|
2431
|
+
if (entry.tags.includes(tag)) {
|
|
2432
|
+
result.push({
|
|
2433
|
+
id: entry.id,
|
|
2434
|
+
x: entry.body.position.x,
|
|
2435
|
+
y: entry.body.position.y,
|
|
2436
|
+
velocity: { x: entry.body.velocity.x, y: entry.body.velocity.y },
|
|
2437
|
+
angle: entry.body.angle,
|
|
2438
|
+
tags: [...entry.tags]
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
return result;
|
|
2443
|
+
}
|
|
2444
|
+
// ==================== LIFECYCLE EVENTS ====================
|
|
2445
|
+
/**
|
|
2446
|
+
* Subscribe to a lifecycle event.
|
|
2447
|
+
* @param event - The event type to subscribe to
|
|
2448
|
+
* @param callback - The callback to invoke when the event occurs
|
|
2449
|
+
*/
|
|
2450
|
+
on(event, callback) {
|
|
2451
|
+
this.lifecycleCallbacks[event].push(callback);
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Unsubscribe from a lifecycle event.
|
|
2455
|
+
* @param event - The event type to unsubscribe from
|
|
2456
|
+
* @param callback - The callback to remove
|
|
2457
|
+
*/
|
|
2458
|
+
off(event, callback) {
|
|
2459
|
+
const arr = this.lifecycleCallbacks[event];
|
|
2460
|
+
const idx = arr.indexOf(callback);
|
|
2461
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
2462
|
+
}
|
|
2463
|
+
/** Create ObjectState from an ObjectEntry */
|
|
2464
|
+
toObjectState(entry) {
|
|
2465
|
+
return {
|
|
2466
|
+
id: entry.id,
|
|
2467
|
+
x: entry.body.position.x,
|
|
2468
|
+
y: entry.body.position.y,
|
|
2469
|
+
velocity: { x: entry.body.velocity.x, y: entry.body.velocity.y },
|
|
2470
|
+
angle: entry.body.angle,
|
|
2471
|
+
tags: [...entry.tags]
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
/** Emit a lifecycle event to all registered callbacks */
|
|
2475
|
+
emitLifecycleEvent(event, ...args) {
|
|
2476
|
+
const callbacks = this.lifecycleCallbacks[event];
|
|
2477
|
+
for (const cb of callbacks) {
|
|
2478
|
+
cb(...args);
|
|
2479
|
+
}
|
|
2291
2480
|
}
|
|
2292
2481
|
// ==================== PRESSURE TRACKING METHODS ====================
|
|
2293
2482
|
/**
|