cubeforge 0.0.6 → 0.0.7

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.js CHANGED
@@ -114,6 +114,23 @@ class ECSWorld {
114
114
  }
115
115
  return;
116
116
  }
117
+ findByTag(tag) {
118
+ for (const id of this.query("Tag")) {
119
+ const t = this.getComponent(id, "Tag");
120
+ if (t?.tags.includes(tag))
121
+ return id;
122
+ }
123
+ return;
124
+ }
125
+ findAllByTag(tag) {
126
+ const result = [];
127
+ for (const id of this.query("Tag")) {
128
+ const t = this.getComponent(id, "Tag");
129
+ if (t?.tags.includes(tag))
130
+ result.push(id);
131
+ }
132
+ return result;
133
+ }
117
134
  setDeterministicSeed(seed) {
118
135
  this._rngState = seed >>> 0;
119
136
  this._deterministic = true;
@@ -194,6 +211,16 @@ class ECSWorld {
194
211
  return this.componentIndex.size;
195
212
  }
196
213
  }
214
+ // ../core/src/ecs/worldQueries.ts
215
+ function findByTag(world, tag) {
216
+ const results = [];
217
+ for (const id of world.query("Tag")) {
218
+ const t = world.getComponent(id, "Tag");
219
+ if (t?.tags.includes(tag))
220
+ results.push(id);
221
+ }
222
+ return results;
223
+ }
197
224
  // ../core/src/loop/gameLoop.ts
198
225
  class GameLoop {
199
226
  onTick;
@@ -605,6 +632,24 @@ class InputManager {
605
632
  return this.keyboard.isReleased(key);
606
633
  }
607
634
  }
635
+ // ../input/src/inputMap.ts
636
+ function createInputMap(bindings) {
637
+ const normalized = {};
638
+ for (const [action, keys] of Object.entries(bindings)) {
639
+ normalized[action] = Array.isArray(keys) ? keys : [keys];
640
+ }
641
+ return {
642
+ isActionDown(input, action) {
643
+ return (normalized[action] ?? []).some((k) => input.isDown(k));
644
+ },
645
+ isActionPressed(input, action) {
646
+ return (normalized[action] ?? []).some((k) => input.isPressed(k));
647
+ },
648
+ isActionReleased(input, action) {
649
+ return (normalized[action] ?? []).some((k) => input.isReleased(k));
650
+ }
651
+ };
652
+ }
608
653
  // ../renderer/src/canvas2d.ts
