cubeforge 0.0.6 → 0.0.8
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 +8 -1
- package/dist/components/Checkpoint.d.ts +1 -1
- package/dist/components/CircleCollider.d.ts +11 -0
- package/dist/components/Game.d.ts +17 -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 +17 -3
- package/dist/index.js +738 -140
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -90,7 +90,27 @@ class ECSWorld {
|
|
|
90
90
|
hasComponent(id, type) {
|
|
91
91
|
return this.componentIndex.get(id)?.has(type) ?? false;
|
|
92
92
|
}
|
|
93
|
+
flushDirty() {
|
|
94
|
+
if (this.dirtyAll) {
|
|
95
|
+
this.queryCache.clear();
|
|
96
|
+
this.dirtyAll = false;
|
|
97
|
+
this.dirtyTypes.clear();
|
|
98
|
+
} else if (this.dirtyTypes.size > 0) {
|
|
99
|
+
for (const key of this.queryCache.keys()) {
|
|
100
|
+
if (key === "") {
|
|
101
|
+
this.queryCache.delete(key);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const keyTypes = key.split("\x00");
|
|
105
|
+
if (keyTypes.some((t) => this.dirtyTypes.has(t))) {
|
|
106
|
+
this.queryCache.delete(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.dirtyTypes.clear();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
93
112
|
query(...types) {
|
|
113
|
+
this.flushDirty();
|
|
94
114
|
const key = types.slice().sort().join("\x00");
|
|
95
115
|
const cached = this.queryCache.get(key);
|
|
96
116
|
if (cached)
|
|
@@ -106,6 +126,7 @@ class ECSWorld {
|
|
|
106
126
|
return result;
|
|
107
127
|
}
|
|
108
128
|
queryOne(...types) {
|
|
129
|
+
this.flushDirty();
|
|
109
130
|
for (const arch of this.archetypes.values()) {
|
|
110
131
|
if (types.every((t) => arch.types.has(t))) {
|
|
111
132
|
if (arch.entities.length > 0)
|
|
@@ -114,6 +135,23 @@ class ECSWorld {
|
|
|
114
135
|
}
|
|
115
136
|
return;
|
|
116
137
|
}
|
|
138
|
+
findByTag(tag) {
|
|
139
|
+
for (const id of this.query("Tag")) {
|
|
140
|
+
const t = this.getComponent(id, "Tag");
|
|
141
|
+
if (t?.tags.includes(tag))
|
|
142
|
+
return id;
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
findAllByTag(tag) {
|
|
147
|
+
const result = [];
|
|
148
|
+
for (const id of this.query("Tag")) {
|
|
149
|
+
const t = this.getComponent(id, "Tag");
|
|
150
|
+
if (t?.tags.includes(tag))
|
|
151
|
+
result.push(id);
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
117
155
|
setDeterministicSeed(seed) {
|
|
118
156
|
this._rngState = seed >>> 0;
|
|
119
157
|
this._deterministic = true;
|
|
@@ -159,22 +197,6 @@ class ECSWorld {
|
|
|
159
197
|
this.systems.splice(idx, 1);
|
|
160
198
|
}
|
|
161
199
|
update(dt) {
|
|
162
|
-
if (this.dirtyAll) {
|
|
163
|
-
this.queryCache.clear();
|
|
164
|
-
} else if (this.dirtyTypes.size > 0) {
|
|
165
|
-
for (const key of this.queryCache.keys()) {
|
|
166
|
-
if (key === "") {
|
|
167
|
-
this.queryCache.delete(key);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const keyTypes = key.split("\x00");
|
|
171
|
-
if (keyTypes.some((t) => this.dirtyTypes.has(t))) {
|
|
172
|
-
this.queryCache.delete(key);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
this.dirtyAll = false;
|
|
177
|
-
this.dirtyTypes.clear();
|
|
178
200
|
for (const system of this.systems) {
|
|
179
201
|
system.update(this, dt);
|
|
180
202
|
}
|
|
@@ -194,6 +216,16 @@ class ECSWorld {
|
|
|
194
216
|
return this.componentIndex.size;
|
|
195
217
|
}
|
|
196
218
|
}
|
|
219
|
+
// ../core/src/ecs/worldQueries.ts
|
|
220
|
+
function findByTag(world, tag) {
|
|
221
|
+
const results = [];
|
|
222
|
+
for (const id of world.query("Tag")) {
|
|
223
|
+
const t = world.getComponent(id, "Tag");
|
|
224
|
+
if (t?.tags.includes(tag))
|
|
225
|
+
results.push(id);
|
|
226
|
+
}
|
|
227
|
+
return results;
|
|
228
|
+
}
|
|
197
229
|
// ../core/src/loop/gameLoop.ts
|
|
198
230
|
class GameLoop {
|
|
199
231
|
onTick;
|
|
@@ -288,6 +320,7 @@ class EventBus {
|
|
|
288
320
|
// ../core/src/assets/assetManager.ts
|
|
289
321
|
class AssetManager {
|
|
290
322
|
images = new Map;
|
|
323
|
+
imagePromises = new Map;
|
|
291
324
|
audio = new Map;
|
|
292
325
|
audioCtx = null;
|
|
293
326
|
activeSources = new Map;
|
|
@@ -298,21 +331,28 @@ class AssetManager {
|
|
|
298
331
|
return this.audioCtx;
|
|
299
332
|
}
|
|
300
333
|
async loadImage(src) {
|
|
301
|
-
if (this.
|
|
302
|
-
return this.
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
if (this.imagePromises.has(src))
|
|
335
|
+
return this.imagePromises.get(src);
|
|
336
|
+
const promise = (async () => {
|
|
337
|
+
const img = new Image;
|
|
338
|
+
img.src = src;
|
|
339
|
+
try {
|
|
340
|
+
await new Promise((resolve, reject) => {
|
|
341
|
+
img.onload = () => resolve();
|
|
342
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
|
343
|
+
});
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn(`[Cubeforge] Failed to load image: ${src}`);
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
this.images.set(src, img);
|
|
349
|
+
return img;
|
|
350
|
+
})();
|
|
351
|
+
this.imagePromises.set(src, promise);
|
|
352
|
+
return promise;
|
|
353
|
+
}
|
|
354
|
+
async waitForImages() {
|
|
355
|
+
await Promise.allSettled([...this.imagePromises.values()]);
|
|
316
356
|
}
|
|
317
357
|
getImage(src) {
|
|
318
358
|
return this.images.get(src);
|
|
@@ -605,6 +645,24 @@ class InputManager {
|
|
|
605
645
|
return this.keyboard.isReleased(key);
|
|
606
646
|
}
|
|
607
647
|
}
|
|
648
|
+
// ../input/src/inputMap.ts
|
|
649
|
+
function createInputMap(bindings) {
|
|
650
|
+
const normalized = {};
|
|
651
|
+
for (const [action, keys] of Object.entries(bindings)) {
|
|
652
|
+
normalized[action] = Array.isArray(keys) ? keys : [keys];
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
isActionDown(input, action) {
|
|
656
|
+
return (normalized[action] ?? []).some((k) => input.isDown(k));
|
|
657
|
+
},
|
|
658
|
+
isActionPressed(input, action) {
|
|
659
|
+
return (normalized[action] ?? []).some((k) => input.isPressed(k));
|
|
660
|
+
},
|
|
661
|
+
isActionReleased(input, action) {
|
|
662
|
+
return (normalized[action] ?? []).some((k) => input.isReleased(k));
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
608
666
|
// ../renderer/src/canvas2d.ts
|
|
609
667
|
class Canvas2DRenderer {
|
|
610
668
|
canvas;
|
|
@@ -656,6 +714,8 @@ function createCamera2D(opts) {
|
|
|
656
714
|
zoom: 1,
|
|
657
715
|
smoothing: 0,
|
|
658
716
|
background: "#1a1a2e",
|
|
717
|
+
followOffsetX: 0,
|
|
718
|
+
followOffsetY: 0,
|
|
659
719
|
shakeIntensity: 0,
|
|
660
720
|
shakeDuration: 0,
|
|
661
721
|
shakeTimer: 0,
|
|
@@ -712,25 +772,27 @@ class RenderSystem {
|
|
|
712
772
|
if (targetId !== undefined) {
|
|
713
773
|
const targetTransform = world2.getComponent(targetId, "Transform");
|
|
714
774
|
if (targetTransform) {
|
|
775
|
+
const tx = targetTransform.x + (cam.followOffsetX ?? 0);
|
|
776
|
+
const ty = targetTransform.y + (cam.followOffsetY ?? 0);
|
|
715
777
|
if (cam.deadZone) {
|
|
716
778
|
const halfW = cam.deadZone.w / 2;
|
|
717
779
|
const halfH = cam.deadZone.h / 2;
|
|
718
|
-
const dx =
|
|
719
|
-
const dy =
|
|
780
|
+
const dx = tx - cam.x;
|
|
781
|
+
const dy = ty - cam.y;
|
|
720
782
|
if (dx > halfW)
|
|
721
|
-
cam.x =
|
|
783
|
+
cam.x = tx - halfW;
|
|
722
784
|
else if (dx < -halfW)
|
|
723
|
-
cam.x =
|
|
785
|
+
cam.x = tx + halfW;
|
|
724
786
|
if (dy > halfH)
|
|
725
|
-
cam.y =
|
|
787
|
+
cam.y = ty - halfH;
|
|
726
788
|
else if (dy < -halfH)
|
|
727
|
-
cam.y =
|
|
789
|
+
cam.y = ty + halfH;
|
|
728
790
|
} else if (cam.smoothing > 0) {
|
|
729
|
-
cam.x += (
|
|
730
|
-
cam.y += (
|
|
791
|
+
cam.x += (tx - cam.x) * (1 - cam.smoothing);
|
|
792
|
+
cam.y += (ty - cam.y) * (1 - cam.smoothing);
|
|
731
793
|
} else {
|
|
732
|
-
cam.x =
|
|
733
|
-
cam.y =
|
|
794
|
+
cam.x = tx;
|
|
795
|
+
cam.y = ty;
|
|
734
796
|
}
|
|
735
797
|
}
|
|
736
798
|
}
|
|
@@ -764,7 +826,16 @@ class RenderSystem {
|
|
|
764
826
|
anim.timer -= frameDuration;
|
|
765
827
|
anim.currentIndex++;
|
|
766
828
|
if (anim.currentIndex >= anim.frames.length) {
|
|
767
|
-
|
|
829
|
+
if (anim.loop) {
|
|
830
|
+
anim.currentIndex = 0;
|
|
831
|
+
} else {
|
|
832
|
+
anim.currentIndex = anim.frames.length - 1;
|
|
833
|
+
anim.playing = false;
|
|
834
|
+
if (anim.onComplete && !anim._completed) {
|
|
835
|
+
anim._completed = true;
|
|
836
|
+
anim.onComplete();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
768
839
|
}
|
|
769
840
|
}
|
|
770
841
|
sprite.frameIndex = anim.frames[anim.currentIndex];
|
|
@@ -958,6 +1029,8 @@ function createRigidBody(opts) {
|
|
|
958
1029
|
isNearGround: false,
|
|
959
1030
|
bounce: 0,
|
|
960
1031
|
friction: 0.85,
|
|
1032
|
+
lockX: false,
|
|
1033
|
+
lockY: false,
|
|
961
1034
|
...opts
|
|
962
1035
|
};
|
|
963
1036
|
}
|
|
@@ -971,7 +1044,22 @@ function createBoxCollider(width, height, opts) {
|
|
|
971
1044
|
offsetY: 0,
|
|
972
1045
|
isTrigger: false,
|
|
973
1046
|
layer: "default",
|
|
1047
|
+
mask: "*",
|
|
974
1048
|
slope: 0,
|
|
1049
|
+
oneWay: false,
|
|
1050
|
+
...opts
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
// ../physics/src/components/circleCollider.ts
|
|
1054
|
+
function createCircleCollider(radius, opts) {
|
|
1055
|
+
return {
|
|
1056
|
+
type: "CircleCollider",
|
|
1057
|
+
radius,
|
|
1058
|
+
offsetX: 0,
|
|
1059
|
+
offsetY: 0,
|
|
1060
|
+
isTrigger: false,
|
|
1061
|
+
layer: "default",
|
|
1062
|
+
mask: "*",
|
|
975
1063
|
...opts
|
|
976
1064
|
};
|
|
977
1065
|
}
|
|
@@ -1009,12 +1097,27 @@ function getSlopeSurfaceY(st, sc, worldX) {
|
|
|
1009
1097
|
const angleRad = sc.slope * (Math.PI / 180);
|
|
1010
1098
|
return cy - hh + dx * Math.tan(angleRad);
|
|
1011
1099
|
}
|
|
1100
|
+
function maskAllows(mask, layer) {
|
|
1101
|
+
if (mask === "*")
|
|
1102
|
+
return true;
|
|
1103
|
+
return Array.isArray(mask) && mask.includes(layer);
|
|
1104
|
+
}
|
|
1105
|
+
function canInteract(a, b) {
|
|
1106
|
+
return maskAllows(a.mask, b.layer) && maskAllows(b.mask, a.layer);
|
|
1107
|
+
}
|
|
1108
|
+
function pairKey(a, b) {
|
|
1109
|
+
return a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
1110
|
+
}
|
|
1012
1111
|
|
|
1013
1112
|
class PhysicsSystem {
|
|
1014
1113
|
gravity;
|
|
1015
1114
|
events;
|
|
1016
1115
|
accumulator = 0;
|
|
1017
1116
|
FIXED_DT = 1 / 60;
|
|
1117
|
+
activeTriggerPairs = new Map;
|
|
1118
|
+
activeCollisionPairs = new Map;
|
|
1119
|
+
activeCirclePairs = new Map;
|
|
1120
|
+
staticPrevPos = new Map;
|
|
1018
1121
|
constructor(gravity, events) {
|
|
1019
1122
|
this.gravity = gravity;
|
|
1020
1123
|
this.events = events;
|
|
@@ -1055,6 +1158,30 @@ class PhysicsSystem {
|
|
|
1055
1158
|
else
|
|
1056
1159
|
dynamics.push(id);
|
|
1057
1160
|
}
|
|
1161
|
+
for (const [key, [a, b]] of this.activeTriggerPairs) {
|
|
1162
|
+
if (!world2.hasEntity(a) || !world2.hasEntity(b)) {
|
|
1163
|
+
this.events?.emit("triggerExit", { a, b });
|
|
1164
|
+
this.activeTriggerPairs.delete(key);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
for (const [key, [a, b]] of this.activeCollisionPairs) {
|
|
1168
|
+
if (!world2.hasEntity(a) || !world2.hasEntity(b)) {
|
|
1169
|
+
this.events?.emit("collisionExit", { a, b });
|
|
1170
|
+
this.activeCollisionPairs.delete(key);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
const staticDelta = new Map;
|
|
1174
|
+
for (const sid of statics) {
|
|
1175
|
+
const st = world2.getComponent(sid, "Transform");
|
|
1176
|
+
const prev = this.staticPrevPos.get(sid);
|
|
1177
|
+
if (prev)
|
|
1178
|
+
staticDelta.set(sid, { dx: st.x - prev.x, dy: st.y - prev.y });
|
|
1179
|
+
this.staticPrevPos.set(sid, { x: st.x, y: st.y });
|
|
1180
|
+
}
|
|
1181
|
+
for (const sid of this.staticPrevPos.keys()) {
|
|
1182
|
+
if (!world2.hasEntity(sid))
|
|
1183
|
+
this.staticPrevPos.delete(sid);
|
|
1184
|
+
}
|
|
1058
1185
|
const staticGrid = new Map;
|
|
1059
1186
|
for (const sid of statics) {
|
|
1060
1187
|
const st = world2.getComponent(sid, "Transform");
|
|
@@ -1073,7 +1200,12 @@ class PhysicsSystem {
|
|
|
1073
1200
|
const rb = world2.getComponent(id, "RigidBody");
|
|
1074
1201
|
rb.onGround = false;
|
|
1075
1202
|
rb.isNearGround = false;
|
|
1076
|
-
|
|
1203
|
+
if (!rb.lockY)
|
|
1204
|
+
rb.vy += this.gravity * rb.gravityScale * dt;
|
|
1205
|
+
if (rb.lockX)
|
|
1206
|
+
rb.vx = 0;
|
|
1207
|
+
if (rb.lockY)
|
|
1208
|
+
rb.vy = 0;
|
|
1077
1209
|
}
|
|
1078
1210
|
for (const id of dynamics) {
|
|
1079
1211
|
const transform2 = world2.getComponent(id, "Transform");
|
|
@@ -1098,6 +1230,8 @@ class PhysicsSystem {
|
|
|
1098
1230
|
continue;
|
|
1099
1231
|
if (sc.slope !== 0)
|
|
1100
1232
|
continue;
|
|
1233
|
+
if (!canInteract(col, sc))
|
|
1234
|
+
continue;
|
|
1101
1235
|
const ov = getOverlap(getAABB(transform2, col), getAABB(st, sc));
|
|
1102
1236
|
if (!ov)
|
|
1103
1237
|
continue;
|
|
@@ -1130,6 +1264,8 @@ class PhysicsSystem {
|
|
|
1130
1264
|
const sc = world2.getComponent(sid, "BoxCollider");
|
|
1131
1265
|
if (sc.isTrigger)
|
|
1132
1266
|
continue;
|
|
1267
|
+
if (!canInteract(col, sc))
|
|
1268
|
+
continue;
|
|
1133
1269
|
if (sc.slope !== 0) {
|
|
1134
1270
|
const ov2 = getOverlap(getAABB(transform2, col), getAABB(st, sc));
|
|
1135
1271
|
if (!ov2)
|
|
@@ -1150,11 +1286,25 @@ class PhysicsSystem {
|
|
|
1150
1286
|
if (!ov)
|
|
1151
1287
|
continue;
|
|
1152
1288
|
if (Math.abs(ov.y) <= Math.abs(ov.x)) {
|
|
1289
|
+
if (sc.oneWay) {
|
|
1290
|
+
if (ov.y >= 0)
|
|
1291
|
+
continue;
|
|
1292
|
+
const platformTop = st.y + sc.offsetY - sc.height / 2;
|
|
1293
|
+
const prevEntityBottom = transform2.y - rb.vy * dt + col.offsetY + col.height / 2;
|
|
1294
|
+
if (prevEntityBottom > platformTop)
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1153
1297
|
transform2.y += ov.y;
|
|
1154
1298
|
if (ov.y < 0) {
|
|
1155
1299
|
rb.onGround = true;
|
|
1156
1300
|
if (rb.friction < 1)
|
|
1157
1301
|
rb.vx *= rb.friction;
|
|
1302
|
+
const delta = staticDelta.get(sid);
|
|
1303
|
+
if (delta) {
|
|
1304
|
+
transform2.x += delta.dx;
|
|
1305
|
+
if (delta.dy < 0)
|
|
1306
|
+
transform2.y += delta.dy;
|
|
1307
|
+
}
|
|
1158
1308
|
}
|
|
1159
1309
|
rb.vy = rb.bounce > 0 ? -rb.vy * rb.bounce : 0;
|
|
1160
1310
|
}
|
|
@@ -1162,6 +1312,7 @@ class PhysicsSystem {
|
|
|
1162
1312
|
}
|
|
1163
1313
|
}
|
|
1164
1314
|
}
|
|
1315
|
+
const currentCollisionPairs = new Map;
|
|
1165
1316
|
for (let i = 0;i < dynamics.length; i++) {
|
|
1166
1317
|
for (let j = i + 1;j < dynamics.length; j++) {
|
|
1167
1318
|
const ia = dynamics[i];
|
|
@@ -1173,10 +1324,10 @@ class PhysicsSystem {
|
|
|
1173
1324
|
const ov = getOverlap(getAABB(ta, ca), getAABB(tb, cb));
|
|
1174
1325
|
if (!ov)
|
|
1175
1326
|
continue;
|
|
1176
|
-
if (ca
|
|
1177
|
-
|
|
1327
|
+
if (!canInteract(ca, cb))
|
|
1328
|
+
continue;
|
|
1329
|
+
if (ca.isTrigger || cb.isTrigger)
|
|
1178
1330
|
continue;
|
|
1179
|
-
}
|
|
1180
1331
|
const rba = world2.getComponent(ia, "RigidBody");
|
|
1181
1332
|
const rbb = world2.getComponent(ib, "RigidBody");
|
|
1182
1333
|
if (Math.abs(ov.y) <= Math.abs(ov.x)) {
|
|
@@ -1198,9 +1349,22 @@ class PhysicsSystem {
|
|
|
1198
1349
|
ta.y += ov.y / 2;
|
|
1199
1350
|
tb.x -= ov.x / 2;
|
|
1200
1351
|
tb.y -= ov.y / 2;
|
|
1201
|
-
|
|
1352
|
+
const key = pairKey(ia, ib);
|
|
1353
|
+
currentCollisionPairs.set(key, [ia, ib]);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
for (const [key, [a, b]] of currentCollisionPairs) {
|
|
1357
|
+
if (!this.activeCollisionPairs.has(key)) {
|
|
1358
|
+
this.events?.emit("collisionEnter", { a, b });
|
|
1202
1359
|
}
|
|
1360
|
+
this.events?.emit("collision", { a, b });
|
|
1203
1361
|
}
|
|
1362
|
+
for (const [key, [a, b]] of this.activeCollisionPairs) {
|
|
1363
|
+
if (!currentCollisionPairs.has(key)) {
|
|
1364
|
+
this.events?.emit("collisionExit", { a, b });
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
this.activeCollisionPairs = currentCollisionPairs;
|
|
1204
1368
|
for (const id of dynamics) {
|
|
1205
1369
|
const rb = world2.getComponent(id, "RigidBody");
|
|
1206
1370
|
if (rb.onGround) {
|
|
@@ -1230,6 +1394,8 @@ class PhysicsSystem {
|
|
|
1230
1394
|
const sc = world2.getComponent(sid, "BoxCollider");
|
|
1231
1395
|
if (sc.isTrigger)
|
|
1232
1396
|
continue;
|
|
1397
|
+
if (!canInteract(col, sc))
|
|
1398
|
+
continue;
|
|
1233
1399
|
const ov = getOverlap(probeAABB, getAABB(st, sc));
|
|
1234
1400
|
if (ov && Math.abs(ov.y) <= Math.abs(ov.x) && ov.y < 0) {
|
|
1235
1401
|
rb.isNearGround = true;
|
|
@@ -1238,7 +1404,194 @@ class PhysicsSystem {
|
|
|
1238
1404
|
}
|
|
1239
1405
|
}
|
|
1240
1406
|
}
|
|
1407
|
+
const allWithCollider = world2.query("Transform", "BoxCollider");
|
|
1408
|
+
const currentTriggerPairs = new Map;
|
|
1409
|
+
for (let i = 0;i < allWithCollider.length; i++) {
|
|
1410
|
+
for (let j = i + 1;j < allWithCollider.length; j++) {
|
|
1411
|
+
const ia = allWithCollider[i];
|
|
1412
|
+
const ib = allWithCollider[j];
|
|
1413
|
+
const ca = world2.getComponent(ia, "BoxCollider");
|
|
1414
|
+
const cb = world2.getComponent(ib, "BoxCollider");
|
|
1415
|
+
if (!ca.isTrigger && !cb.isTrigger)
|
|
1416
|
+
continue;
|
|
1417
|
+
if (!canInteract(ca, cb))
|
|
1418
|
+
continue;
|
|
1419
|
+
const ta = world2.getComponent(ia, "Transform");
|
|
1420
|
+
const tb = world2.getComponent(ib, "Transform");
|
|
1421
|
+
const ov = getOverlap(getAABB(ta, ca), getAABB(tb, cb));
|
|
1422
|
+
if (!ov)
|
|
1423
|
+
continue;
|
|
1424
|
+
const key = pairKey(ia, ib);
|
|
1425
|
+
currentTriggerPairs.set(key, [ia, ib]);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
for (const [key, [a, b]] of currentTriggerPairs) {
|
|
1429
|
+
if (!this.activeTriggerPairs.has(key)) {
|
|
1430
|
+
this.events?.emit("triggerEnter", { a, b });
|
|
1431
|
+
}
|
|
1432
|
+
this.events?.emit("trigger", { a, b });
|
|
1433
|
+
}
|
|
1434
|
+
for (const [key, [a, b]] of this.activeTriggerPairs) {
|
|
1435
|
+
if (!currentTriggerPairs.has(key)) {
|
|
1436
|
+
this.events?.emit("triggerExit", { a, b });
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
this.activeTriggerPairs = currentTriggerPairs;
|
|
1440
|
+
const allCircles = world2.query("Transform", "CircleCollider");
|
|
1441
|
+
if (allCircles.length > 0) {
|
|
1442
|
+
const currentCirclePairs = new Map;
|
|
1443
|
+
for (let i = 0;i < allCircles.length; i++) {
|
|
1444
|
+
for (let j = i + 1;j < allCircles.length; j++) {
|
|
1445
|
+
const ia = allCircles[i];
|
|
1446
|
+
const ib = allCircles[j];
|
|
1447
|
+
const ca = world2.getComponent(ia, "CircleCollider");
|
|
1448
|
+
const cb = world2.getComponent(ib, "CircleCollider");
|
|
1449
|
+
if (!maskAllows(ca.mask, cb.layer) || !maskAllows(cb.mask, ca.layer))
|
|
1450
|
+
continue;
|
|
1451
|
+
const ta = world2.getComponent(ia, "Transform");
|
|
1452
|
+
const tb = world2.getComponent(ib, "Transform");
|
|
1453
|
+
const dx = ta.x + ca.offsetX - (tb.x + cb.offsetX);
|
|
1454
|
+
const dy = ta.y + ca.offsetY - (tb.y + cb.offsetY);
|
|
1455
|
+
if (dx * dx + dy * dy < (ca.radius + cb.radius) ** 2) {
|
|
1456
|
+
currentCirclePairs.set(pairKey(ia, ib), [ia, ib]);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const allBoxes = world2.query("Transform", "BoxCollider");
|
|
1461
|
+
for (const cid of allCircles) {
|
|
1462
|
+
const cc = world2.getComponent(cid, "CircleCollider");
|
|
1463
|
+
const ct = world2.getComponent(cid, "Transform");
|
|
1464
|
+
const cx = ct.x + cc.offsetX;
|
|
1465
|
+
const cy = ct.y + cc.offsetY;
|
|
1466
|
+
for (const bid of allBoxes) {
|
|
1467
|
+
if (bid === cid)
|
|
1468
|
+
continue;
|
|
1469
|
+
const bc = world2.getComponent(bid, "BoxCollider");
|
|
1470
|
+
if (!maskAllows(cc.mask, bc.layer) || !maskAllows(bc.mask, cc.layer))
|
|
1471
|
+
continue;
|
|
1472
|
+
const bt = world2.getComponent(bid, "Transform");
|
|
1473
|
+
const bx = bt.x + bc.offsetX;
|
|
1474
|
+
const by = bt.y + bc.offsetY;
|
|
1475
|
+
const nearX = Math.max(bx - bc.width / 2, Math.min(cx, bx + bc.width / 2));
|
|
1476
|
+
const nearY = Math.max(by - bc.height / 2, Math.min(cy, by + bc.height / 2));
|
|
1477
|
+
const dx = cx - nearX;
|
|
1478
|
+
const dy = cy - nearY;
|
|
1479
|
+
if (dx * dx + dy * dy < cc.radius * cc.radius) {
|
|
1480
|
+
currentCirclePairs.set(pairKey(cid, bid), [cid, bid]);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
for (const [key, [a, b]] of currentCirclePairs) {
|
|
1485
|
+
if (!this.activeCirclePairs.has(key)) {
|
|
1486
|
+
this.events?.emit("circleEnter", { a, b });
|
|
1487
|
+
}
|
|
1488
|
+
this.events?.emit("circle", { a, b });
|
|
1489
|
+
}
|
|
1490
|
+
for (const [key, [a, b]] of this.activeCirclePairs) {
|
|
1491
|
+
if (!currentCirclePairs.has(key)) {
|
|
1492
|
+
this.events?.emit("circleExit", { a, b });
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
this.activeCirclePairs = currentCirclePairs;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
// ../physics/src/queries.ts
|
|
1500
|
+
function passesFilter(world2, id, col, opts) {
|
|
1501
|
+
if (opts.exclude?.includes(id))
|
|
1502
|
+
return false;
|
|
1503
|
+
if (opts.layer && col.layer !== opts.layer)
|
|
1504
|
+
return false;
|
|
1505
|
+
if (opts.tag) {
|
|
1506
|
+
const t = world2.getComponent(id, "Tag");
|
|
1507
|
+
if (!t?.tags.includes(opts.tag))
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
function overlapBox(world2, cx, cy, hw, hh, opts = {}) {
|
|
1513
|
+
const results = [];
|
|
1514
|
+
for (const id of world2.query("Transform", "BoxCollider")) {
|
|
1515
|
+
const t = world2.getComponent(id, "Transform");
|
|
1516
|
+
const c = world2.getComponent(id, "BoxCollider");
|
|
1517
|
+
if (!passesFilter(world2, id, c, opts))
|
|
1518
|
+
continue;
|
|
1519
|
+
const ecx = t.x + c.offsetX;
|
|
1520
|
+
const ecy = t.y + c.offsetY;
|
|
1521
|
+
const ehw = c.width / 2;
|
|
1522
|
+
const ehh = c.height / 2;
|
|
1523
|
+
if (Math.abs(ecx - cx) < hw + ehw && Math.abs(ecy - cy) < hh + ehh) {
|
|
1524
|
+
results.push(id);
|
|
1525
|
+
}
|
|
1241
1526
|
}
|
|
1527
|
+
return results;
|
|
1528
|
+
}
|
|
1529
|
+
function raycast(world2, origin, direction, maxDistance, opts = {}) {
|
|
1530
|
+
const len = Math.hypot(direction.x, direction.y);
|
|
1531
|
+
if (len === 0)
|
|
1532
|
+
return null;
|
|
1533
|
+
const dx = direction.x / len;
|
|
1534
|
+
const dy = direction.y / len;
|
|
1535
|
+
let closest = null;
|
|
1536
|
+
for (const id of world2.query("Transform", "BoxCollider")) {
|
|
1537
|
+
const t = world2.getComponent(id, "Transform");
|
|
1538
|
+
const c = world2.getComponent(id, "BoxCollider");
|
|
1539
|
+
if (!opts.includeTriggers && c.isTrigger)
|
|
1540
|
+
continue;
|
|
1541
|
+
if (!passesFilter(world2, id, c, opts))
|
|
1542
|
+
continue;
|
|
1543
|
+
const cx = t.x + c.offsetX;
|
|
1544
|
+
const cy = t.y + c.offsetY;
|
|
1545
|
+
const hw = c.width / 2;
|
|
1546
|
+
const hh = c.height / 2;
|
|
1547
|
+
const left = cx - hw;
|
|
1548
|
+
const right = cx + hw;
|
|
1549
|
+
const top = cy - hh;
|
|
1550
|
+
const bottom = cy + hh;
|
|
1551
|
+
let tmin = -Infinity;
|
|
1552
|
+
let tmax = Infinity;
|
|
1553
|
+
if (dx !== 0) {
|
|
1554
|
+
const t1 = (left - origin.x) / dx;
|
|
1555
|
+
const t2 = (right - origin.x) / dx;
|
|
1556
|
+
tmin = Math.max(tmin, Math.min(t1, t2));
|
|
1557
|
+
tmax = Math.min(tmax, Math.max(t1, t2));
|
|
1558
|
+
} else if (origin.x < left || origin.x > right) {
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
if (dy !== 0) {
|
|
1562
|
+
const t1 = (top - origin.y) / dy;
|
|
1563
|
+
const t2 = (bottom - origin.y) / dy;
|
|
1564
|
+
tmin = Math.max(tmin, Math.min(t1, t2));
|
|
1565
|
+
tmax = Math.min(tmax, Math.max(t1, t2));
|
|
1566
|
+
} else if (origin.y < top || origin.y > bottom) {
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (tmax < 0 || tmin > tmax || tmin > maxDistance)
|
|
1570
|
+
continue;
|
|
1571
|
+
const dist = Math.max(0, tmin);
|
|
1572
|
+
if (closest && dist >= closest.distance)
|
|
1573
|
+
continue;
|
|
1574
|
+
const hitX = origin.x + dx * tmin;
|
|
1575
|
+
const hitY = origin.y + dy * tmin;
|
|
1576
|
+
let nx = 0;
|
|
1577
|
+
let ny = 0;
|
|
1578
|
+
const edgeEps = 0.001;
|
|
1579
|
+
if (Math.abs(hitX - left) < edgeEps)
|
|
1580
|
+
nx = -1;
|
|
1581
|
+
else if (Math.abs(hitX - right) < edgeEps)
|
|
1582
|
+
nx = 1;
|
|
1583
|
+
else if (Math.abs(hitY - top) < edgeEps)
|
|
1584
|
+
ny = -1;
|
|
1585
|
+
else if (Math.abs(hitY - bottom) < edgeEps)
|
|
1586
|
+
ny = 1;
|
|
1587
|
+
closest = {
|
|
1588
|
+
entityId: id,
|
|
1589
|
+
distance: dist,
|
|
1590
|
+
point: { x: hitX, y: hitY },
|
|
1591
|
+
normal: { x: nx, y: ny }
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
return closest;
|
|
1242
1595
|
}
|
|
1243
1596
|
// src/context.ts
|
|
1244
1597
|
import { createContext } from "react";
|
|
@@ -1655,8 +2008,10 @@ function Game({
|
|
|
1655
2008
|
scale = "none",
|
|
1656
2009
|
deterministic = false,
|
|
1657
2010
|
seed = 0,
|
|
2011
|
+
asyncAssets = false,
|
|
1658
2012
|
onReady,
|
|
1659
2013
|
plugins,
|
|
2014
|
+
renderer: CustomRenderer,
|
|
1660
2015
|
style,
|
|
1661
2016
|
className,
|
|
1662
2017
|
children
|
|
@@ -1664,6 +2019,7 @@ function Game({
|
|
|
1664
2019
|
const canvasRef = useRef(null);
|
|
1665
2020
|
const wrapperRef = useRef(null);
|
|
1666
2021
|
const [engine, setEngine] = useState2(null);
|
|
2022
|
+
const [assetsReady, setAssetsReady] = useState2(asyncAssets);
|
|
1667
2023
|
const devtoolsHandle = useRef({ buffer: [] });
|
|
1668
2024
|
useEffect2(() => {
|
|
1669
2025
|
const canvas = canvasRef.current;
|
|
@@ -1671,13 +2027,21 @@ function Game({
|
|
|
1671
2027
|
if (deterministic)
|
|
1672
2028
|
ecs.setDeterministicSeed(seed);
|
|
1673
2029
|
const input = new InputManager;
|
|
1674
|
-
const renderer = new Canvas2DRenderer(canvas);
|
|
1675
2030
|
const events = new EventBus;
|
|
1676
2031
|
const assets = new AssetManager;
|
|
1677
2032
|
const physics = new PhysicsSystem(gravity, events);
|
|
1678
2033
|
const entityIds = new Map;
|
|
1679
|
-
|
|
1680
|
-
|
|
2034
|
+
let canvas2d2;
|
|
2035
|
+
let builtinRenderSystem;
|
|
2036
|
+
let renderSystem2;
|
|
2037
|
+
if (CustomRenderer) {
|
|
2038
|
+
renderSystem2 = new CustomRenderer(canvas, entityIds);
|
|
2039
|
+
} else {
|
|
2040
|
+
canvas2d2 = new Canvas2DRenderer(canvas);
|
|
2041
|
+
builtinRenderSystem = new RenderSystem(canvas2d2, entityIds);
|
|
2042
|
+
renderSystem2 = builtinRenderSystem;
|
|
2043
|
+
}
|
|
2044
|
+
const debugSystem = debug && canvas2d2 ? new DebugSystem(canvas2d2) : null;
|
|
1681
2045
|
ecs.addSystem(new ScriptSystem(input));
|
|
1682
2046
|
ecs.addSystem(physics);
|
|
1683
2047
|
ecs.addSystem(renderSystem2);
|
|
@@ -1699,7 +2063,7 @@ function Game({
|
|
|
1699
2063
|
handle.onFrame?.();
|
|
1700
2064
|
}
|
|
1701
2065
|
});
|
|
1702
|
-
const state = { ecs, input, renderer, physics, events, assets, loop, canvas, entityIds };
|
|
2066
|
+
const state = { ecs, input, renderer: canvas2d2, renderSystem: builtinRenderSystem, physics, events, assets, loop, canvas, entityIds };
|
|
1703
2067
|
setEngine(state);
|
|
1704
2068
|
if (plugins) {
|
|
1705
2069
|
for (const plugin2 of plugins) {
|
|
@@ -1709,7 +2073,6 @@ function Game({
|
|
|
1709
2073
|
plugin2.onInit?.(state);
|
|
1710
2074
|
}
|
|
1711
2075
|
}
|
|
1712
|
-
loop.start();
|
|
1713
2076
|
onReady?.({
|
|
1714
2077
|
pause: () => loop.pause(),
|
|
1715
2078
|
resume: () => loop.resume(),
|
|
@@ -1743,6 +2106,25 @@ function Game({
|
|
|
1743
2106
|
resizeObserver?.disconnect();
|
|
1744
2107
|
};
|
|
1745
2108
|
}, []);
|
|
2109
|
+
useEffect2(() => {
|
|
2110
|
+
if (!engine)
|
|
2111
|
+
return;
|
|
2112
|
+
let cancelled = false;
|
|
2113
|
+
if (asyncAssets) {
|
|
2114
|
+
engine.loop.start();
|
|
2115
|
+
setAssetsReady(true);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
engine.assets.waitForImages().then(() => {
|
|
2119
|
+
if (!cancelled) {
|
|
2120
|
+
engine.loop.start();
|
|
2121
|
+
setAssetsReady(true);
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
return () => {
|
|
2125
|
+
cancelled = true;
|
|
2126
|
+
};
|
|
2127
|
+
}, [engine]);
|
|
1746
2128
|
useEffect2(() => {
|
|
1747
2129
|
engine?.physics.setGravity(gravity);
|
|
1748
2130
|
}, [gravity, engine]);
|
|
@@ -1752,21 +2134,71 @@ function Game({
|
|
|
1752
2134
|
imageRendering: scale === "pixel" ? "pixelated" : undefined,
|
|
1753
2135
|
...style
|
|
1754
2136
|
};
|
|
1755
|
-
const wrapperStyle =
|
|
2137
|
+
const wrapperStyle = {
|
|
2138
|
+
position: "relative",
|
|
2139
|
+
display: "inline-block",
|
|
2140
|
+
...scale === "contain" ? { width, height, overflow: "visible" } : {}
|
|
2141
|
+
};
|
|
1756
2142
|
return /* @__PURE__ */ jsxDEV2(EngineContext.Provider, {
|
|
1757
2143
|
value: engine,
|
|
1758
2144
|
children: [
|
|
1759
2145
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
1760
2146
|
ref: wrapperRef,
|
|
1761
2147
|
style: wrapperStyle,
|
|
1762
|
-
children:
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2148
|
+
children: [
|
|
2149
|
+
/* @__PURE__ */ jsxDEV2("canvas", {
|
|
2150
|
+
ref: canvasRef,
|
|
2151
|
+
width,
|
|
2152
|
+
height,
|
|
2153
|
+
style: canvasStyle,
|
|
2154
|
+
className
|
|
2155
|
+
}, undefined, false, undefined, this),
|
|
2156
|
+
!assetsReady && /* @__PURE__ */ jsxDEV2("div", {
|
|
2157
|
+
style: {
|
|
2158
|
+
position: "absolute",
|
|
2159
|
+
inset: 0,
|
|
2160
|
+
display: "flex",
|
|
2161
|
+
flexDirection: "column",
|
|
2162
|
+
alignItems: "center",
|
|
2163
|
+
justifyContent: "center",
|
|
2164
|
+
background: "#0a0a0f",
|
|
2165
|
+
pointerEvents: "none"
|
|
2166
|
+
},
|
|
2167
|
+
children: [
|
|
2168
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
2169
|
+
style: { display: "flex", gap: 6, marginBottom: 12 },
|
|
2170
|
+
children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxDEV2("div", {
|
|
2171
|
+
style: {
|
|
2172
|
+
width: 8,
|
|
2173
|
+
height: 8,
|
|
2174
|
+
borderRadius: "50%",
|
|
2175
|
+
background: "#4fc3f7",
|
|
2176
|
+
animation: "cubeforge-loading-dot 1.2s ease-in-out infinite",
|
|
2177
|
+
animationDelay: `${i * 0.2}s`
|
|
2178
|
+
}
|
|
2179
|
+
}, i, false, undefined, this))
|
|
2180
|
+
}, undefined, false, undefined, this),
|
|
2181
|
+
/* @__PURE__ */ jsxDEV2("span", {
|
|
2182
|
+
style: {
|
|
2183
|
+
fontFamily: "monospace",
|
|
2184
|
+
fontSize: 11,
|
|
2185
|
+
letterSpacing: 3,
|
|
2186
|
+
color: "#37474f"
|
|
2187
|
+
},
|
|
2188
|
+
children: "LOADING"
|
|
2189
|
+
}, undefined, false, undefined, this),
|
|
2190
|
+
/* @__PURE__ */ jsxDEV2("style", {
|
|
2191
|
+
children: `
|
|
2192
|
+
@keyframes cubeforge-loading-dot {
|
|
2193
|
+
0%, 80%, 100% { transform: scale(0.6); opacity: 0.3; }
|
|
2194
|
+
40% { transform: scale(1); opacity: 1; }
|
|
2195
|
+
}
|
|
2196
|
+
`
|
|
2197
|
+
}, undefined, false, undefined, this)
|
|
2198
|
+
]
|
|
2199
|
+
}, undefined, true, undefined, this)
|
|
2200
|
+
]
|
|
2201
|
+
}, undefined, true, undefined, this),
|
|
1770
2202
|
engine && children,
|
|
1771
2203
|
engine && devtools && /* @__PURE__ */ jsxDEV2(DevToolsOverlay, {
|
|
1772
2204
|
handle: devtoolsHandle.current,
|
|
@@ -1898,7 +2330,10 @@ function Sprite({
|
|
|
1898
2330
|
});
|
|
1899
2331
|
engine.ecs.addComponent(entityId, comp);
|
|
1900
2332
|
if (src) {
|
|
1901
|
-
|
|
2333
|
+
const viteEnv = import.meta.env;
|
|
2334
|
+
const base = (viteEnv?.BASE_URL ?? "/").replace(/\/$/, "");
|
|
2335
|
+
const resolvedSrc = base && src.startsWith("/") ? base + src : src;
|
|
2336
|
+
engine.assets.loadImage(resolvedSrc).then((img) => {
|
|
1902
2337
|
const c = engine.ecs.getComponent(entityId, "Sprite");
|
|
1903
2338
|
if (c)
|
|
1904
2339
|
c.image = img;
|
|
@@ -1927,12 +2362,14 @@ function RigidBody({
|
|
|
1927
2362
|
bounce = 0,
|
|
1928
2363
|
friction = 0.85,
|
|
1929
2364
|
vx = 0,
|
|
1930
|
-
vy = 0
|
|
2365
|
+
vy = 0,
|
|
2366
|
+
lockX = false,
|
|
2367
|
+
lockY = false
|
|
1931
2368
|
}) {
|
|
1932
2369
|
const engine = useContext5(EngineContext);
|
|
1933
2370
|
const entityId = useContext5(EntityContext);
|
|
1934
2371
|
useEffect7(() => {
|
|
1935
|
-
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy }));
|
|
2372
|
+
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY }));
|
|
1936
2373
|
return () => engine.ecs.removeComponent(entityId, "RigidBody");
|
|
1937
2374
|
}, []);
|
|
1938
2375
|
return null;
|
|
@@ -1945,12 +2382,14 @@ function BoxCollider({
|
|
|
1945
2382
|
offsetX = 0,
|
|
1946
2383
|
offsetY = 0,
|
|
1947
2384
|
isTrigger = false,
|
|
1948
|
-
layer = "default"
|
|
2385
|
+
layer = "default",
|
|
2386
|
+
mask = "*",
|
|
2387
|
+
oneWay = false
|
|
1949
2388
|
}) {
|
|
1950
2389
|
const engine = useContext6(EngineContext);
|
|
1951
2390
|
const entityId = useContext6(EntityContext);
|
|
1952
2391
|
useEffect8(() => {
|
|
1953
|
-
engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer }));
|
|
2392
|
+
engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer, mask, oneWay }));
|
|
1954
2393
|
const checkId = setTimeout(() => {
|
|
1955
2394
|
if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
|
|
1956
2395
|
console.warn(`[Cubeforge] BoxCollider on entity ${entityId} has no Transform. Physics requires Transform.`);
|
|
@@ -1963,12 +2402,30 @@ function BoxCollider({
|
|
|
1963
2402
|
}, []);
|
|
1964
2403
|
return null;
|
|
1965
2404
|
}
|
|
1966
|
-
// src/components/
|
|
2405
|
+
// src/components/CircleCollider.tsx
|
|
1967
2406
|
import { useEffect as useEffect9, useContext as useContext7 } from "react";
|
|
1968
|
-
function
|
|
2407
|
+
function CircleCollider({
|
|
2408
|
+
radius,
|
|
2409
|
+
offsetX = 0,
|
|
2410
|
+
offsetY = 0,
|
|
2411
|
+
isTrigger = false,
|
|
2412
|
+
layer = "default",
|
|
2413
|
+
mask = "*"
|
|
2414
|
+
}) {
|
|
1969
2415
|
const engine = useContext7(EngineContext);
|
|
1970
2416
|
const entityId = useContext7(EntityContext);
|
|
1971
2417
|
useEffect9(() => {
|
|
2418
|
+
engine.ecs.addComponent(entityId, createCircleCollider(radius, { offsetX, offsetY, isTrigger, layer, mask }));
|
|
2419
|
+
return () => engine.ecs.removeComponent(entityId, "CircleCollider");
|
|
2420
|
+
}, []);
|
|
2421
|
+
return null;
|
|
2422
|
+
}
|
|
2423
|
+
// src/components/Script.tsx
|
|
2424
|
+
import { useEffect as useEffect10, useContext as useContext8 } from "react";
|
|
2425
|
+
function Script({ init, update }) {
|
|
2426
|
+
const engine = useContext8(EngineContext);
|
|
2427
|
+
const entityId = useContext8(EntityContext);
|
|
2428
|
+
useEffect10(() => {
|
|
1972
2429
|
if (init) {
|
|
1973
2430
|
try {
|
|
1974
2431
|
init(entityId, engine.ecs);
|
|
@@ -1982,29 +2439,37 @@ function Script({ init, update }) {
|
|
|
1982
2439
|
return null;
|
|
1983
2440
|
}
|
|
1984
2441
|
// src/components/Camera2D.tsx
|
|
1985
|
-
import { useEffect as
|
|
2442
|
+
import { useEffect as useEffect11, useContext as useContext9 } from "react";
|
|
1986
2443
|
function Camera2D({
|
|
1987
2444
|
followEntity,
|
|
2445
|
+
x = 0,
|
|
2446
|
+
y = 0,
|
|
1988
2447
|
zoom = 1,
|
|
1989
2448
|
smoothing = 0,
|
|
1990
2449
|
background = "#1a1a2e",
|
|
1991
2450
|
bounds,
|
|
1992
|
-
deadZone
|
|
2451
|
+
deadZone,
|
|
2452
|
+
followOffsetX = 0,
|
|
2453
|
+
followOffsetY = 0
|
|
1993
2454
|
}) {
|
|
1994
|
-
const engine =
|
|
1995
|
-
|
|
2455
|
+
const engine = useContext9(EngineContext);
|
|
2456
|
+
useEffect11(() => {
|
|
1996
2457
|
const entityId = engine.ecs.createEntity();
|
|
1997
2458
|
engine.ecs.addComponent(entityId, createCamera2D({
|
|
1998
2459
|
followEntityId: followEntity,
|
|
2460
|
+
x,
|
|
2461
|
+
y,
|
|
1999
2462
|
zoom,
|
|
2000
2463
|
smoothing,
|
|
2001
2464
|
background,
|
|
2002
2465
|
bounds,
|
|
2003
|
-
deadZone
|
|
2466
|
+
deadZone,
|
|
2467
|
+
followOffsetX,
|
|
2468
|
+
followOffsetY
|
|
2004
2469
|
}));
|
|
2005
2470
|
return () => engine.ecs.destroyEntity(entityId);
|
|
2006
2471
|
}, []);
|
|
2007
|
-
|
|
2472
|
+
useEffect11(() => {
|
|
2008
2473
|
const camId = engine.ecs.queryOne("Camera2D");
|
|
2009
2474
|
if (camId === undefined)
|
|
2010
2475
|
return;
|
|
@@ -2015,15 +2480,17 @@ function Camera2D({
|
|
|
2015
2480
|
cam.background = background;
|
|
2016
2481
|
cam.bounds = bounds;
|
|
2017
2482
|
cam.deadZone = deadZone;
|
|
2018
|
-
|
|
2483
|
+
cam.followOffsetX = followOffsetX;
|
|
2484
|
+
cam.followOffsetY = followOffsetY;
|
|
2485
|
+
}, [followEntity, zoom, smoothing, background, bounds, deadZone, followOffsetX, followOffsetY, engine]);
|
|
2019
2486
|
return null;
|
|
2020
2487
|
}
|
|
2021
2488
|
// 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
|
-
|
|
2489
|
+
import { useEffect as useEffect12, useContext as useContext10 } from "react";
|
|
2490
|
+
function Animation({ frames, fps = 12, loop = true, playing = true, onComplete }) {
|
|
2491
|
+
const engine = useContext10(EngineContext);
|
|
2492
|
+
const entityId = useContext10(EntityContext);
|
|
2493
|
+
useEffect12(() => {
|
|
2027
2494
|
const state = {
|
|
2028
2495
|
type: "AnimationState",
|
|
2029
2496
|
frames,
|
|
@@ -2031,29 +2498,39 @@ function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
|
2031
2498
|
loop,
|
|
2032
2499
|
playing,
|
|
2033
2500
|
currentIndex: 0,
|
|
2034
|
-
timer: 0
|
|
2501
|
+
timer: 0,
|
|
2502
|
+
_completed: false,
|
|
2503
|
+
onComplete
|
|
2035
2504
|
};
|
|
2036
2505
|
engine.ecs.addComponent(entityId, state);
|
|
2037
2506
|
return () => {
|
|
2038
2507
|
engine.ecs.removeComponent(entityId, "AnimationState");
|
|
2039
2508
|
};
|
|
2040
2509
|
}, []);
|
|
2041
|
-
|
|
2510
|
+
useEffect12(() => {
|
|
2042
2511
|
const anim = engine.ecs.getComponent(entityId, "AnimationState");
|
|
2043
2512
|
if (!anim)
|
|
2044
2513
|
return;
|
|
2514
|
+
const wasFramesChanged = anim.frames !== frames;
|
|
2045
2515
|
anim.playing = playing;
|
|
2046
2516
|
anim.fps = fps;
|
|
2047
2517
|
anim.loop = loop;
|
|
2048
|
-
|
|
2518
|
+
anim.onComplete = onComplete;
|
|
2519
|
+
if (wasFramesChanged) {
|
|
2520
|
+
anim.frames = frames;
|
|
2521
|
+
anim.currentIndex = 0;
|
|
2522
|
+
anim.timer = 0;
|
|
2523
|
+
anim._completed = false;
|
|
2524
|
+
}
|
|
2525
|
+
}, [playing, fps, loop, frames, onComplete, engine, entityId]);
|
|
2049
2526
|
return null;
|
|
2050
2527
|
}
|
|
2051
2528
|
// src/components/SquashStretch.tsx
|
|
2052
|
-
import { useEffect as
|
|
2529
|
+
import { useEffect as useEffect13, useContext as useContext11 } from "react";
|
|
2053
2530
|
function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
2054
|
-
const engine =
|
|
2055
|
-
const entityId =
|
|
2056
|
-
|
|
2531
|
+
const engine = useContext11(EngineContext);
|
|
2532
|
+
const entityId = useContext11(EntityContext);
|
|
2533
|
+
useEffect13(() => {
|
|
2057
2534
|
engine.ecs.addComponent(entityId, {
|
|
2058
2535
|
type: "SquashStretch",
|
|
2059
2536
|
intensity,
|
|
@@ -2066,7 +2543,7 @@ function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
|
2066
2543
|
return null;
|
|
2067
2544
|
}
|
|
2068
2545
|
// src/components/ParticleEmitter.tsx
|
|
2069
|
-
import { useEffect as
|
|
2546
|
+
import { useEffect as useEffect14, useContext as useContext12 } from "react";
|
|
2070
2547
|
|
|
2071
2548
|
// src/components/particlePresets.ts
|
|
2072
2549
|
var PARTICLE_PRESETS = {
|
|
@@ -2151,9 +2628,9 @@ function ParticleEmitter({
|
|
|
2151
2628
|
const resolvedColor = color ?? presetConfig.color ?? "#ffffff";
|
|
2152
2629
|
const resolvedGravity = gravity ?? presetConfig.gravity ?? 200;
|
|
2153
2630
|
const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
|
|
2154
|
-
const engine =
|
|
2155
|
-
const entityId =
|
|
2156
|
-
|
|
2631
|
+
const engine = useContext12(EngineContext);
|
|
2632
|
+
const entityId = useContext12(EntityContext);
|
|
2633
|
+
useEffect14(() => {
|
|
2157
2634
|
engine.ecs.addComponent(entityId, {
|
|
2158
2635
|
type: "ParticlePool",
|
|
2159
2636
|
particles: [],
|
|
@@ -2171,7 +2648,7 @@ function ParticleEmitter({
|
|
|
2171
2648
|
});
|
|
2172
2649
|
return () => engine.ecs.removeComponent(entityId, "ParticlePool");
|
|
2173
2650
|
}, []);
|
|
2174
|
-
|
|
2651
|
+
useEffect14(() => {
|
|
2175
2652
|
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
2176
2653
|
if (!pool)
|
|
2177
2654
|
return;
|
|
@@ -2229,8 +2706,70 @@ function MovingPlatform({
|
|
|
2229
2706
|
]
|
|
2230
2707
|
}, undefined, true, undefined, this);
|
|
2231
2708
|
}
|
|
2709
|
+
// src/components/Checkpoint.tsx
|
|
2710
|
+
import { useState as useState4 } from "react";
|
|
2711
|
+
|
|
2712
|
+
// src/hooks/useContact.ts
|
|
2713
|
+
import { useContext as useContext13, useEffect as useEffect15 } from "react";
|
|
2714
|
+
function useContactEvent(eventName, handler, opts) {
|
|
2715
|
+
const engine = useContext13(EngineContext);
|
|
2716
|
+
const entityId = useContext13(EntityContext);
|
|
2717
|
+
if (!engine)
|
|
2718
|
+
throw new Error(`${eventName} hook must be used inside <Game>`);
|
|
2719
|
+
if (entityId === null)
|
|
2720
|
+
throw new Error(`${eventName} hook must be used inside <Entity>`);
|
|
2721
|
+
useEffect15(() => {
|
|
2722
|
+
return engine.events.on(eventName, ({ a, b }) => {
|
|
2723
|
+
const isA = a === entityId;
|
|
2724
|
+
const isB = b === entityId;
|
|
2725
|
+
if (!isA && !isB)
|
|
2726
|
+
return;
|
|
2727
|
+
const other = isA ? b : a;
|
|
2728
|
+
if (opts?.tag) {
|
|
2729
|
+
const tagComp = engine.ecs.getComponent(other, "Tag");
|
|
2730
|
+
if (!tagComp?.tags.includes(opts.tag))
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
if (opts?.layer) {
|
|
2734
|
+
const col = engine.ecs.getComponent(other, "BoxCollider");
|
|
2735
|
+
if (col?.layer !== opts.layer)
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
handler(other);
|
|
2739
|
+
});
|
|
2740
|
+
}, [engine.events, engine.ecs, entityId, opts?.tag, opts?.layer]);
|
|
2741
|
+
}
|
|
2742
|
+
function useTriggerEnter(handler, opts) {
|
|
2743
|
+
useContactEvent("triggerEnter", handler, opts);
|
|
2744
|
+
}
|
|
2745
|
+
function useTriggerExit(handler, opts) {
|
|
2746
|
+
useContactEvent("triggerExit", handler, opts);
|
|
2747
|
+
}
|
|
2748
|
+
function useCollisionEnter(handler, opts) {
|
|
2749
|
+
useContactEvent("collisionEnter", handler, opts);
|
|
2750
|
+
}
|
|
2751
|
+
function useCollisionExit(handler, opts) {
|
|
2752
|
+
useContactEvent("collisionExit", handler, opts);
|
|
2753
|
+
}
|
|
2754
|
+
function useCircleEnter(handler, opts) {
|
|
2755
|
+
useContactEvent("circleEnter", handler, opts);
|
|
2756
|
+
}
|
|
2757
|
+
function useCircleExit(handler, opts) {
|
|
2758
|
+
useContactEvent("circleExit", handler, opts);
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2232
2761
|
// src/components/Checkpoint.tsx
|
|
2233
2762
|
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
2763
|
+
function CheckpointActivator({ onActivate }) {
|
|
2764
|
+
const [used, setUsed] = useState4(false);
|
|
2765
|
+
useTriggerEnter(() => {
|
|
2766
|
+
if (used)
|
|
2767
|
+
return;
|
|
2768
|
+
setUsed(true);
|
|
2769
|
+
onActivate?.();
|
|
2770
|
+
}, { tag: "player" });
|
|
2771
|
+
return null;
|
|
2772
|
+
}
|
|
2234
2773
|
function Checkpoint({
|
|
2235
2774
|
x,
|
|
2236
2775
|
y,
|
|
@@ -2257,36 +2796,14 @@ function Checkpoint({
|
|
|
2257
2796
|
height,
|
|
2258
2797
|
isTrigger: true
|
|
2259
2798
|
}, 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
|
-
}
|
|
2799
|
+
/* @__PURE__ */ jsxDEV6(CheckpointActivator, {
|
|
2800
|
+
onActivate
|
|
2284
2801
|
}, undefined, false, undefined, this)
|
|
2285
2802
|
]
|
|
2286
2803
|
}, undefined, true, undefined, this);
|
|
2287
2804
|
}
|
|
2288
2805
|
// src/components/Tilemap.tsx
|
|
2289
|
-
import { useEffect as
|
|
2806
|
+
import { useEffect as useEffect16, useState as useState5, useContext as useContext14 } from "react";
|
|
2290
2807
|
import { jsxDEV as jsxDEV7, Fragment as Fragment3 } from "react/jsx-dev-runtime";
|
|
2291
2808
|
var animatedTiles = new Map;
|
|
2292
2809
|
function getProperty(props, name) {
|
|
@@ -2310,9 +2827,9 @@ function Tilemap({
|
|
|
2310
2827
|
triggerLayer: triggerLayerName = "triggers",
|
|
2311
2828
|
onTileProperty
|
|
2312
2829
|
}) {
|
|
2313
|
-
const engine =
|
|
2314
|
-
const [spawnedNodes, setSpawnedNodes] =
|
|
2315
|
-
|
|
2830
|
+
const engine = useContext14(EngineContext);
|
|
2831
|
+
const [spawnedNodes, setSpawnedNodes] = useState5([]);
|
|
2832
|
+
useEffect16(() => {
|
|
2316
2833
|
if (!engine)
|
|
2317
2834
|
return;
|
|
2318
2835
|
const createdEntities = [];
|
|
@@ -2508,7 +3025,7 @@ function Tilemap({
|
|
|
2508
3025
|
}, undefined, false, undefined, this);
|
|
2509
3026
|
}
|
|
2510
3027
|
// src/components/ParallaxLayer.tsx
|
|
2511
|
-
import { useEffect as
|
|
3028
|
+
import { useEffect as useEffect17, useContext as useContext15 } from "react";
|
|
2512
3029
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
2513
3030
|
function ParallaxLayerInner({
|
|
2514
3031
|
src,
|
|
@@ -2520,9 +3037,9 @@ function ParallaxLayerInner({
|
|
|
2520
3037
|
offsetX,
|
|
2521
3038
|
offsetY
|
|
2522
3039
|
}) {
|
|
2523
|
-
const engine =
|
|
2524
|
-
const entityId =
|
|
2525
|
-
|
|
3040
|
+
const engine = useContext15(EngineContext);
|
|
3041
|
+
const entityId = useContext15(EntityContext);
|
|
3042
|
+
useEffect17(() => {
|
|
2526
3043
|
engine.ecs.addComponent(entityId, {
|
|
2527
3044
|
type: "ParallaxLayer",
|
|
2528
3045
|
src,
|
|
@@ -2538,7 +3055,7 @@ function ParallaxLayerInner({
|
|
|
2538
3055
|
});
|
|
2539
3056
|
return () => engine.ecs.removeComponent(entityId, "ParallaxLayer");
|
|
2540
3057
|
}, []);
|
|
2541
|
-
|
|
3058
|
+
useEffect17(() => {
|
|
2542
3059
|
const layer = engine.ecs.getComponent(entityId, "ParallaxLayer");
|
|
2543
3060
|
if (!layer)
|
|
2544
3061
|
return;
|
|
@@ -2620,47 +3137,111 @@ var ScreenFlash = forwardRef((_, ref) => {
|
|
|
2620
3137
|
});
|
|
2621
3138
|
ScreenFlash.displayName = "ScreenFlash";
|
|
2622
3139
|
// src/hooks/useGame.ts
|
|
2623
|
-
import { useContext as
|
|
3140
|
+
import { useContext as useContext16 } from "react";
|
|
2624
3141
|
function useGame() {
|
|
2625
|
-
const engine =
|
|
3142
|
+
const engine = useContext16(EngineContext);
|
|
2626
3143
|
if (!engine)
|
|
2627
3144
|
throw new Error("useGame must be used inside <Game>");
|
|
2628
3145
|
return engine;
|
|
2629
3146
|
}
|
|
3147
|
+
// src/hooks/useCamera.ts
|
|
3148
|
+
import { useMemo } from "react";
|
|
3149
|
+
function useCamera() {
|
|
3150
|
+
const engine = useGame();
|
|
3151
|
+
return useMemo(() => ({
|
|
3152
|
+
shake(intensity, duration) {
|
|
3153
|
+
engine.renderSystem?.triggerShake(intensity, duration);
|
|
3154
|
+
},
|
|
3155
|
+
setFollowOffset(x, y) {
|
|
3156
|
+
const camId = engine.ecs.queryOne("Camera2D");
|
|
3157
|
+
if (camId === undefined)
|
|
3158
|
+
return;
|
|
3159
|
+
const cam = engine.ecs.getComponent(camId, "Camera2D");
|
|
3160
|
+
if (cam) {
|
|
3161
|
+
cam.followOffsetX = x;
|
|
3162
|
+
cam.followOffsetY = y;
|
|
3163
|
+
}
|
|
3164
|
+
},
|
|
3165
|
+
setPosition(x, y) {
|
|
3166
|
+
const camId = engine.ecs.queryOne("Camera2D");
|
|
3167
|
+
if (camId === undefined)
|
|
3168
|
+
return;
|
|
3169
|
+
const cam = engine.ecs.getComponent(camId, "Camera2D");
|
|
3170
|
+
if (cam) {
|
|
3171
|
+
cam.x = x;
|
|
3172
|
+
cam.y = y;
|
|
3173
|
+
}
|
|
3174
|
+
},
|
|
3175
|
+
setZoom(zoom) {
|
|
3176
|
+
const camId = engine.ecs.queryOne("Camera2D");
|
|
3177
|
+
if (camId === undefined)
|
|
3178
|
+
return;
|
|
3179
|
+
const cam = engine.ecs.getComponent(camId, "Camera2D");
|
|
3180
|
+
if (cam)
|
|
3181
|
+
cam.zoom = zoom;
|
|
3182
|
+
}
|
|
3183
|
+
}), [engine]);
|
|
3184
|
+
}
|
|
3185
|
+
// src/hooks/useSnapshot.ts
|
|
3186
|
+
import { useMemo as useMemo2 } from "react";
|
|
3187
|
+
function useSnapshot() {
|
|
3188
|
+
const engine = useGame();
|
|
3189
|
+
return useMemo2(() => ({
|
|
3190
|
+
save: () => engine.ecs.getSnapshot(),
|
|
3191
|
+
restore: (snapshot) => engine.ecs.restoreSnapshot(snapshot)
|
|
3192
|
+
}), [engine]);
|
|
3193
|
+
}
|
|
2630
3194
|
// src/hooks/useEntity.ts
|
|
2631
|
-
import { useContext as
|
|
3195
|
+
import { useContext as useContext17 } from "react";
|
|
2632
3196
|
function useEntity() {
|
|
2633
|
-
const id =
|
|
3197
|
+
const id = useContext17(EntityContext);
|
|
2634
3198
|
if (id === null)
|
|
2635
3199
|
throw new Error("useEntity must be used inside <Entity>");
|
|
2636
3200
|
return id;
|
|
2637
3201
|
}
|
|
2638
3202
|
// src/hooks/useInput.ts
|
|
2639
|
-
import { useContext as
|
|
3203
|
+
import { useContext as useContext18 } from "react";
|
|
2640
3204
|
function useInput() {
|
|
2641
|
-
const engine =
|
|
3205
|
+
const engine = useContext18(EngineContext);
|
|
2642
3206
|
if (!engine)
|
|
2643
3207
|
throw new Error("useInput must be used inside <Game>");
|
|
2644
3208
|
return engine.input;
|
|
2645
3209
|
}
|
|
3210
|
+
// src/hooks/useInputMap.ts
|
|
3211
|
+
import { useMemo as useMemo3 } from "react";
|
|
3212
|
+
function useInputMap(bindings) {
|
|
3213
|
+
const input = useInput();
|
|
3214
|
+
const normalized = useMemo3(() => {
|
|
3215
|
+
const out = {};
|
|
3216
|
+
for (const [action, keys] of Object.entries(bindings)) {
|
|
3217
|
+
out[action] = Array.isArray(keys) ? keys : [keys];
|
|
3218
|
+
}
|
|
3219
|
+
return out;
|
|
3220
|
+
}, []);
|
|
3221
|
+
return useMemo3(() => ({
|
|
3222
|
+
isActionDown: (action) => (normalized[action] ?? []).some((k) => input.isDown(k)),
|
|
3223
|
+
isActionPressed: (action) => (normalized[action] ?? []).some((k) => input.isPressed(k)),
|
|
3224
|
+
isActionReleased: (action) => (normalized[action] ?? []).some((k) => input.isReleased(k))
|
|
3225
|
+
}), [input, normalized]);
|
|
3226
|
+
}
|
|
2646
3227
|
// src/hooks/useEvents.ts
|
|
2647
|
-
import { useContext as
|
|
3228
|
+
import { useContext as useContext19, useEffect as useEffect18 } from "react";
|
|
2648
3229
|
function useEvents() {
|
|
2649
|
-
const engine =
|
|
3230
|
+
const engine = useContext19(EngineContext);
|
|
2650
3231
|
if (!engine)
|
|
2651
3232
|
throw new Error("useEvents must be used inside <Game>");
|
|
2652
3233
|
return engine.events;
|
|
2653
3234
|
}
|
|
2654
3235
|
function useEvent(event, handler) {
|
|
2655
3236
|
const events = useEvents();
|
|
2656
|
-
|
|
3237
|
+
useEffect18(() => {
|
|
2657
3238
|
return events.on(event, handler);
|
|
2658
3239
|
}, [events, event]);
|
|
2659
3240
|
}
|
|
2660
3241
|
// src/hooks/usePlatformerController.ts
|
|
2661
|
-
import { useContext as
|
|
3242
|
+
import { useContext as useContext20, useEffect as useEffect19 } from "react";
|
|
2662
3243
|
function usePlatformerController(entityId, opts = {}) {
|
|
2663
|
-
const engine =
|
|
3244
|
+
const engine = useContext20(EngineContext);
|
|
2664
3245
|
const {
|
|
2665
3246
|
speed = 200,
|
|
2666
3247
|
jumpForce = -500,
|
|
@@ -2668,7 +3249,7 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
2668
3249
|
coyoteTime = 0.08,
|
|
2669
3250
|
jumpBuffer = 0.08
|
|
2670
3251
|
} = opts;
|
|
2671
|
-
|
|
3252
|
+
useEffect19(() => {
|
|
2672
3253
|
const state = { coyoteTimer: 0, jumpBuffer: 0, jumpsLeft: maxJumps };
|
|
2673
3254
|
const updateFn = (id, world2, input, dt) => {
|
|
2674
3255
|
if (!world2.hasEntity(id))
|
|
@@ -2717,11 +3298,11 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
2717
3298
|
}, []);
|
|
2718
3299
|
}
|
|
2719
3300
|
// src/hooks/useTopDownMovement.ts
|
|
2720
|
-
import { useContext as
|
|
3301
|
+
import { useContext as useContext21, useEffect as useEffect20 } from "react";
|
|
2721
3302
|
function useTopDownMovement(entityId, opts = {}) {
|
|
2722
|
-
const engine =
|
|
3303
|
+
const engine = useContext21(EngineContext);
|
|
2723
3304
|
const { speed = 200, normalizeDiagonal = true } = opts;
|
|
2724
|
-
|
|
3305
|
+
useEffect20(() => {
|
|
2725
3306
|
const updateFn = (id, world2, input) => {
|
|
2726
3307
|
if (!world2.hasEntity(id))
|
|
2727
3308
|
return;
|
|
@@ -2755,15 +3336,31 @@ function createAtlas(names, _columns) {
|
|
|
2755
3336
|
return atlas;
|
|
2756
3337
|
}
|
|
2757
3338
|
export {
|
|
3339
|
+
useTriggerExit,
|
|
3340
|
+
useTriggerEnter,
|
|
2758
3341
|
useTopDownMovement,
|
|
3342
|
+
useSnapshot,
|
|
2759
3343
|
usePlatformerController,
|
|
3344
|
+
useInputMap,
|
|
2760
3345
|
useInput,
|
|
2761
3346
|
useGame,
|
|
2762
3347
|
useEvents,
|
|
2763
3348
|
useEvent,
|
|
2764
3349
|
useEntity,
|
|
3350
|
+
useCollisionExit,
|
|
3351
|
+
useCollisionEnter,
|
|
3352
|
+
useCircleExit,
|
|
3353
|
+
useCircleEnter,
|
|
3354
|
+
useCamera,
|
|
2765
3355
|
tween,
|
|
3356
|
+
raycast,
|
|
3357
|
+
overlapBox,
|
|
3358
|
+
findByTag,
|
|
2766
3359
|
definePlugin,
|
|
3360
|
+
createTransform,
|
|
3361
|
+
createTag,
|
|
3362
|
+
createSprite,
|
|
3363
|
+
createInputMap,
|
|
2767
3364
|
createAtlas,
|
|
2768
3365
|
World,
|
|
2769
3366
|
Transform,
|
|
@@ -2779,6 +3376,7 @@ export {
|
|
|
2779
3376
|
Game,
|
|
2780
3377
|
Entity,
|
|
2781
3378
|
Ease,
|
|
3379
|
+
CircleCollider,
|
|
2782
3380
|
Checkpoint,
|
|
2783
3381
|
Camera2D,
|
|
2784
3382
|
BoxCollider,
|