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/components/Animation.d.ts +3 -1
- package/dist/components/BoxCollider.d.ts +8 -1
- package/dist/components/Camera2D.d.ts +4 -1
- package/dist/components/Checkpoint.d.ts +1 -1
- package/dist/components/CircleCollider.d.ts +11 -0
- package/dist/components/Game.d.ts +10 -2
- package/dist/components/RigidBody.d.ts +5 -1
- package/dist/context.d.ts +5 -2
- package/dist/hooks/useCamera.d.ts +37 -0
- package/dist/hooks/useContact.d.ts +57 -0
- package/dist/hooks/useInputMap.d.ts +25 -0
- package/dist/hooks/useSnapshot.d.ts +42 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +603 -98
- package/package.json +1 -1
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 =
|
|
719
|
-
const dy =
|
|
767
|
+
const dx = tx - cam.x;
|
|
768
|
+
const dy = ty - cam.y;
|
|
720
769
|
if (dx > halfW)
|
|
721
|
-
cam.x =
|
|
770
|
+
cam.x = tx - halfW;
|
|
722
771
|
else if (dx < -halfW)
|
|
723
|
-
cam.x =
|
|
772
|
+
cam.x = tx + halfW;
|
|
724
773
|
if (dy > halfH)
|
|
725
|
-
cam.y =
|
|
774
|
+
cam.y = ty - halfH;
|
|
726
775
|
else if (dy < -halfH)
|
|
727
|
-
cam.y =
|
|
776
|
+
cam.y = ty + halfH;
|
|
728
777
|
} else if (cam.smoothing > 0) {
|
|
729
|
-
cam.x += (
|
|
730
|
-
cam.y += (
|
|
778
|
+
cam.x += (tx - cam.x) * (1 - cam.smoothing);
|
|
779
|
+
cam.y += (ty - cam.y) * (1 - cam.smoothing);
|
|
731
780
|
} else {
|
|
732
|
-
cam.x =
|
|
733
|
-
cam.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
|
-
|
|
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
|
-
|
|
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
|
|
1177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1680
|
-
|
|
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/
|
|
2319
|
+
// src/components/CircleCollider.tsx
|
|
1967
2320
|
import { useEffect as useEffect9, useContext as useContext7 } from "react";
|
|
1968
|
-
function
|
|
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
|
|
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 =
|
|
1995
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2023
|
-
function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
2024
|
-
const engine =
|
|
2025
|
-
const entityId =
|
|
2026
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2439
|
+
import { useEffect as useEffect13, useContext as useContext11 } from "react";
|
|
2053
2440
|
function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
2054
|
-
const engine =
|
|
2055
|
-
const entityId =
|
|
2056
|
-
|
|
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
|
|
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 =
|
|
2155
|
-
const entityId =
|
|
2156
|
-
|
|
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
|
-
|
|
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(
|
|
2261
|
-
|
|
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
|
|
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 =
|
|
2314
|
-
const [spawnedNodes, setSpawnedNodes] =
|
|
2315
|
-
|
|
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
|
|
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 =
|
|
2524
|
-
const entityId =
|
|
2525
|
-
|
|
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
|
-
|
|
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
|
|
3050
|
+
import { useContext as useContext16 } from "react";
|
|
2624
3051
|
function useGame() {
|
|
2625
|
-
const engine =
|
|
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
|
|
3105
|
+
import { useContext as useContext17 } from "react";
|
|
2632
3106
|
function useEntity() {
|
|
2633
|
-
const id =
|
|
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
|
|
3113
|
+
import { useContext as useContext18 } from "react";
|
|
2640
3114
|
function useInput() {
|
|
2641
|
-
const engine =
|
|
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
|
|
3138
|
+
import { useContext as useContext19, useEffect as useEffect18 } from "react";
|
|
2648
3139
|
function useEvents() {
|
|
2649
|
-
const engine =
|
|
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
|
-
|
|
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
|
|
3152
|
+
import { useContext as useContext20, useEffect as useEffect19 } from "react";
|
|
2662
3153
|
function usePlatformerController(entityId, opts = {}) {
|
|
2663
|
-
const engine =
|
|
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
|
-
|
|
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
|
|
3211
|
+
import { useContext as useContext21, useEffect as useEffect20 } from "react";
|
|
2721
3212
|
function useTopDownMovement(entityId, opts = {}) {
|
|
2722
|
-
const engine =
|
|
3213
|
+
const engine = useContext21(EngineContext);
|
|
2723
3214
|
const { speed = 200, normalizeDiagonal = true } = opts;
|
|
2724
|
-
|
|
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,
|