609
654
  class Canvas2DRenderer {
610
655
  canvas;
@@ -656,6 +701,8 @@ function createCamera2D(opts) {
656
701
  zoom: 1,
657
702
  smoothing: 0,
658
703
  background: "#1a1a2e",
704
+ followOffsetX: 0,
705
+ followOffsetY: 0,
659
706
  shakeIntensity: 0,
660
707
  shakeDuration: 0,
661
708
  shakeTimer: 0,
@@ -712,25 +759,27 @@ class RenderSystem {
712
759
  if (targetId !== undefined) {
713
760
  const targetTransform = world2.getComponent(targetId, "Transform");
714
761
  if (targetTransform) {
762
+ const tx = targetTransform.x + (cam.followOffsetX ?? 0);
763
+ const ty = targetTransform.y + (cam.followOffsetY ?? 0);
715
764
  if (cam.deadZone) {
716
765
  const halfW = cam.deadZone.w / 2;
717
766
  const halfH = cam.deadZone.h / 2;
718
- const dx = targetTransform.x - cam.x;
719
- const dy = targetTransform.y - cam.y;
767
+ const dx = tx - cam.x;
768
+ const dy = ty - cam.y;
720
769
  if (dx > halfW)
721
- cam.x = targetTransform.x - halfW;
770
+ cam.x = tx - halfW;
722
771
  else if (dx < -halfW)
723
- cam.x = targetTransform.x + halfW;
772
+ cam.x = tx + halfW;
724
773
  if (dy > halfH)
725
- cam.y = targetTransform.y - halfH;
774
+ cam.y = ty - halfH;
726
775
  else if (dy < -halfH)
727
- cam.y = targetTransform.y + halfH;
776
+ cam.y = ty + halfH;
728
777
  } else if (cam.smoothing > 0) {
729
- cam.x += (targetTransform.x - cam.x) * (1 - cam.smoothing);
730
- cam.y += (targetTransform.y - cam.y) * (1 - cam.smoothing);
778
+ cam.x += (tx - cam.x) * (1 - cam.smoothing);
779
+ cam.y += (ty - cam.y) * (1 - cam.smoothing);
731
780
  } else {
732
- cam.x = targetTransform.x;
733
- cam.y = targetTransform.y;
781
+ cam.x = tx;
782
+ cam.y = ty;
734
783
  }
735
784
  }
736
785
  }
@@ -764,7 +813,16 @@ class RenderSystem {
764
813
  anim.timer -= frameDuration;
765
814
  anim.currentIndex++;
766
815
  if (anim.currentIndex >= anim.frames.length) {
767
- anim.currentIndex = anim.loop ? 0 : anim.frames.length - 1;
816
+ if (anim.loop) {
817
+ anim.currentIndex = 0;
818
+ } else {
819
+ anim.currentIndex = anim.frames.length - 1;
820
+ anim.playing = false;
821
+ if (anim.onComplete && !anim._completed) {
822
+ anim._completed = true;
823
+ anim.onComplete();
824
+ }
825
+ }
768
826
  }
769
827
  }
770
828
  sprite.frameIndex = anim.frames[anim.currentIndex];
@@ -958,6 +1016,8 @@ function createRigidBody(opts) {
958
1016
  isNearGround: false,
959
1017
  bounce: 0,
960
1018
  friction: 0.85,
1019
+ lockX: false,
1020
+ lockY: false,
961
1021
  ...opts
962
1022
  };
963
1023
  }
@@ -971,7 +1031,22 @@ function createBoxCollider(width, height, opts) {
971
1031
  offsetY: 0,
972
1032
  isTrigger: false,
973
1033
  layer: "default",
1034
+ mask: "*",
974
1035
  slope: 0,
1036
+ oneWay: false,
1037
+ ...opts
1038
+ };
1039
+ }
1040
+ // ../physics/src/components/circleCollider.ts
1041
+ function createCircleCollider(radius, opts) {
1042
+ return {
1043
+ type: "CircleCollider",
1044
+ radius,
1045
+ offsetX: 0,
1046
+ offsetY: 0,
1047
+ isTrigger: false,
1048
+ layer: "default",
1049
+ mask: "*",
975
1050
  ...opts
976
1051
  };
977
1052
  }
@@ -1009,12 +1084,27 @@ function getSlopeSurfaceY(st, sc, worldX) {
1009
1084
  const angleRad = sc.slope * (Math.PI / 180);
1010
1085
  return cy - hh + dx * Math.tan(angleRad);
1011
1086
  }
1087
+ function maskAllows(mask, layer) {
1088
+ if (mask === "*")
1089
+ return true;
1090
+ return Array.isArray(mask) && mask.includes(layer);
1091
+ }
1092
+ function canInteract(a, b) {
1093
+ return maskAllows(a.mask, b.layer) && maskAllows(b.mask, a.layer);
1094
+ }
1095
+ function pairKey(a, b) {
1096
+ return a < b ? `${a}:${b}` : `${b}:${a}`;
1097
+ }
1012
1098
 
1013
1099
  class PhysicsSystem {
1014
1100
  gravity;
1015
1101
  events;
1016
1102
  accumulator = 0;
1017
1103
  FIXED_DT = 1 / 60;
1104
+ activeTriggerPairs = new Map;
1105
+ activeCollisionPairs = new Map;
1106
+ activeCirclePairs = new Map;
1107
+ staticPrevPos = new Map;
1018
1108
  constructor(gravity, events) {
1019
1109
  this.gravity = gravity;
1020
1110
  this.events = events;
@@ -1055,6 +1145,30 @@ class PhysicsSystem {
1055
1145
  else
1056
1146
  dynamics.push(id);
1057
1147
  }
1148
+ for (const [key, [a, b]] of this.activeTriggerPairs) {
1149
+ if (!world2.hasEntity(a) || !world2.hasEntity(b)) {
1150
+ this.events?.emit("triggerExit", { a, b });
1151
+ this.activeTriggerPairs.delete(key);
1152
+ }
1153
+ }
1154
+ for (const [key, [a, b]] of this.activeCollisionPairs) {
1155
+ if (!world2.hasEntity(a) || !world2.hasEntity(b)) {
1156
+ this.events?.emit("collisionExit", { a, b });
1157
+ this.activeCollisionPairs.delete(key);
1158
+ }
1159
+ }
1160
+ const staticDelta = new Map;
1161
+ for (const sid of statics) {
1162
+ const st = world2.getComponent(sid, "Transform");
1163
+ const prev = this.staticPrevPos.get(sid);
1164
+ if (prev)
1165
+ staticDelta.set(sid, { dx: st.x - prev.x, dy: st.y - prev.y });
1166
+ this.staticPrevPos.set(sid, { x: st.x, y: st.y });
1167
+ }
1168
+ for (const sid of this.staticPrevPos.keys()) {
1169
+ if (!world2.hasEntity(sid))
1170
+ this.staticPrevPos.delete(sid);
1171
+ }
1058
1172
  const staticGrid = new Map;
1059
1173
  for (const sid of statics) {
1060
1174
  const st = world2.getComponent(sid, "Transform");
@@ -1073,7 +1187,12 @@ class PhysicsSystem {
1073
1187
  const rb = world2.getComponent(id, "RigidBody");
1074
1188
  rb.onGround = false;
1075
1189
  rb.isNearGround = false;
1076
- rb.vy += this.gravity * rb.gravityScale * dt;
1190
+ if (!rb.lockY)
1191
+ rb.vy += this.gravity * rb.gravityScale * dt;
1192
+ if (rb.lockX)
1193
+ rb.vx = 0;
1194
+ if (rb.lockY)
1195
+ rb.vy = 0;
1077
1196
  }
1078
1197
  for (const id of dynamics) {
1079
1198
  const transform2 = world2.getComponent(id, "Transform");
@@ -1098,6 +1217,8 @@ class PhysicsSystem {
1098
1217
  continue;
1099
1218
  if (sc.slope !== 0)
1100
1219
  continue;
1220
+ if (!canInteract(col, sc))
1221
+ continue;
1101
1222
  const ov = getOverlap(getAABB(transform2, col), getAABB(st, sc));
1102
1223
  if (!ov)
1103
1224
  continue;
@@ -1130,6 +1251,8 @@ class PhysicsSystem {
1130
1251
  const sc = world2.getComponent(sid, "BoxCollider");
1131
1252
  if (sc.isTrigger)
1132
1253
  continue;
1254
+ if (!canInteract(col, sc))
1255
+ continue;
1133
1256
  if (sc.slope !== 0) {
1134
1257
  const ov2 = getOverlap(getAABB(transform2, col), getAABB(st, sc));
1135
1258
  if (!ov2)
@@ -1150,11 +1273,25 @@ class PhysicsSystem {
1150
1273
  if (!ov)
1151
1274
  continue;
1152
1275
  if (Math.abs(ov.y) <= Math.abs(ov.x)) {
1276
+ if (sc.oneWay) {
1277
+ if (ov.y >= 0)
1278
+ continue;
1279
+ const platformTop = st.y + sc.offsetY - sc.height / 2;
1280
+ const prevEntityBottom = transform2.y - rb.vy * dt + col.offsetY + col.height / 2;
1281
+ if (prevEntityBottom > platformTop)
1282
+ continue;
1283
+ }
1153
1284
  transform2.y += ov.y;
1154
1285
  if (ov.y < 0) {
1155
1286
  rb.onGround = true;
1156
1287
  if (rb.friction < 1)
1157
1288
  rb.vx *= rb.friction;
1289
+ const delta = staticDelta.get(sid);
1290
+ if (delta) {
1291
+ transform2.x += delta.dx;
1292
+ if (delta.dy < 0)
1293
+ transform2.y += delta.dy;
1294
+ }
1158
1295
  }
1159
1296
  rb.vy = rb.bounce > 0 ? -rb.vy * rb.bounce : 0;
1160
1297
  }
@@ -1162,6 +1299,7 @@ class PhysicsSystem {
1162
1299
  }
1163
1300
  }
1164
1301
  }
1302
+ const currentCollisionPairs = new Map;
1165
1303
  for (let i = 0;i < dynamics.length; i++) {
1166
1304
  for (let j = i + 1;j < dynamics.length; j++) {
1167
1305
  const ia = dynamics[i];
@@ -1173,10 +1311,10 @@ class PhysicsSystem {
1173
1311
  const ov = getOverlap(getAABB(ta, ca), getAABB(tb, cb));
1174
1312
  if (!ov)
1175
1313
  continue;
1176
- if (ca.isTrigger || cb.isTrigger) {
1177
- this.events?.emit("trigger", { a: ia, b: ib });
1314
+ if (!canInteract(ca, cb))
1315
+ continue;
1316
+ if (ca.isTrigger || cb.isTrigger)
1178
1317
  continue;
1179
- }
1180
1318
  const rba = world2.getComponent(ia, "RigidBody");
1181
1319
  const rbb = world2.getComponent(ib, "RigidBody");
1182
1320
  if (Math.abs(ov.y) <= Math.abs(ov.x)) {
@@ -1198,9 +1336,22 @@ class PhysicsSystem {
1198
1336
  ta.y += ov.y / 2;
1199
1337
  tb.x -= ov.x / 2;
1200
1338
  tb.y -= ov.y / 2;
1201
- this.events?.emit("collision", { a: ia, b: ib });
1339
+ const key = pairKey(ia, ib);
1340
+ currentCollisionPairs.set(key, [ia, ib]);
1202
1341
  }
1203
1342
  }
1343
+ for (const [key, [a, b]] of currentCollisionPairs) {
1344
+ if (!this.activeCollisionPairs.has(key)) {
1345
+ this.events?.emit("collisionEnter", { a, b });
1346
+ }
1347
+ this.events?.emit("collision", { a, b });
1348
+ }
1349
+ for (const [key, [a, b]] of this.activeCollisionPairs) {
1350
+ if (!currentCollisionPairs.has(key)) {
1351
+ this.events?.emit("collisionExit", { a, b });
1352
+ }
1353
+ }
1354
+ this.activeCollisionPairs = currentCollisionPairs;
1204
1355
  for (const id of dynamics) {
1205
1356
  const rb = world2.getComponent(id, "RigidBody");
1206
1357
  if (rb.onGround) {
@@ -1230,6 +1381,8 @@ class PhysicsSystem {
1230
1381
  const sc = world2.getComponent(sid, "BoxCollider");
1231
1382
  if (sc.isTrigger)
1232
1383
  continue;
1384
+ if (!canInteract(col, sc))
1385
+ continue;
1233
1386
  const ov = getOverlap(probeAABB, getAABB(st, sc));
1234
1387
  if (ov && Math.abs(ov.y) <= Math.abs(ov.x) && ov.y < 0) {
1235
1388
  rb.isNearGround = true;
@@ -1238,8 +1391,195 @@ class PhysicsSystem {
1238
1391
  }
1239
1392
  }
1240
1393
  }
1394
+ const allWithCollider = world2.query("Transform", "BoxCollider");
1395
+ const currentTriggerPairs = new Map;
1396
+ for (let i = 0;i < allWithCollider.length; i++) {
1397
+ for (let j = i + 1;j < allWithCollider.length; j++) {
1398
+ const ia = allWithCollider[i];
1399
+ const ib = allWithCollider[j];
1400
+ const ca = world2.getComponent(ia, "BoxCollider");
1401
+ const cb = world2.getComponent(ib, "BoxCollider");
1402
+ if (!ca.isTrigger && !cb.isTrigger)
1403
+ continue;
1404
+ if (!canInteract(ca, cb))
1405
+ continue;
1406
+ const ta = world2.getComponent(ia, "Transform");
1407
+ const tb = world2.getComponent(ib, "Transform");
1408
+ const ov = getOverlap(getAABB(ta, ca), getAABB(tb, cb));
1409
+ if (!ov)
1410
+ continue;
1411
+ const key = pairKey(ia, ib);
1412
+ currentTriggerPairs.set(key, [ia, ib]);
1413
+ }
1414
+ }
1415
+ for (const [key, [a, b]] of currentTriggerPairs) {
1416
+ if (!this.activeTriggerPairs.has(key)) {
1417
+ this.events?.emit("triggerEnter", { a, b });
1418
+ }
1419
+ this.events?.emit("trigger", { a, b });
1420
+ }
1421
+ for (const [key, [a, b]] of this.activeTriggerPairs) {
1422
+ if (!currentTriggerPairs.has(key)) {
1423
+ this.events?.emit("triggerExit", { a, b });
1424
+ }
1425
+ }
1426
+ this.activeTriggerPairs = currentTriggerPairs;
1427
+ const allCircles = world2.query("Transform", "CircleCollider");
1428
+ if (allCircles.length > 0) {
1429
+ const currentCirclePairs = new Map;
1430
+ for (let i = 0;i < allCircles.length; i++) {
1431
+ for (let j = i + 1;j < allCircles.length; j++) {
1432
+ const ia = allCircles[i];
1433
+ const ib = allCircles[j];
1434
+ const ca = world2.getComponent(ia, "CircleCollider");
1435
+ const cb = world2.getComponent(ib, "CircleCollider");
1436
+ if (!maskAllows(ca.mask, cb.layer) || !maskAllows(cb.mask, ca.layer))
1437
+ continue;
1438
+ const ta = world2.getComponent(ia, "Transform");
1439
+ const tb = world2.getComponent(ib, "Transform");
1440
+ const dx = ta.x + ca.offsetX - (tb.x + cb.offsetX);
1441
+ const dy = ta.y + ca.offsetY - (tb.y + cb.offsetY);
1442
+ if (dx * dx + dy * dy < (ca.radius + cb.radius) ** 2) {
1443
+ currentCirclePairs.set(pairKey(ia, ib), [ia, ib]);
1444
+ }
1445
+ }
1446
+ }
1447
+ const allBoxes = world2.query("Transform", "BoxCollider");
1448
+ for (const cid of allCircles) {
1449
+ const cc = world2.getComponent(cid, "CircleCollider");
1450
+ const ct = world2.getComponent(cid, "Transform");
1451
+ const cx = ct.x + cc.offsetX;
1452
+ const cy = ct.y + cc.offsetY;
1453
+ for (const bid of allBoxes) {
1454
+ if (bid === cid)
1455
+ continue;
1456
+ const bc = world2.getComponent(bid, "BoxCollider");
1457
+ if (!maskAllows(cc.mask, bc.layer) || !maskAllows(bc.mask, cc.layer))
1458
+ continue;
1459
+ const bt = world2.getComponent(bid, "Transform");
1460
+ const bx = bt.x + bc.offsetX;
1461
+ const by = bt.y + bc.offsetY;
1462
+ const nearX = Math.max(bx - bc.width / 2, Math.min(cx, bx + bc.width / 2));
1463
+ const nearY = Math.max(by - bc.height / 2, Math.min(cy, by + bc.height / 2));
1464
+ const dx = cx - nearX;
1465
+ const dy = cy - nearY;
1466
+ if (dx * dx + dy * dy < cc.radius * cc.radius) {
1467
+ currentCirclePairs.set(pairKey(cid, bid), [cid, bid]);
1468
+ }
1469
+ }
1470
+ }
1471
+ for (const [key, [a, b]] of currentCirclePairs) {
1472
+ if (!this.activeCirclePairs.has(key)) {
1473
+ this.events?.emit("circleEnter", { a, b });
1474
+ }
1475
+ this.events?.emit("circle", { a, b });
1476
+ }
1477
+ for (const [key, [a, b]] of this.activeCirclePairs) {
1478
+ if (!currentCirclePairs.has(key)) {
1479
+ this.events?.emit("circleExit", { a, b });
1480
+ }
1481
+ }
1482
+ this.activeCirclePairs = currentCirclePairs;
1483
+ }
1241
1484
  }
1242
1485
  }
1486
+ // ../physics/src/queries.ts
1487
+ function passesFilter(world2, id, col, opts) {
1488
+ if (opts.exclude?.includes(id))
1489
+ return false;
1490
+ if (opts.layer && col.layer !== opts.layer)
1491
+ return false;
1492
+ if (opts.tag) {
1493
+ const t = world2.getComponent(id, "Tag");
1494
+ if (!t?.tags.includes(opts.tag))
1495
+ return false;
1496
+ }
1497
+ return true;
1498
+ }
1499
+ function overlapBox(world2, cx, cy, hw, hh, opts = {}) {
1500
+ const results = [];
1501
+ for (const id of world2.query("Transform", "BoxCollider")) {
1502
+ const t = world2.getComponent(id, "Transform");
1503
+ const c = world2.getComponent(id, "BoxCollider");
1504
+ if (!passesFilter(world2, id, c, opts))
1505
+ continue;
1506
+ const ecx = t.x + c.offsetX;
1507
+ const ecy = t.y + c.offsetY;
1508
+ const ehw = c.width / 2;
1509
+ const ehh = c.height / 2;
1510
+ if (Math.abs(ecx - cx) < hw + ehw && Math.abs(ecy - cy) < hh + ehh) {
1511
+ results.push(id);
1512
+ }
1513
+ }
1514
+ return results;
1515
+ }
1516
+ function raycast(world2, origin, direction, maxDistance, opts = {}) {
1517
+ const len = Math.hypot(direction.x, direction.y);
1518
+ if (len === 0)
1519
+ return null;
1520
+ const dx = direction.x / len;
1521
+ const dy = direction.y / len;
1522
+ let closest = null;
1523
+ for (const id of world2.query("Transform", "BoxCollider")) {
1524
+ const t = world2.getComponent(id, "Transform");
1525
+ const c = world2.getComponent(id, "BoxCollider");
1526
+ if (!opts.includeTriggers && c.isTrigger)
1527
+ continue;
1528
+ if (!passesFilter(world2, id, c, opts))
1529
+ continue;
1530
+ const cx = t.x + c.offsetX;
1531
+ const cy = t.y + c.offsetY;
1532
+ const hw = c.width / 2;
1533
+ const hh = c.height / 2;
1534
+ const left = cx - hw;
1535
+ const right = cx + hw;
1536
+ const top = cy - hh;
1537
+ const bottom = cy + hh;
1538
+ let tmin = -Infinity;
1539
+ let tmax = Infinity;
1540
+ if (dx !== 0) {
1541
+ const t1 = (left - origin.x) / dx;
1542
+ const t2 = (right - origin.x) / dx;
1543
+ tmin = Math.max(tmin, Math.min(t1, t2));
1544
+ tmax = Math.min(tmax, Math.max(t1, t2));
1545
+ } else if (origin.x < left || origin.x > right) {
1546
+ continue;
1547
+ }
1548
+ if (dy !== 0) {
1549
+ const t1 = (top - origin.y) / dy;
1550
+ const t2 = (bottom - origin.y) / dy;
1551
+ tmin = Math.max(tmin, Math.min(t1, t2));
1552
+ tmax = Math.min(tmax, Math.max(t1, t2));
1553
+ } else if (origin.y < top || origin.y > bottom) {
1554
+ continue;
1555
+ }
1556
+ if (tmax < 0 || tmin > tmax || tmin > maxDistance)
1557
+ continue;
1558
+ const dist = Math.max(0, tmin);
1559
+ if (closest && dist >= closest.distance)
1560
+ continue;
1561
+ const hitX = origin.x + dx * tmin;
1562
+ const hitY = origin.y + dy * tmin;
1563
+ let nx = 0;
1564
+ let ny = 0;
1565
+ const edgeEps = 0.001;
1566
+ if (Math.abs(hitX - left) < edgeEps)
1567
+ nx = -1;
1568
+ else if (Math.abs(hitX - right) < edgeEps)
1569
+ nx = 1;
1570
+ else if (Math.abs(hitY - top) < edgeEps)
1571
+ ny = -1;
1572
+ else if (Math.abs(hitY - bottom) < edgeEps)
1573
+ ny = 1;
1574
+ closest = {
1575
+ entityId: id,
1576
+ distance: dist,
1577
+ point: { x: hitX, y: hitY },
1578
+ normal: { x: nx, y: ny }
1579
+ };
1580
+ }
1581
+ return closest;
1582
+ }
1243
1583
  // src/context.ts
1244
1584
  import { createContext } from "react";
1245
1585
  var EngineContext = createContext(null);
@@ -1657,6 +1997,7 @@ function Game({
1657
1997
  seed = 0,
1658
1998
  onReady,
1659
1999
  plugins,
2000
+ renderer: CustomRenderer,
1660
2001
  style,
1661
2002
  className,
1662
2003
  children
@@ -1671,13 +2012,21 @@ function Game({
1671
2012
  if (deterministic)
1672
2013
  ecs.setDeterministicSeed(seed);
1673
2014
  const input = new InputManager;
1674
- const renderer = new Canvas2DRenderer(canvas);
1675
2015
  const events = new EventBus;
1676
2016
  const assets = new AssetManager;
1677
2017
  const physics = new PhysicsSystem(gravity, events);
1678
2018
  const entityIds = new Map;
1679
- const renderSystem2 = new RenderSystem(renderer, entityIds);
1680
- const debugSystem = debug ? new DebugSystem(renderer) : null;
2019
+ let canvas2d2;
2020
+ let builtinRenderSystem;
2021
+ let renderSystem2;
2022
+ if (CustomRenderer) {
2023
+ renderSystem2 = new CustomRenderer(canvas, entityIds);
2024
+ } else {
2025
+ canvas2d2 = new Canvas2DRenderer(canvas);
2026
+ builtinRenderSystem = new RenderSystem(canvas2d2, entityIds);
2027
+ renderSystem2 = builtinRenderSystem;
2028
+ }
2029
+ const debugSystem = debug && canvas2d2 ? new DebugSystem(canvas2d2) : null;
1681
2030
  ecs.addSystem(new ScriptSystem(input));
1682
2031
  ecs.addSystem(physics);
1683
2032
  ecs.addSystem(renderSystem2);
@@ -1699,7 +2048,7 @@ function Game({
1699
2048
  handle.onFrame?.();
1700
2049
  }
1701
2050
  });
1702
- const state = { ecs, input, renderer, physics, events, assets, loop, canvas, entityIds };
2051
+ const state = { ecs, input, renderer: canvas2d2, renderSystem: builtinRenderSystem, physics, events, assets, loop, canvas, entityIds };
1703
2052
  setEngine(state);
1704
2053
  if (plugins) {
1705
2054
  for (const plugin2 of plugins) {
@@ -1927,12 +2276,14 @@ function RigidBody({
1927
2276
  bounce = 0,
1928
2277
  friction = 0.85,
1929
2278
  vx = 0,
1930
- vy = 0
2279
+ vy = 0,
2280
+ lockX = false,
2281
+ lockY = false
1931
2282
  }) {
1932
2283
  const engine = useContext5(EngineContext);
1933
2284
  const entityId = useContext5(EntityContext);
1934
2285
  useEffect7(() => {
1935
- engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy }));
2286
+ engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY }));
1936
2287
  return () => engine.ecs.removeComponent(entityId, "RigidBody");
1937
2288
  }, []);
1938
2289
  return null;
@@ -1945,12 +2296,14 @@ function BoxCollider({
1945
2296
  offsetX = 0,
1946
2297
  offsetY = 0,
1947
2298
  isTrigger = false,
1948
- layer = "default"
2299
+ layer = "default",
2300
+ mask = "*",
2301
+ oneWay = false
1949
2302
  }) {
1950
2303
  const engine = useContext6(EngineContext);
1951
2304
  const entityId = useContext6(EntityContext);
1952
2305
  useEffect8(() => {
1953
- engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer }));
2306
+ engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer, mask, oneWay }));
1954
2307
  const checkId = setTimeout(() => {
1955
2308
  if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
1956
2309
  console.warn(`[Cubeforge] BoxCollider on entity ${entityId} has no Transform. Physics requires Transform.`);
@@ -1963,12 +2316,30 @@ function BoxCollider({
1963
2316
  }, []);
1964
2317
  return null;
1965
2318
  }
1966
- // src/components/Script.tsx
2319
+ // src/components/CircleCollider.tsx
1967
2320
  import { useEffect as useEffect9, useContext as useContext7 } from "react";
1968
- function Script({ init, update }) {
2321
+ function CircleCollider({
2322
+ radius,
2323
+ offsetX = 0,
2324
+ offsetY = 0,
2325
+ isTrigger = false,
2326
+ layer = "default",
2327
+ mask = "*"
2328
+ }) {
1969
2329
  const engine = useContext7(EngineContext);
1970
2330
  const entityId = useContext7(EntityContext);
1971
2331
  useEffect9(() => {
2332
+ engine.ecs.addComponent(entityId, createCircleCollider(radius, { offsetX, offsetY, isTrigger, layer, mask }));
2333
+ return () => engine.ecs.removeComponent(entityId, "CircleCollider");
2334
+ }, []);
2335
+ return null;
2336
+ }
2337
+ // src/components/Script.tsx
2338
+ import { useEffect as useEffect10, useContext as useContext8 } from "react";
2339
+ function Script({ init, update }) {
2340
+ const engine = useContext8(EngineContext);
2341
+ const entityId = useContext8(EntityContext);
2342
+ useEffect10(() => {
1972
2343
  if (init) {
1973
2344
  try {
1974
2345
  init(entityId, engine.ecs);
@@ -1982,17 +2353,19 @@ function Script({ init, update }) {
1982
2353
  return null;
1983
2354
  }
1984
2355
  // src/components/Camera2D.tsx
1985
- import { useEffect as useEffect10, useContext as useContext8 } from "react";
2356
+ import { useEffect as useEffect11, useContext as useContext9 } from "react";
1986
2357
  function Camera2D({
1987
2358
  followEntity,
1988
2359
  zoom = 1,
1989
2360
  smoothing = 0,
1990
2361
  background = "#1a1a2e",
1991
2362
  bounds,
1992
- deadZone
2363
+ deadZone,
2364
+ followOffsetX = 0,
2365
+ followOffsetY = 0
1993
2366
  }) {
1994
- const engine = useContext8(EngineContext);
1995
- useEffect10(() => {
2367
+ const engine = useContext9(EngineContext);
2368
+ useEffect11(() => {
1996
2369
  const entityId = engine.ecs.createEntity();
1997
2370
  engine.ecs.addComponent(entityId, createCamera2D({
1998
2371
  followEntityId: followEntity,
@@ -2000,11 +2373,13 @@ function Camera2D({
2000
2373
  smoothing,
2001
2374
  background,
2002
2375
  bounds,
2003
- deadZone
2376
+ deadZone,
2377
+ followOffsetX,
2378
+ followOffsetY
2004
2379
  }));
2005
2380
  return () => engine.ecs.destroyEntity(entityId);
2006
2381
  }, []);
2007
- useEffect10(() => {
2382
+ useEffect11(() => {
2008
2383
  const camId = engine.ecs.queryOne("Camera2D");
2009
2384
  if (camId === undefined)
2010
2385
  return;
@@ -2015,15 +2390,17 @@ function Camera2D({
2015
2390
  cam.background = background;
2016
2391
  cam.bounds = bounds;
2017
2392
  cam.deadZone = deadZone;
2018
- }, [followEntity, zoom, smoothing, background, bounds, deadZone, engine]);
2393
+ cam.followOffsetX = followOffsetX;
2394
+ cam.followOffsetY = followOffsetY;
2395
+ }, [followEntity, zoom, smoothing, background, bounds, deadZone, followOffsetX, followOffsetY, engine]);
2019
2396
  return null;
2020
2397
  }
2021
2398
  // src/components/Animation.tsx
2022
- import { useEffect as useEffect11, useContext as useContext9 } from "react";
2023
- function Animation({ frames, fps = 12, loop = true, playing = true }) {
2024
- const engine = useContext9(EngineContext);
2025
- const entityId = useContext9(EntityContext);
2026
- useEffect11(() => {
2399
+ import { useEffect as useEffect12, useContext as useContext10 } from "react";
2400
+ function Animation({ frames, fps = 12, loop = true, playing = true, onComplete }) {
2401
+ const engine = useContext10(EngineContext);
2402
+ const entityId = useContext10(EntityContext);
2403
+ useEffect12(() => {
2027
2404
  const state = {
2028
2405
  type: "AnimationState",
2029
2406
  frames,
@@ -2031,29 +2408,39 @@ function Animation({ frames, fps = 12, loop = true, playing = true }) {
2031
2408
  loop,
2032
2409
  playing,
2033
2410
  currentIndex: 0,
2034
- timer: 0
2411
+ timer: 0,
2412
+ _completed: false,
2413
+ onComplete
2035
2414
  };
2036
2415
  engine.ecs.addComponent(entityId, state);
2037
2416
  return () => {
2038
2417
  engine.ecs.removeComponent(entityId, "AnimationState");
2039
2418
  };
2040
2419
  }, []);
2041
- useEffect11(() => {
2420
+ useEffect12(() => {
2042
2421
  const anim = engine.ecs.getComponent(entityId, "AnimationState");
2043
2422
  if (!anim)
2044
2423
  return;
2424
+ const wasFramesChanged = anim.frames !== frames;
2045
2425
  anim.playing = playing;
2046
2426
  anim.fps = fps;
2047
2427
  anim.loop = loop;
2048
- }, [playing, fps, loop, engine, entityId]);
2428
+ anim.onComplete = onComplete;
2429
+ if (wasFramesChanged) {
2430
+ anim.frames = frames;
2431
+ anim.currentIndex = 0;
2432
+ anim.timer = 0;
2433
+ anim._completed = false;
2434
+ }
2435
+ }, [playing, fps, loop, frames, onComplete, engine, entityId]);
2049
2436
  return null;
2050
2437
  }
2051
2438
  // src/components/SquashStretch.tsx
2052
- import { useEffect as useEffect12, useContext as useContext10 } from "react";
2439
+ import { useEffect as useEffect13, useContext as useContext11 } from "react";
2053
2440
  function SquashStretch({ intensity = 0.2, recovery = 8 }) {
2054
- const engine = useContext10(EngineContext);
2055
- const entityId = useContext10(EntityContext);
2056
- useEffect12(() => {
2441
+ const engine = useContext11(EngineContext);
2442
+ const entityId = useContext11(EntityContext);
2443
+ useEffect13(() => {
2057
2444
  engine.ecs.addComponent(entityId, {
2058
2445
  type: "SquashStretch",
2059
2446
  intensity,
@@ -2066,7 +2453,7 @@ function SquashStretch({ intensity = 0.2, recovery = 8 }) {
2066
2453
  return null;
2067
2454
  }
2068
2455
  // src/components/ParticleEmitter.tsx
2069
- import { useEffect as useEffect13, useContext as useContext11 } from "react";
2456
+ import { useEffect as useEffect14, useContext as useContext12 } from "react";
2070
2457
 
2071
2458
  // src/components/particlePresets.ts
2072
2459
  var PARTICLE_PRESETS = {
@@ -2151,9 +2538,9 @@ function ParticleEmitter({
2151
2538
  const resolvedColor = color ?? presetConfig.color ?? "#ffffff";
2152
2539
  const resolvedGravity = gravity ?? presetConfig.gravity ?? 200;
2153
2540
  const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
2154
- const engine = useContext11(EngineContext);
2155
- const entityId = useContext11(EntityContext);
2156
- useEffect13(() => {
2541
+ const engine = useContext12(EngineContext);
2542
+ const entityId = useContext12(EntityContext);
2543
+ useEffect14(() => {
2157
2544
  engine.ecs.addComponent(entityId, {
2158
2545
  type: "ParticlePool",
2159
2546
  particles: [],
@@ -2171,7 +2558,7 @@ function ParticleEmitter({
2171
2558
  });
2172
2559
  return () => engine.ecs.removeComponent(entityId, "ParticlePool");
2173
2560
  }, []);
2174
- useEffect13(() => {
2561
+ useEffect14(() => {
2175
2562
  const pool = engine.ecs.getComponent(entityId, "ParticlePool");
2176
2563
  if (!pool)
2177
2564
  return;
@@ -2229,8 +2616,70 @@ function MovingPlatform({
2229
2616
  ]
2230
2617
  }, undefined, true, undefined, this);
2231
2618
  }
2619
+ // src/components/Checkpoint.tsx
2620
+ import { useState as useState4 } from "react";
2621
+
2622
+ // src/hooks/useContact.ts
2623
+ import { useContext as useContext13, useEffect as useEffect15 } from "react";
2624
+ function useContactEvent(eventName, handler, opts) {
2625
+ const engine = useContext13(EngineContext);
2626
+ const entityId = useContext13(EntityContext);
2627
+ if (!engine)
2628
+ throw new Error(`${eventName} hook must be used inside <Game>`);
2629
+ if (entityId === null)
2630
+ throw new Error(`${eventName} hook must be used inside <Entity>`);
2631
+ useEffect15(() => {
2632
+ return engine.events.on(eventName, ({ a, b }) => {
2633
+ const isA = a === entityId;
2634
+ const isB = b === entityId;
2635
+ if (!isA && !isB)
2636
+ return;
2637
+ const other = isA ? b : a;
2638
+ if (opts?.tag) {
2639
+ const tagComp = engine.ecs.getComponent(other, "Tag");
2640
+ if (!tagComp?.tags.includes(opts.tag))
2641
+ return;
2642
+ }
2643
+ if (opts?.layer) {
2644
+ const col = engine.ecs.getComponent(other, "BoxCollider");
2645
+ if (col?.layer !== opts.layer)
2646
+ return;
2647
+ }
2648
+ handler(other);
2649
+ });
2650
+ }, [engine.events, engine.ecs, entityId, opts?.tag, opts?.layer]);
2651
+ }
2652
+ function useTriggerEnter(handler, opts) {
2653
+ useContactEvent("triggerEnter", handler, opts);
2654
+ }
2655
+ function useTriggerExit(handler, opts) {
2656
+ useContactEvent("triggerExit", handler, opts);
2657
+ }
2658
+ function useCollisionEnter(handler, opts) {
2659
+ useContactEvent("collisionEnter", handler, opts);
2660
+ }
2661
+ function useCollisionExit(handler, opts) {
2662
+ useContactEvent("collisionExit", handler, opts);
2663
+ }
2664
+ function useCircleEnter(handler, opts) {
2665
+ useContactEvent("circleEnter", handler, opts);
2666
+ }
2667
+ function useCircleExit(handler, opts) {
2668
+ useContactEvent("circleExit", handler, opts);
2669
+ }
2670
+
2232
2671
  // src/components/Checkpoint.tsx
2233
2672
  import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
2673
+ function CheckpointActivator({ onActivate }) {
2674
+ const [used, setUsed] = useState4(false);
2675
+ useTriggerEnter(() => {
2676
+ if (used)
2677
+ return;
2678
+ setUsed(true);
2679
+ onActivate?.();
2680
+ }, { tag: "player" });
2681
+ return null;
2682
+ }
2234
2683
  function Checkpoint({
2235
2684
  x,
2236
2685
  y,
@@ -2257,36 +2706,14 @@ function Checkpoint({
2257
2706
  height,
2258
2707
  isTrigger: true
2259
2708
  }, undefined, false, undefined, this),
2260
- /* @__PURE__ */ jsxDEV6(Script, {
2261
- init: () => {},
2262
- update: (id, world2) => {
2263
- if (!world2.hasEntity(id))
2264
- return;
2265
- const ct = world2.getComponent(id, "Transform");
2266
- if (!ct)
2267
- return;
2268
- for (const pid of world2.query("Tag")) {
2269
- const tag2 = world2.getComponent(pid, "Tag");
2270
- if (!tag2?.tags.includes("player"))
2271
- continue;
2272
- const pt = world2.getComponent(pid, "Transform");
2273
- if (!pt)
2274
- continue;
2275
- const dx = Math.abs(pt.x - ct.x);
2276
- const dy = Math.abs(pt.y - ct.y);
2277
- if (dx < width / 2 + 16 && dy < height / 2 + 20) {
2278
- onActivate?.();
2279
- world2.destroyEntity(id);
2280
- return;
2281
- }
2282
- }
2283
- }
2709
+ /* @__PURE__ */ jsxDEV6(CheckpointActivator, {
2710
+ onActivate
2284
2711
  }, undefined, false, undefined, this)
2285
2712
  ]
2286
2713
  }, undefined, true, undefined, this);
2287
2714
  }
2288
2715
  // src/components/Tilemap.tsx
2289
- import { useEffect as useEffect14, useState as useState4, useContext as useContext12 } from "react";
2716
+ import { useEffect as useEffect16, useState as useState5, useContext as useContext14 } from "react";
2290
2717
  import { jsxDEV as jsxDEV7, Fragment as Fragment3 } from "react/jsx-dev-runtime";
2291
2718
  var animatedTiles = new Map;
2292
2719
  function getProperty(props, name) {
@@ -2310,9 +2737,9 @@ function Tilemap({
2310
2737
  triggerLayer: triggerLayerName = "triggers",
2311
2738
  onTileProperty
2312
2739
  }) {
2313
- const engine = useContext12(EngineContext);
2314
- const [spawnedNodes, setSpawnedNodes] = useState4([]);
2315
- useEffect14(() => {
2740
+ const engine = useContext14(EngineContext);
2741
+ const [spawnedNodes, setSpawnedNodes] = useState5([]);
2742
+ useEffect16(() => {
2316
2743
  if (!engine)
2317
2744
  return;
2318
2745
  const createdEntities = [];
@@ -2508,7 +2935,7 @@ function Tilemap({
2508
2935
  }, undefined, false, undefined, this);
2509
2936
  }
2510
2937
  // src/components/ParallaxLayer.tsx
2511
- import { useEffect as useEffect15, useContext as useContext13 } from "react";
2938
+ import { useEffect as useEffect17, useContext as useContext15 } from "react";
2512
2939
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
2513
2940
  function ParallaxLayerInner({
2514
2941
  src,
@@ -2520,9 +2947,9 @@ function ParallaxLayerInner({
2520
2947
  offsetX,
2521
2948
  offsetY
2522
2949
  }) {
2523
- const engine = useContext13(EngineContext);
2524
- const entityId = useContext13(EntityContext);
2525
- useEffect15(() => {
2950
+ const engine = useContext15(EngineContext);
2951
+ const entityId = useContext15(EntityContext);
2952
+ useEffect17(() => {
2526
2953
  engine.ecs.addComponent(entityId, {
2527
2954
  type: "ParallaxLayer",
2528
2955
  src,
@@ -2538,7 +2965,7 @@ function ParallaxLayerInner({
2538
2965
  });
2539
2966
  return () => engine.ecs.removeComponent(entityId, "ParallaxLayer");
2540
2967
  }, []);
2541
- useEffect15(() => {
2968
+ useEffect17(() => {
2542
2969
  const layer = engine.ecs.getComponent(entityId, "ParallaxLayer");
2543
2970
  if (!layer)
2544
2971
  return;
@@ -2620,47 +3047,111 @@ var ScreenFlash = forwardRef((_, ref) => {
2620
3047
  });
2621
3048
  ScreenFlash.displayName = "ScreenFlash";
2622
3049
  // src/hooks/useGame.ts
2623
- import { useContext as useContext14 } from "react";
3050
+ import { useContext as useContext16 } from "react";
2624
3051
  function useGame() {
2625
- const engine = useContext14(EngineContext);
3052
+ const engine = useContext16(EngineContext);
2626
3053
  if (!engine)
2627
3054
  throw new Error("useGame must be used inside <Game>");
2628
3055
  return engine;
2629
3056
  }
3057
+ // src/hooks/useCamera.ts
3058
+ import { useMemo } from "react";
3059
+ function useCamera() {
3060
+ const engine = useGame();
3061
+ return useMemo(() => ({
3062
+ shake(intensity, duration) {
3063
+ engine.renderSystem?.triggerShake(intensity, duration);
3064
+ },
3065
+ setFollowOffset(x, y) {
3066
+ const camId = engine.ecs.queryOne("Camera2D");
3067
+ if (camId === undefined)
3068
+ return;
3069
+ const cam = engine.ecs.getComponent(camId, "Camera2D");
3070
+ if (cam) {
3071
+ cam.followOffsetX = x;
3072
+ cam.followOffsetY = y;
3073
+ }
3074
+ },
3075
+ setPosition(x, y) {
3076
+ const camId = engine.ecs.queryOne("Camera2D");
3077
+ if (camId === undefined)
3078
+ return;
3079
+ const cam = engine.ecs.getComponent(camId, "Camera2D");
3080
+ if (cam) {
3081
+ cam.x = x;
3082
+ cam.y = y;
3083
+ }
3084
+ },
3085
+ setZoom(zoom) {
3086
+ const camId = engine.ecs.queryOne("Camera2D");
3087
+ if (camId === undefined)
3088
+ return;
3089
+ const cam = engine.ecs.getComponent(camId, "Camera2D");
3090
+ if (cam)
3091
+ cam.zoom = zoom;
3092
+ }
3093
+ }), [engine]);
3094
+ }
3095
+ // src/hooks/useSnapshot.ts
3096
+ import { useMemo as useMemo2 } from "react";
3097
+ function useSnapshot() {
3098
+ const engine = useGame();
3099
+ return useMemo2(() => ({
3100
+ save: () => engine.ecs.getSnapshot(),
3101
+ restore: (snapshot) => engine.ecs.restoreSnapshot(snapshot)
3102
+ }), [engine]);
3103
+ }
2630
3104
  // src/hooks/useEntity.ts
2631
- import { useContext as useContext15 } from "react";
3105
+ import { useContext as useContext17 } from "react";
2632
3106
  function useEntity() {
2633
- const id = useContext15(EntityContext);
3107
+ const id = useContext17(EntityContext);
2634
3108
  if (id === null)
2635
3109
  throw new Error("useEntity must be used inside <Entity>");
2636
3110
  return id;
2637
3111
  }
2638
3112
  // src/hooks/useInput.ts
2639
- import { useContext as useContext16 } from "react";
3113
+ import { useContext as useContext18 } from "react";
2640
3114
  function useInput() {
2641
- const engine = useContext16(EngineContext);
3115
+ const engine = useContext18(EngineContext);
2642
3116
  if (!engine)
2643
3117
  throw new Error("useInput must be used inside <Game>");
2644
3118
  return engine.input;
2645
3119
  }
3120
+ // src/hooks/useInputMap.ts
3121
+ import { useMemo as useMemo3 } from "react";
3122
+ function useInputMap(bindings) {
3123
+ const input = useInput();
3124
+ const normalized = useMemo3(() => {
3125
+ const out = {};
3126
+ for (const [action, keys] of Object.entries(bindings)) {
3127
+ out[action] = Array.isArray(keys) ? keys : [keys];
3128
+ }
3129
+ return out;
3130
+ }, []);
3131
+ return useMemo3(() => ({
3132
+ isActionDown: (action) => (normalized[action] ?? []).some((k) => input.isDown(k)),
3133
+ isActionPressed: (action) => (normalized[action] ?? []).some((k) => input.isPressed(k)),
3134
+ isActionReleased: (action) => (normalized[action] ?? []).some((k) => input.isReleased(k))
3135
+ }), [input, normalized]);
3136
+ }
2646
3137
  // src/hooks/useEvents.ts
2647
- import { useContext as useContext17, useEffect as useEffect16 } from "react";
3138
+ import { useContext as useContext19, useEffect as useEffect18 } from "react";
2648
3139
  function useEvents() {
2649
- const engine = useContext17(EngineContext);
3140
+ const engine = useContext19(EngineContext);
2650
3141
  if (!engine)
2651
3142
  throw new Error("useEvents must be used inside <Game>");
2652
3143
  return engine.events;
2653
3144
  }
2654
3145
  function useEvent(event, handler) {
2655
3146
  const events = useEvents();
2656
- useEffect16(() => {
3147
+ useEffect18(() => {
2657
3148
  return events.on(event, handler);
2658
3149
  }, [events, event]);
2659
3150
  }
2660
3151
  // src/hooks/usePlatformerController.ts
2661
- import { useContext as useContext18, useEffect as useEffect17 } from "react";
3152
+ import { useContext as useContext20, useEffect as useEffect19 } from "react";
2662
3153
  function usePlatformerController(entityId, opts = {}) {
2663
- const engine = useContext18(EngineContext);
3154
+ const engine = useContext20(EngineContext);
2664
3155
  const {
2665
3156
  speed = 200,
2666
3157
  jumpForce = -500,
@@ -2668,7 +3159,7 @@ function usePlatformerController(entityId, opts = {}) {
2668
3159
  coyoteTime = 0.08,
2669
3160
  jumpBuffer = 0.08
2670
3161
  } = opts;
2671
- useEffect17(() => {
3162
+ useEffect19(() => {
2672
3163
  const state = { coyoteTimer: 0, jumpBuffer: 0, jumpsLeft: maxJumps };
2673
3164
  const updateFn = (id, world2, input, dt) => {
2674
3165
  if (!world2.hasEntity(id))
@@ -2717,11 +3208,11 @@ function usePlatformerController(entityId, opts = {}) {
2717
3208
  }, []);
2718
3209
  }
2719
3210
  // src/hooks/useTopDownMovement.ts
2720
- import { useContext as useContext19, useEffect as useEffect18 } from "react";
3211
+ import { useContext as useContext21, useEffect as useEffect20 } from "react";
2721
3212
  function useTopDownMovement(entityId, opts = {}) {
2722
- const engine = useContext19(EngineContext);
3213
+ const engine = useContext21(EngineContext);
2723
3214
  const { speed = 200, normalizeDiagonal = true } = opts;
2724
- useEffect18(() => {
3215
+ useEffect20(() => {
2725
3216
  const updateFn = (id, world2, input) => {
2726
3217
  if (!world2.hasEntity(id))
2727
3218
  return;
@@ -2755,15 +3246,28 @@ function createAtlas(names, _columns) {
2755
3246
  return atlas;
2756
3247
  }
2757
3248
  export {
3249
+ useTriggerExit,
3250
+ useTriggerEnter,
2758
3251
  useTopDownMovement,
3252
+ useSnapshot,
2759
3253
  usePlatformerController,
3254
+ useInputMap,
2760
3255
  useInput,
2761
3256
  useGame,
2762
3257
  useEvents,
2763
3258
  useEvent,
2764
3259
  useEntity,
3260
+ useCollisionExit,
3261
+ useCollisionEnter,
3262
+ useCircleExit,
3263
+ useCircleEnter,
3264
+ useCamera,
2765
3265
  tween,
3266
+ raycast,
3267
+ overlapBox,
3268
+ findByTag,
2766
3269
  definePlugin,
3270
+ createInputMap,
2767
3271
  createAtlas,
2768
3272
  World,
2769
3273
  Transform,
@@ -2779,6 +3283,7 @@ export {
2779
3283
  Game,
2780
3284
  Entity,
2781
3285
  Ease,
3286
+ CircleCollider,
2782
3287
  Checkpoint,
2783
3288
  Camera2D,
2784
3289
  BoxCollider,