cubeforge 0.0.5 → 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/DevTools.d.ts +15 -0
- package/dist/components/Game.d.ts +16 -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 +16 -3
- package/dist/index.js +1105 -191
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,48 +1,94 @@
|
|
|
1
1
|
// src/components/Game.tsx
|
|
2
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
3
3
|
// ../core/src/ecs/world.ts
|
|
4
4
|
class ECSWorld {
|
|
5
5
|
nextId = 0;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
componentIndex = new Map;
|
|
7
|
+
_rngState = 0;
|
|
8
|
+
_deterministic = false;
|
|
9
|
+
archetypes = new Map;
|
|
10
|
+
entityArchetype = new Map;
|
|
8
11
|
systems = [];
|
|
9
12
|
queryCache = new Map;
|
|
10
13
|
dirtyTypes = new Set;
|
|
11
14
|
dirtyAll = false;
|
|
15
|
+
getOrCreateArchetype(types) {
|
|
16
|
+
const arr = [...types].sort();
|
|
17
|
+
const key = arr.join("\x00");
|
|
18
|
+
let arch = this.archetypes.get(key);
|
|
19
|
+
if (!arch) {
|
|
20
|
+
arch = { key, types: new Set(arr), entities: [] };
|
|
21
|
+
this.archetypes.set(key, arch);
|
|
22
|
+
}
|
|
23
|
+
return arch;
|
|
24
|
+
}
|
|
25
|
+
moveToArchetype(id, newArch) {
|
|
26
|
+
const oldKey = this.entityArchetype.get(id);
|
|
27
|
+
if (oldKey !== undefined) {
|
|
28
|
+
const oldArch = this.archetypes.get(oldKey);
|
|
29
|
+
if (oldArch) {
|
|
30
|
+
const idx = oldArch.entities.indexOf(id);
|
|
31
|
+
if (idx !== -1)
|
|
32
|
+
oldArch.entities.splice(idx, 1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
newArch.entities.push(id);
|
|
36
|
+
this.entityArchetype.set(id, newArch.key);
|
|
37
|
+
}
|
|
12
38
|
createEntity() {
|
|
13
39
|
const id = this.nextId++;
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
40
|
+
this.componentIndex.set(id, new Map);
|
|
41
|
+
const emptyArch = this.getOrCreateArchetype([]);
|
|
42
|
+
emptyArch.entities.push(id);
|
|
43
|
+
this.entityArchetype.set(id, emptyArch.key);
|
|
16
44
|
this.dirtyAll = true;
|
|
17
45
|
return id;
|
|
18
46
|
}
|
|
19
47
|
destroyEntity(id) {
|
|
20
|
-
const comps = this.
|
|
48
|
+
const comps = this.componentIndex.get(id);
|
|
21
49
|
if (comps) {
|
|
22
|
-
for (const type of comps.keys())
|
|
50
|
+
for (const type of comps.keys())
|
|
23
51
|
this.dirtyTypes.add(type);
|
|
52
|
+
}
|
|
53
|
+
const archKey = this.entityArchetype.get(id);
|
|
54
|
+
if (archKey !== undefined) {
|
|
55
|
+
const arch = this.archetypes.get(archKey);
|
|
56
|
+
if (arch) {
|
|
57
|
+
const idx = arch.entities.indexOf(id);
|
|
58
|
+
if (idx !== -1)
|
|
59
|
+
arch.entities.splice(idx, 1);
|
|
24
60
|
}
|
|
25
61
|
}
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
62
|
+
this.componentIndex.delete(id);
|
|
63
|
+
this.entityArchetype.delete(id);
|
|
28
64
|
this.dirtyAll = true;
|
|
29
65
|
}
|
|
30
66
|
hasEntity(id) {
|
|
31
|
-
return this.
|
|
67
|
+
return this.componentIndex.has(id);
|
|
32
68
|
}
|
|
33
69
|
addComponent(id, component) {
|
|
34
|
-
this.
|
|
70
|
+
const comps = this.componentIndex.get(id);
|
|
71
|
+
if (!comps)
|
|
72
|
+
return;
|
|
73
|
+
comps.set(component.type, component);
|
|
35
74
|
this.dirtyTypes.add(component.type);
|
|
75
|
+
const newArch = this.getOrCreateArchetype(comps.keys());
|
|
76
|
+
this.moveToArchetype(id, newArch);
|
|
36
77
|
}
|
|
37
78
|
removeComponent(id, type) {
|
|
38
|
-
this.
|
|
79
|
+
const comps = this.componentIndex.get(id);
|
|
80
|
+
if (!comps)
|
|
81
|
+
return;
|
|
82
|
+
comps.delete(type);
|
|
39
83
|
this.dirtyTypes.add(type);
|
|
84
|
+
const newArch = this.getOrCreateArchetype(comps.keys());
|
|
85
|
+
this.moveToArchetype(id, newArch);
|
|
40
86
|
}
|
|
41
87
|
getComponent(id, type) {
|
|
42
|
-
return this.
|
|
88
|
+
return this.componentIndex.get(id)?.get(type);
|
|
43
89
|
}
|
|
44
90
|
hasComponent(id, type) {
|
|
45
|
-
return this.
|
|
91
|
+
return this.componentIndex.get(id)?.has(type) ?? false;
|
|
46
92
|
}
|
|
47
93
|
query(...types) {
|
|
48
94
|
const key = types.slice().sort().join("\x00");
|
|
@@ -50,36 +96,77 @@ class ECSWorld {
|
|
|
50
96
|
if (cached)
|
|
51
97
|
return cached;
|
|
52
98
|
const result = [];
|
|
53
|
-
for (const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!comps.has(t)) {
|
|
58
|
-
match = false;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
99
|
+
for (const arch of this.archetypes.values()) {
|
|
100
|
+
if (types.every((t) => arch.types.has(t))) {
|
|
101
|
+
for (const id of arch.entities)
|
|
102
|
+
result.push(id);
|
|
61
103
|
}
|
|
62
|
-
if (match)
|
|
63
|
-
result.push(id);
|
|
64
104
|
}
|
|
65
105
|
this.queryCache.set(key, result);
|
|
66
106
|
return result;
|
|
67
107
|
}
|
|
68
108
|
queryOne(...types) {
|
|
69
|
-
for (const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!comps.has(t)) {
|
|
74
|
-
match = false;
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
109
|
+
for (const arch of this.archetypes.values()) {
|
|
110
|
+
if (types.every((t) => arch.types.has(t))) {
|
|
111
|
+
if (arch.entities.length > 0)
|
|
112
|
+
return arch.entities[0];
|
|
77
113
|
}
|
|
78
|
-
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
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))
|
|
79
121
|
return id;
|
|
80
122
|
}
|
|
81
123
|
return;
|
|
82
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
|
+
}
|
|
134
|
+
setDeterministicSeed(seed) {
|
|
135
|
+
this._rngState = seed >>> 0;
|
|
136
|
+
this._deterministic = true;
|
|
137
|
+
}
|
|
138
|
+
rng() {
|
|
139
|
+
if (!this._deterministic)
|
|
140
|
+
return Math.random();
|
|
141
|
+
this._rngState = Math.imul(this._rngState, 1664525) + 1013904223 >>> 0;
|
|
142
|
+
return this._rngState / 4294967296;
|
|
143
|
+
}
|
|
144
|
+
getSnapshot() {
|
|
145
|
+
const entities = [];
|
|
146
|
+
for (const [id, comps] of this.componentIndex) {
|
|
147
|
+
const components = [];
|
|
148
|
+
for (const comp of comps.values()) {
|
|
149
|
+
components.push(JSON.parse(JSON.stringify(comp)));
|
|
150
|
+
}
|
|
151
|
+
entities.push({ id, components });
|
|
152
|
+
}
|
|
153
|
+
return { nextId: this.nextId, rngState: this._rngState, entities };
|
|
154
|
+
}
|
|
155
|
+
restoreSnapshot(snapshot) {
|
|
156
|
+
this.clear();
|
|
157
|
+
this.nextId = snapshot.nextId;
|
|
158
|
+
this._rngState = snapshot.rngState;
|
|
159
|
+
for (const { id, components } of snapshot.entities) {
|
|
160
|
+
const compMap = new Map;
|
|
161
|
+
for (const comp of components)
|
|
162
|
+
compMap.set(comp.type, comp);
|
|
163
|
+
this.componentIndex.set(id, compMap);
|
|
164
|
+
const arch = this.getOrCreateArchetype(compMap.keys());
|
|
165
|
+
arch.entities.push(id);
|
|
166
|
+
this.entityArchetype.set(id, arch.key);
|
|
167
|
+
}
|
|
168
|
+
this.dirtyAll = true;
|
|
169
|
+
}
|
|
83
170
|
addSystem(system) {
|
|
84
171
|
this.systems.push(system);
|
|
85
172
|
}
|
|
@@ -110,17 +197,30 @@ class ECSWorld {
|
|
|
110
197
|
}
|
|
111
198
|
}
|
|
112
199
|
clear() {
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
200
|
+
this.componentIndex.clear();
|
|
201
|
+
this.archetypes.clear();
|
|
202
|
+
this.entityArchetype.clear();
|
|
115
203
|
this.queryCache.clear();
|
|
116
204
|
this.dirtyTypes.clear();
|
|
117
205
|
this.dirtyAll = false;
|
|
118
206
|
this.nextId = 0;
|
|
207
|
+
this._rngState = 0;
|
|
208
|
+
this._deterministic = false;
|
|
119
209
|
}
|
|
120
210
|
get entityCount() {
|
|
121
|
-
return this.
|
|
211
|
+
return this.componentIndex.size;
|
|
122
212
|
}
|
|
123
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
|
+
}
|
|
124
224
|
// ../core/src/loop/gameLoop.ts
|
|
125
225
|
class GameLoop {
|
|
126
226
|
onTick;
|
|
@@ -532,6 +632,24 @@ class InputManager {
|
|
|
532
632
|
return this.keyboard.isReleased(key);
|
|
533
633
|
}
|
|
534
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
|
+
}
|
|
535
653
|
// ../renderer/src/canvas2d.ts
|
|
536
654
|
class Canvas2DRenderer {
|
|
537
655
|
canvas;
|
|
@@ -583,6 +701,8 @@ function createCamera2D(opts) {
|
|
|
583
701
|
zoom: 1,
|
|
584
702
|
smoothing: 0,
|
|
585
703
|
background: "#1a1a2e",
|
|
704
|
+
followOffsetX: 0,
|
|
705
|
+
followOffsetY: 0,
|
|
586
706
|
shakeIntensity: 0,
|
|
587
707
|
shakeDuration: 0,
|
|
588
708
|
shakeTimer: 0,
|
|
@@ -639,25 +759,27 @@ class RenderSystem {
|
|
|
639
759
|
if (targetId !== undefined) {
|
|
640
760
|
const targetTransform = world2.getComponent(targetId, "Transform");
|
|
641
761
|
if (targetTransform) {
|
|
762
|
+
const tx = targetTransform.x + (cam.followOffsetX ?? 0);
|
|
763
|
+
const ty = targetTransform.y + (cam.followOffsetY ?? 0);
|
|
642
764
|
if (cam.deadZone) {
|
|
643
765
|
const halfW = cam.deadZone.w / 2;
|
|
644
766
|
const halfH = cam.deadZone.h / 2;
|
|
645
|
-
const dx =
|
|
646
|
-
const dy =
|
|
767
|
+
const dx = tx - cam.x;
|
|
768
|
+
const dy = ty - cam.y;
|
|
647
769
|
if (dx > halfW)
|
|
648
|
-
cam.x =
|
|
770
|
+
cam.x = tx - halfW;
|
|
649
771
|
else if (dx < -halfW)
|
|
650
|
-
cam.x =
|
|
772
|
+
cam.x = tx + halfW;
|
|
651
773
|
if (dy > halfH)
|
|
652
|
-
cam.y =
|
|
774
|
+
cam.y = ty - halfH;
|
|
653
775
|
else if (dy < -halfH)
|
|
654
|
-
cam.y =
|
|
776
|
+
cam.y = ty + halfH;
|
|
655
777
|
} else if (cam.smoothing > 0) {
|
|
656
|
-
cam.x += (
|
|
657
|
-
cam.y += (
|
|
778
|
+
cam.x += (tx - cam.x) * (1 - cam.smoothing);
|
|
779
|
+
cam.y += (ty - cam.y) * (1 - cam.smoothing);
|
|
658
780
|
} else {
|
|
659
|
-
cam.x =
|
|
660
|
-
cam.y =
|
|
781
|
+
cam.x = tx;
|
|
782
|
+
cam.y = ty;
|
|
661
783
|
}
|
|
662
784
|
}
|
|
663
785
|
}
|
|
@@ -673,8 +795,8 @@ class RenderSystem {
|
|
|
673
795
|
if (cam.shakeTimer < 0)
|
|
674
796
|
cam.shakeTimer = 0;
|
|
675
797
|
const progress = cam.shakeDuration > 0 ? cam.shakeTimer / cam.shakeDuration : 0;
|
|
676
|
-
shakeX = (
|
|
677
|
-
shakeY = (
|
|
798
|
+
shakeX = (world2.rng() * 2 - 1) * cam.shakeIntensity * progress;
|
|
799
|
+
shakeY = (world2.rng() * 2 - 1) * cam.shakeIntensity * progress;
|
|
678
800
|
}
|
|
679
801
|
camX = cam.x;
|
|
680
802
|
camY = cam.y;
|
|
@@ -691,7 +813,16 @@ class RenderSystem {
|
|
|
691
813
|
anim.timer -= frameDuration;
|
|
692
814
|
anim.currentIndex++;
|
|
693
815
|
if (anim.currentIndex >= anim.frames.length) {
|
|
694
|
-
|
|
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
|
+
}
|
|
695
826
|
}
|
|
696
827
|
}
|
|
697
828
|
sprite.frameIndex = anim.frames[anim.currentIndex];
|
|
@@ -755,10 +886,27 @@ class RenderSystem {
|
|
|
755
886
|
ctx.translate(canvas.width / 2 - camX * zoom + shakeX, canvas.height / 2 - camY * zoom + shakeY);
|
|
756
887
|
ctx.scale(zoom, zoom);
|
|
757
888
|
const renderables = world2.query("Transform", "Sprite");
|
|
889
|
+
const textureKey = (id) => {
|
|
890
|
+
const sprite = world2.getComponent(id, "Sprite");
|
|
891
|
+
if (sprite.image && sprite.image.src)
|
|
892
|
+
return sprite.image.src;
|
|
893
|
+
if (sprite.src)
|
|
894
|
+
return sprite.src;
|
|
895
|
+
return `__color__:${sprite.color}`;
|
|
896
|
+
};
|
|
758
897
|
renderables.sort((a, b) => {
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
|
|
898
|
+
const sa = world2.getComponent(a, "Sprite");
|
|
899
|
+
const sb = world2.getComponent(b, "Sprite");
|
|
900
|
+
const zDiff = sa.zIndex - sb.zIndex;
|
|
901
|
+
if (zDiff !== 0)
|
|
902
|
+
return zDiff;
|
|
903
|
+
const ka = textureKey(a);
|
|
904
|
+
const kb = textureKey(b);
|
|
905
|
+
if (ka < kb)
|
|
906
|
+
return -1;
|
|
907
|
+
if (ka > kb)
|
|
908
|
+
return 1;
|
|
909
|
+
return 0;
|
|
762
910
|
});
|
|
763
911
|
for (const id of renderables) {
|
|
764
912
|
const transform2 = world2.getComponent(id, "Transform");
|
|
@@ -809,8 +957,8 @@ class RenderSystem {
|
|
|
809
957
|
const spawnCount = Math.floor(pool.timer * pool.rate);
|
|
810
958
|
pool.timer -= spawnCount / pool.rate;
|
|
811
959
|
for (let i = 0;i < spawnCount && pool.particles.length < pool.maxParticles; i++) {
|
|
812
|
-
const angle = pool.angle + (
|
|
813
|
-
const speed = pool.speed * (0.5 +
|
|
960
|
+
const angle = pool.angle + (world2.rng() - 0.5) * pool.spread;
|
|
961
|
+
const speed = pool.speed * (0.5 + world2.rng() * 0.5);
|
|
814
962
|
pool.particles.push({
|
|
815
963
|
x: t.x,
|
|
816
964
|
y: t.y,
|
|
@@ -868,6 +1016,8 @@ function createRigidBody(opts) {
|
|
|
868
1016
|
isNearGround: false,
|
|
869
1017
|
bounce: 0,
|
|
870
1018
|
friction: 0.85,
|
|
1019
|
+
lockX: false,
|
|
1020
|
+
lockY: false,
|
|
871
1021
|
...opts
|
|
872
1022
|
};
|
|
873
1023
|
}
|
|
@@ -881,7 +1031,22 @@ function createBoxCollider(width, height, opts) {
|
|
|
881
1031
|
offsetY: 0,
|
|
882
1032
|
isTrigger: false,
|
|
883
1033
|
layer: "default",
|
|
1034
|
+
mask: "*",
|
|
884
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: "*",
|
|
885
1050
|
...opts
|
|
886
1051
|
};
|
|
887
1052
|
}
|
|
@@ -919,12 +1084,27 @@ function getSlopeSurfaceY(st, sc, worldX) {
|
|
|
919
1084
|
const angleRad = sc.slope * (Math.PI / 180);
|
|
920
1085
|
return cy - hh + dx * Math.tan(angleRad);
|
|
921
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
|
+
}
|
|
922
1098
|
|
|
923
1099
|
class PhysicsSystem {
|
|
924
1100
|
gravity;
|
|
925
1101
|
events;
|
|
926
1102
|
accumulator = 0;
|
|
927
1103
|
FIXED_DT = 1 / 60;
|
|
1104
|
+
activeTriggerPairs = new Map;
|
|
1105
|
+
activeCollisionPairs = new Map;
|
|
1106
|
+
activeCirclePairs = new Map;
|
|
1107
|
+
staticPrevPos = new Map;
|
|
928
1108
|
constructor(gravity, events) {
|
|
929
1109
|
this.gravity = gravity;
|
|
930
1110
|
this.events = events;
|
|
@@ -965,6 +1145,30 @@ class PhysicsSystem {
|
|
|
965
1145
|
else
|
|
966
1146
|
dynamics.push(id);
|
|
967
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
|
+
}
|
|
968
1172
|
const staticGrid = new Map;
|
|
969
1173
|
for (const sid of statics) {
|
|
970
1174
|
const st = world2.getComponent(sid, "Transform");
|
|
@@ -983,7 +1187,12 @@ class PhysicsSystem {
|
|
|
983
1187
|
const rb = world2.getComponent(id, "RigidBody");
|
|
984
1188
|
rb.onGround = false;
|
|
985
1189
|
rb.isNearGround = false;
|
|
986
|
-
|
|
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;
|
|
987
1196
|
}
|
|
988
1197
|
for (const id of dynamics) {
|
|
989
1198
|
const transform2 = world2.getComponent(id, "Transform");
|
|
@@ -1008,6 +1217,8 @@ class PhysicsSystem {
|
|
|
1008
1217
|
continue;
|
|
1009
1218
|
if (sc.slope !== 0)
|
|
1010
1219
|
continue;
|
|
1220
|
+
if (!canInteract(col, sc))
|
|
1221
|
+
continue;
|
|
1011
1222
|
const ov = getOverlap(getAABB(transform2, col), getAABB(st, sc));
|
|
1012
1223
|
if (!ov)
|
|
1013
1224
|
continue;
|
|
@@ -1040,6 +1251,8 @@ class PhysicsSystem {
|
|
|
1040
1251
|
const sc = world2.getComponent(sid, "BoxCollider");
|
|
1041
1252
|
if (sc.isTrigger)
|
|
1042
1253
|
continue;
|
|
1254
|
+
if (!canInteract(col, sc))
|
|
1255
|
+
continue;
|
|
1043
1256
|
if (sc.slope !== 0) {
|
|
1044
1257
|
const ov2 = getOverlap(getAABB(transform2, col), getAABB(st, sc));
|
|
1045
1258
|
if (!ov2)
|
|
@@ -1060,11 +1273,25 @@ class PhysicsSystem {
|
|
|
1060
1273
|
if (!ov)
|
|
1061
1274
|
continue;
|
|
1062
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
|
+
}
|
|
1063
1284
|
transform2.y += ov.y;
|
|
1064
1285
|
if (ov.y < 0) {
|
|
1065
1286
|
rb.onGround = true;
|
|
1066
1287
|
if (rb.friction < 1)
|
|
1067
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
|
+
}
|
|
1068
1295
|
}
|
|
1069
1296
|
rb.vy = rb.bounce > 0 ? -rb.vy * rb.bounce : 0;
|
|
1070
1297
|
}
|
|
@@ -1072,6 +1299,7 @@ class PhysicsSystem {
|
|
|
1072
1299
|
}
|
|
1073
1300
|
}
|
|
1074
1301
|
}
|
|
1302
|
+
const currentCollisionPairs = new Map;
|
|
1075
1303
|
for (let i = 0;i < dynamics.length; i++) {
|
|
1076
1304
|
for (let j = i + 1;j < dynamics.length; j++) {
|
|
1077
1305
|
const ia = dynamics[i];
|
|
@@ -1083,10 +1311,10 @@ class PhysicsSystem {
|
|
|
1083
1311
|
const ov = getOverlap(getAABB(ta, ca), getAABB(tb, cb));
|
|
1084
1312
|
if (!ov)
|
|
1085
1313
|
continue;
|
|
1086
|
-
if (ca
|
|
1087
|
-
|
|
1314
|
+
if (!canInteract(ca, cb))
|
|
1315
|
+
continue;
|
|
1316
|
+
if (ca.isTrigger || cb.isTrigger)
|
|
1088
1317
|
continue;
|
|
1089
|
-
}
|
|
1090
1318
|
const rba = world2.getComponent(ia, "RigidBody");
|
|
1091
1319
|
const rbb = world2.getComponent(ib, "RigidBody");
|
|
1092
1320
|
if (Math.abs(ov.y) <= Math.abs(ov.x)) {
|
|
@@ -1108,9 +1336,22 @@ class PhysicsSystem {
|
|
|
1108
1336
|
ta.y += ov.y / 2;
|
|
1109
1337
|
tb.x -= ov.x / 2;
|
|
1110
1338
|
tb.y -= ov.y / 2;
|
|
1111
|
-
|
|
1339
|
+
const key = pairKey(ia, ib);
|
|
1340
|
+
currentCollisionPairs.set(key, [ia, ib]);
|
|
1112
1341
|
}
|
|
1113
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;
|
|
1114
1355
|
for (const id of dynamics) {
|
|
1115
1356
|
const rb = world2.getComponent(id, "RigidBody");
|
|
1116
1357
|
if (rb.onGround) {
|
|
@@ -1140,6 +1381,8 @@ class PhysicsSystem {
|
|
|
1140
1381
|
const sc = world2.getComponent(sid, "BoxCollider");
|
|
1141
1382
|
if (sc.isTrigger)
|
|
1142
1383
|
continue;
|
|
1384
|
+
if (!canInteract(col, sc))
|
|
1385
|
+
continue;
|
|
1143
1386
|
const ov = getOverlap(probeAABB, getAABB(st, sc));
|
|
1144
1387
|
if (ov && Math.abs(ov.y) <= Math.abs(ov.x) && ov.y < 0) {
|
|
1145
1388
|
rb.isNearGround = true;
|
|
@@ -1148,7 +1391,194 @@ class PhysicsSystem {
|
|
|
1148
1391
|
}
|
|
1149
1392
|
}
|
|
1150
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
|
+
}
|
|
1484
|
+
}
|
|
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
|
+
}
|
|
1151
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;
|
|
1152
1582
|
}
|
|
1153
1583
|
// src/context.ts
|
|
1154
1584
|
import { createContext } from "react";
|
|
@@ -1253,34 +1683,350 @@ class DebugSystem {
|
|
|
1253
1683
|
}
|
|
1254
1684
|
}
|
|
1255
1685
|
|
|
1686
|
+
// src/components/DevTools.tsx
|
|
1687
|
+
import React from "react";
|
|
1688
|
+
import { createPortal } from "react-dom";
|
|
1689
|
+
import { useState, useEffect, useCallback } from "react";
|
|
1690
|
+
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
1691
|
+
var MAX_DEVTOOLS_FRAMES = 600;
|
|
1692
|
+
var css = {
|
|
1693
|
+
overlay: {
|
|
1694
|
+
position: "fixed",
|
|
1695
|
+
bottom: 0,
|
|
1696
|
+
left: 0,
|
|
1697
|
+
right: 0,
|
|
1698
|
+
zIndex: 99999,
|
|
1699
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', 'Courier New', monospace",
|
|
1700
|
+
fontSize: 11,
|
|
1701
|
+
color: "#cdd6f4",
|
|
1702
|
+
userSelect: "none",
|
|
1703
|
+
pointerEvents: "auto"
|
|
1704
|
+
},
|
|
1705
|
+
bar: {
|
|
1706
|
+
background: "rgba(11,13,20,0.97)",
|
|
1707
|
+
borderTop: "1px solid #2a3048",
|
|
1708
|
+
padding: "6px 12px",
|
|
1709
|
+
display: "flex",
|
|
1710
|
+
alignItems: "center",
|
|
1711
|
+
gap: 10,
|
|
1712
|
+
height: 40
|
|
1713
|
+
},
|
|
1714
|
+
badge: {
|
|
1715
|
+
fontSize: 9,
|
|
1716
|
+
fontWeight: 700,
|
|
1717
|
+
letterSpacing: "0.1em",
|
|
1718
|
+
color: "#4fc3f7",
|
|
1719
|
+
background: "rgba(79,195,247,0.1)",
|
|
1720
|
+
border: "1px solid rgba(79,195,247,0.2)",
|
|
1721
|
+
borderRadius: 4,
|
|
1722
|
+
padding: "2px 6px",
|
|
1723
|
+
flexShrink: 0
|
|
1724
|
+
},
|
|
1725
|
+
btn: (active = false, danger = false) => ({
|
|
1726
|
+
background: active ? "rgba(79,195,247,0.15)" : "rgba(255,255,255,0.04)",
|
|
1727
|
+
border: `1px solid ${active ? "rgba(79,195,247,0.3)" : "#2a3048"}`,
|
|
1728
|
+
borderRadius: 4,
|
|
1729
|
+
color: danger ? "#f38ba8" : active ? "#4fc3f7" : "#6b7a9e",
|
|
1730
|
+
cursor: "pointer",
|
|
1731
|
+
padding: "3px 8px",
|
|
1732
|
+
fontSize: 10,
|
|
1733
|
+
fontFamily: "inherit",
|
|
1734
|
+
display: "flex",
|
|
1735
|
+
alignItems: "center",
|
|
1736
|
+
gap: 4,
|
|
1737
|
+
transition: "all 0.1s",
|
|
1738
|
+
flexShrink: 0
|
|
1739
|
+
}),
|
|
1740
|
+
scrubber: {
|
|
1741
|
+
flex: 1,
|
|
1742
|
+
accentColor: "#4fc3f7",
|
|
1743
|
+
cursor: "pointer",
|
|
1744
|
+
height: 4
|
|
1745
|
+
},
|
|
1746
|
+
counter: {
|
|
1747
|
+
color: "#6b7a9e",
|
|
1748
|
+
fontSize: 10,
|
|
1749
|
+
whiteSpace: "nowrap",
|
|
1750
|
+
flexShrink: 0,
|
|
1751
|
+
minWidth: 80,
|
|
1752
|
+
textAlign: "right"
|
|
1753
|
+
},
|
|
1754
|
+
panel: {
|
|
1755
|
+
background: "rgba(11,13,20,0.97)",
|
|
1756
|
+
borderTop: "1px solid #1f2435",
|
|
1757
|
+
maxHeight: 260,
|
|
1758
|
+
overflowY: "auto",
|
|
1759
|
+
padding: "8px 0"
|
|
1760
|
+
},
|
|
1761
|
+
entityRow: (selected) => ({
|
|
1762
|
+
display: "flex",
|
|
1763
|
+
alignItems: "center",
|
|
1764
|
+
gap: 8,
|
|
1765
|
+
padding: "4px 12px",
|
|
1766
|
+
cursor: "pointer",
|
|
1767
|
+
background: selected ? "rgba(79,195,247,0.06)" : "transparent",
|
|
1768
|
+
borderLeft: `2px solid ${selected ? "#4fc3f7" : "transparent"}`
|
|
1769
|
+
}),
|
|
1770
|
+
entityId: {
|
|
1771
|
+
color: "#4fc3f7",
|
|
1772
|
+
minWidth: 28,
|
|
1773
|
+
fontSize: 10
|
|
1774
|
+
},
|
|
1775
|
+
compPill: {
|
|
1776
|
+
fontSize: 9,
|
|
1777
|
+
background: "rgba(79,195,247,0.08)",
|
|
1778
|
+
border: "1px solid rgba(79,195,247,0.12)",
|
|
1779
|
+
borderRadius: 3,
|
|
1780
|
+
padding: "1px 5px",
|
|
1781
|
+
color: "#6b7a9e"
|
|
1782
|
+
},
|
|
1783
|
+
detailPanel: {
|
|
1784
|
+
background: "rgba(18,21,31,0.98)",
|
|
1785
|
+
borderTop: "1px solid #1f2435",
|
|
1786
|
+
padding: "10px 14px",
|
|
1787
|
+
maxHeight: 200,
|
|
1788
|
+
overflowY: "auto"
|
|
1789
|
+
},
|
|
1790
|
+
kv: {
|
|
1791
|
+
display: "grid",
|
|
1792
|
+
gridTemplateColumns: "140px 1fr",
|
|
1793
|
+
gap: "2px 12px",
|
|
1794
|
+
lineHeight: 1.8
|
|
1795
|
+
},
|
|
1796
|
+
key: { color: "#6b7a9e" },
|
|
1797
|
+
val: { color: "#cdd6f4" }
|
|
1798
|
+
};
|
|
1799
|
+
function DevToolsOverlay({ handle, loop, ecs }) {
|
|
1800
|
+
const [, forceUpdate] = useState(0);
|
|
1801
|
+
const [paused, setPaused] = useState(false);
|
|
1802
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
1803
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
1804
|
+
const [selectedEntity, setSelectedEntity] = useState(null);
|
|
1805
|
+
useEffect(() => {
|
|
1806
|
+
handle.onFrame = () => {
|
|
1807
|
+
if (!paused) {
|
|
1808
|
+
forceUpdate((n) => n + 1);
|
|
1809
|
+
setSelectedIdx(Math.max(0, handle.buffer.length - 1));
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
return () => {
|
|
1813
|
+
handle.onFrame = undefined;
|
|
1814
|
+
};
|
|
1815
|
+
}, [handle, paused]);
|
|
1816
|
+
const totalFrames = handle.buffer.length;
|
|
1817
|
+
const currentSnap = handle.buffer[selectedIdx];
|
|
1818
|
+
const handlePauseResume = useCallback(() => {
|
|
1819
|
+
if (paused) {
|
|
1820
|
+
if (currentSnap)
|
|
1821
|
+
ecs.restoreSnapshot(currentSnap);
|
|
1822
|
+
loop.resume();
|
|
1823
|
+
setPaused(false);
|
|
1824
|
+
setSelectedEntity(null);
|
|
1825
|
+
} else {
|
|
1826
|
+
loop.pause();
|
|
1827
|
+
setPaused(true);
|
|
1828
|
+
setSelectedIdx(Math.max(0, handle.buffer.length - 1));
|
|
1829
|
+
}
|
|
1830
|
+
}, [paused, currentSnap, ecs, loop, handle]);
|
|
1831
|
+
const stepBack = useCallback(() => {
|
|
1832
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
1833
|
+
setSelectedEntity(null);
|
|
1834
|
+
}, []);
|
|
1835
|
+
const stepForward = useCallback(() => {
|
|
1836
|
+
setSelectedIdx((i) => Math.min(handle.buffer.length - 1, i + 1));
|
|
1837
|
+
setSelectedEntity(null);
|
|
1838
|
+
}, [handle]);
|
|
1839
|
+
const frameLabel = totalFrames === 0 ? "0 / 0" : `${selectedIdx + 1} / ${totalFrames}`;
|
|
1840
|
+
const entities = currentSnap?.entities ?? [];
|
|
1841
|
+
const selectedEntityData = selectedEntity !== null ? entities.find((e) => e.id === selectedEntity) : null;
|
|
1842
|
+
return createPortal(/* @__PURE__ */ jsxDEV("div", {
|
|
1843
|
+
style: css.overlay,
|
|
1844
|
+
children: [
|
|
1845
|
+
panelOpen && paused && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
1846
|
+
children: [
|
|
1847
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1848
|
+
style: css.panel,
|
|
1849
|
+
children: [
|
|
1850
|
+
entities.length === 0 && /* @__PURE__ */ jsxDEV("div", {
|
|
1851
|
+
style: { padding: "4px 14px", color: "#3d4666" },
|
|
1852
|
+
children: "No entities"
|
|
1853
|
+
}, undefined, false, undefined, this),
|
|
1854
|
+
entities.map((e) => /* @__PURE__ */ jsxDEV("div", {
|
|
1855
|
+
style: css.entityRow(selectedEntity === e.id),
|
|
1856
|
+
onClick: () => setSelectedEntity((s) => s === e.id ? null : e.id),
|
|
1857
|
+
children: [
|
|
1858
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1859
|
+
style: css.entityId,
|
|
1860
|
+
children: [
|
|
1861
|
+
"#",
|
|
1862
|
+
e.id
|
|
1863
|
+
]
|
|
1864
|
+
}, undefined, true, undefined, this),
|
|
1865
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1866
|
+
style: { display: "flex", gap: 4, flexWrap: "wrap" },
|
|
1867
|
+
children: e.components.map((c) => /* @__PURE__ */ jsxDEV("span", {
|
|
1868
|
+
style: css.compPill,
|
|
1869
|
+
children: c.type
|
|
1870
|
+
}, c.type, false, undefined, this))
|
|
1871
|
+
}, undefined, false, undefined, this)
|
|
1872
|
+
]
|
|
1873
|
+
}, e.id, true, undefined, this))
|
|
1874
|
+
]
|
|
1875
|
+
}, undefined, true, undefined, this),
|
|
1876
|
+
selectedEntityData && /* @__PURE__ */ jsxDEV("div", {
|
|
1877
|
+
style: css.detailPanel,
|
|
1878
|
+
children: selectedEntityData.components.map((comp) => /* @__PURE__ */ jsxDEV("div", {
|
|
1879
|
+
style: { marginBottom: 10 },
|
|
1880
|
+
children: [
|
|
1881
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1882
|
+
style: { color: "#4fc3f7", fontSize: 10, fontWeight: 700, marginBottom: 4 },
|
|
1883
|
+
children: comp.type
|
|
1884
|
+
}, undefined, false, undefined, this),
|
|
1885
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1886
|
+
style: css.kv,
|
|
1887
|
+
children: Object.entries(comp).filter(([k]) => k !== "type").map(([k, v]) => /* @__PURE__ */ jsxDEV(React.Fragment, {
|
|
1888
|
+
children: [
|
|
1889
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1890
|
+
style: css.key,
|
|
1891
|
+
children: k
|
|
1892
|
+
}, undefined, false, undefined, this),
|
|
1893
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1894
|
+
style: css.val,
|
|
1895
|
+
children: formatValue(v)
|
|
1896
|
+
}, undefined, false, undefined, this)
|
|
1897
|
+
]
|
|
1898
|
+
}, k, true, undefined, this))
|
|
1899
|
+
}, undefined, false, undefined, this)
|
|
1900
|
+
]
|
|
1901
|
+
}, comp.type, true, undefined, this))
|
|
1902
|
+
}, undefined, false, undefined, this)
|
|
1903
|
+
]
|
|
1904
|
+
}, undefined, true, undefined, this),
|
|
1905
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1906
|
+
style: css.bar,
|
|
1907
|
+
children: [
|
|
1908
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1909
|
+
style: css.badge,
|
|
1910
|
+
children: "DEVTOOLS"
|
|
1911
|
+
}, undefined, false, undefined, this),
|
|
1912
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1913
|
+
style: css.btn(paused),
|
|
1914
|
+
onClick: handlePauseResume,
|
|
1915
|
+
children: paused ? "▶ Resume" : "⏸ Pause"
|
|
1916
|
+
}, undefined, false, undefined, this),
|
|
1917
|
+
paused && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
1918
|
+
children: [
|
|
1919
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1920
|
+
style: css.btn(),
|
|
1921
|
+
onClick: stepBack,
|
|
1922
|
+
children: "◀◀"
|
|
1923
|
+
}, undefined, false, undefined, this),
|
|
1924
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1925
|
+
style: css.btn(),
|
|
1926
|
+
onClick: stepForward,
|
|
1927
|
+
children: "▶▶"
|
|
1928
|
+
}, undefined, false, undefined, this)
|
|
1929
|
+
]
|
|
1930
|
+
}, undefined, true, undefined, this),
|
|
1931
|
+
/* @__PURE__ */ jsxDEV("input", {
|
|
1932
|
+
type: "range",
|
|
1933
|
+
min: 0,
|
|
1934
|
+
max: Math.max(0, totalFrames - 1),
|
|
1935
|
+
value: selectedIdx,
|
|
1936
|
+
style: css.scrubber,
|
|
1937
|
+
onChange: (e) => {
|
|
1938
|
+
const idx = Number(e.target.value);
|
|
1939
|
+
setSelectedIdx(idx);
|
|
1940
|
+
setSelectedEntity(null);
|
|
1941
|
+
if (!paused) {
|
|
1942
|
+
loop.pause();
|
|
1943
|
+
setPaused(true);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}, undefined, false, undefined, this),
|
|
1947
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1948
|
+
style: css.counter,
|
|
1949
|
+
children: frameLabel
|
|
1950
|
+
}, undefined, false, undefined, this),
|
|
1951
|
+
paused && /* @__PURE__ */ jsxDEV("button", {
|
|
1952
|
+
style: css.btn(panelOpen),
|
|
1953
|
+
onClick: () => setPanelOpen((o) => !o),
|
|
1954
|
+
children: [
|
|
1955
|
+
panelOpen ? "▾" : "▸",
|
|
1956
|
+
" Entities (",
|
|
1957
|
+
entities.length,
|
|
1958
|
+
")"
|
|
1959
|
+
]
|
|
1960
|
+
}, undefined, true, undefined, this),
|
|
1961
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1962
|
+
style: css.btn(false, true),
|
|
1963
|
+
onClick: () => {
|
|
1964
|
+
handle.buffer.length = 0;
|
|
1965
|
+
setSelectedIdx(0);
|
|
1966
|
+
forceUpdate((n) => n + 1);
|
|
1967
|
+
},
|
|
1968
|
+
children: "Clear"
|
|
1969
|
+
}, undefined, false, undefined, this)
|
|
1970
|
+
]
|
|
1971
|
+
}, undefined, true, undefined, this)
|
|
1972
|
+
]
|
|
1973
|
+
}, undefined, true, undefined, this), document.body);
|
|
1974
|
+
}
|
|
1975
|
+
function formatValue(v) {
|
|
1976
|
+
if (typeof v === "number")
|
|
1977
|
+
return v.toFixed(2);
|
|
1978
|
+
if (typeof v === "boolean")
|
|
1979
|
+
return v ? "true" : "false";
|
|
1980
|
+
if (v === null || v === undefined)
|
|
1981
|
+
return "—";
|
|
1982
|
+
if (typeof v === "object")
|
|
1983
|
+
return JSON.stringify(v).slice(0, 60);
|
|
1984
|
+
return String(v);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1256
1987
|
// src/components/Game.tsx
|
|
1257
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1988
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
1258
1989
|
function Game({
|
|
1259
1990
|
width = 800,
|
|
1260
1991
|
height = 600,
|
|
1261
1992
|
gravity = 980,
|
|
1262
1993
|
debug = false,
|
|
1994
|
+
devtools = false,
|
|
1263
1995
|
scale = "none",
|
|
1996
|
+
deterministic = false,
|
|
1997
|
+
seed = 0,
|
|
1264
1998
|
onReady,
|
|
1265
1999
|
plugins,
|
|
2000
|
+
renderer: CustomRenderer,
|
|
1266
2001
|
style,
|
|
1267
2002
|
className,
|
|
1268
2003
|
children
|
|
1269
2004
|
}) {
|
|
1270
2005
|
const canvasRef = useRef(null);
|
|
1271
2006
|
const wrapperRef = useRef(null);
|
|
1272
|
-
const [engine, setEngine] =
|
|
1273
|
-
|
|
2007
|
+
const [engine, setEngine] = useState2(null);
|
|
2008
|
+
const devtoolsHandle = useRef({ buffer: [] });
|
|
2009
|
+
useEffect2(() => {
|
|
1274
2010
|
const canvas = canvasRef.current;
|
|
1275
2011
|
const ecs = new ECSWorld;
|
|
2012
|
+
if (deterministic)
|
|
2013
|
+
ecs.setDeterministicSeed(seed);
|
|
1276
2014
|
const input = new InputManager;
|
|
1277
|
-
const renderer = new Canvas2DRenderer(canvas);
|
|
1278
2015
|
const events = new EventBus;
|
|
1279
2016
|
const assets = new AssetManager;
|
|
1280
2017
|
const physics = new PhysicsSystem(gravity, events);
|
|
1281
2018
|
const entityIds = new Map;
|
|
1282
|
-
|
|
1283
|
-
|
|
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;
|
|
1284
2030
|
ecs.addSystem(new ScriptSystem(input));
|
|
1285
2031
|
ecs.addSystem(physics);
|
|
1286
2032
|
ecs.addSystem(renderSystem2);
|
|
@@ -1294,8 +2040,15 @@ function Game({
|
|
|
1294
2040
|
const loop = new GameLoop((dt) => {
|
|
1295
2041
|
ecs.update(dt);
|
|
1296
2042
|
input.flush();
|
|
2043
|
+
if (devtools) {
|
|
2044
|
+
const handle = devtoolsHandle.current;
|
|
2045
|
+
handle.buffer.push(ecs.getSnapshot());
|
|
2046
|
+
if (handle.buffer.length > MAX_DEVTOOLS_FRAMES)
|
|
2047
|
+
handle.buffer.shift();
|
|
2048
|
+
handle.onFrame?.();
|
|
2049
|
+
}
|
|
1297
2050
|
});
|
|
1298
|
-
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 };
|
|
1299
2052
|
setEngine(state);
|
|
1300
2053
|
if (plugins) {
|
|
1301
2054
|
for (const plugin2 of plugins) {
|
|
@@ -1339,7 +2092,7 @@ function Game({
|
|
|
1339
2092
|
resizeObserver?.disconnect();
|
|
1340
2093
|
};
|
|
1341
2094
|
}, []);
|
|
1342
|
-
|
|
2095
|
+
useEffect2(() => {
|
|
1343
2096
|
engine?.physics.setGravity(gravity);
|
|
1344
2097
|
}, [gravity, engine]);
|
|
1345
2098
|
const canvasStyle = {
|
|
@@ -1349,13 +2102,13 @@ function Game({
|
|
|
1349
2102
|
...style
|
|
1350
2103
|
};
|
|
1351
2104
|
const wrapperStyle = scale === "contain" ? { position: "relative", width, height, overflow: "visible" } : {};
|
|
1352
|
-
return /* @__PURE__ */
|
|
2105
|
+
return /* @__PURE__ */ jsxDEV2(EngineContext.Provider, {
|
|
1353
2106
|
value: engine,
|
|
1354
2107
|
children: [
|
|
1355
|
-
/* @__PURE__ */
|
|
2108
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
1356
2109
|
ref: wrapperRef,
|
|
1357
2110
|
style: wrapperStyle,
|
|
1358
|
-
children: /* @__PURE__ */
|
|
2111
|
+
children: /* @__PURE__ */ jsxDEV2("canvas", {
|
|
1359
2112
|
ref: canvasRef,
|
|
1360
2113
|
width,
|
|
1361
2114
|
height,
|
|
@@ -1363,22 +2116,27 @@ function Game({
|
|
|
1363
2116
|
className
|
|
1364
2117
|
}, undefined, false, undefined, this)
|
|
1365
2118
|
}, undefined, false, undefined, this),
|
|
1366
|
-
engine && children
|
|
2119
|
+
engine && children,
|
|
2120
|
+
engine && devtools && /* @__PURE__ */ jsxDEV2(DevToolsOverlay, {
|
|
2121
|
+
handle: devtoolsHandle.current,
|
|
2122
|
+
loop: engine.loop,
|
|
2123
|
+
ecs: engine.ecs
|
|
2124
|
+
}, undefined, false, undefined, this)
|
|
1367
2125
|
]
|
|
1368
2126
|
}, undefined, true, undefined, this);
|
|
1369
2127
|
}
|
|
1370
2128
|
// src/components/World.tsx
|
|
1371
|
-
import { useEffect as
|
|
1372
|
-
import { jsxDEV as
|
|
2129
|
+
import { useEffect as useEffect3, useContext } from "react";
|
|
2130
|
+
import { jsxDEV as jsxDEV3, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
1373
2131
|
function World({ gravity, background = "#1a1a2e", children }) {
|
|
1374
2132
|
const engine = useContext(EngineContext);
|
|
1375
|
-
|
|
2133
|
+
useEffect3(() => {
|
|
1376
2134
|
if (!engine)
|
|
1377
2135
|
return;
|
|
1378
2136
|
if (gravity !== undefined)
|
|
1379
2137
|
engine.physics.setGravity(gravity);
|
|
1380
2138
|
}, [gravity, engine]);
|
|
1381
|
-
|
|
2139
|
+
useEffect3(() => {
|
|
1382
2140
|
if (!engine)
|
|
1383
2141
|
return;
|
|
1384
2142
|
const camId = engine.ecs.queryOne("Camera2D");
|
|
@@ -1390,17 +2148,17 @@ function World({ gravity, background = "#1a1a2e", children }) {
|
|
|
1390
2148
|
engine.canvas.style.background = background;
|
|
1391
2149
|
}
|
|
1392
2150
|
}, [background, engine]);
|
|
1393
|
-
return /* @__PURE__ */
|
|
2151
|
+
return /* @__PURE__ */ jsxDEV3(Fragment2, {
|
|
1394
2152
|
children
|
|
1395
2153
|
}, undefined, false, undefined, this);
|
|
1396
2154
|
}
|
|
1397
2155
|
// src/components/Entity.tsx
|
|
1398
|
-
import { useEffect as
|
|
1399
|
-
import { jsxDEV as
|
|
2156
|
+
import { useEffect as useEffect4, useContext as useContext2, useState as useState3 } from "react";
|
|
2157
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
1400
2158
|
function Entity({ id, tags = [], children }) {
|
|
1401
2159
|
const engine = useContext2(EngineContext);
|
|
1402
|
-
const [entityId, setEntityId] =
|
|
1403
|
-
|
|
2160
|
+
const [entityId, setEntityId] = useState3(null);
|
|
2161
|
+
useEffect4(() => {
|
|
1404
2162
|
const eid = engine.ecs.createEntity();
|
|
1405
2163
|
if (id) {
|
|
1406
2164
|
if (engine.entityIds.has(id)) {
|
|
@@ -1419,21 +2177,21 @@ function Entity({ id, tags = [], children }) {
|
|
|
1419
2177
|
}, []);
|
|
1420
2178
|
if (entityId === null)
|
|
1421
2179
|
return null;
|
|
1422
|
-
return /* @__PURE__ */
|
|
2180
|
+
return /* @__PURE__ */ jsxDEV4(EntityContext.Provider, {
|
|
1423
2181
|
value: entityId,
|
|
1424
2182
|
children
|
|
1425
2183
|
}, undefined, false, undefined, this);
|
|
1426
2184
|
}
|
|
1427
2185
|
// src/components/Transform.tsx
|
|
1428
|
-
import { useEffect as
|
|
2186
|
+
import { useEffect as useEffect5, useContext as useContext3 } from "react";
|
|
1429
2187
|
function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
|
|
1430
2188
|
const engine = useContext3(EngineContext);
|
|
1431
2189
|
const entityId = useContext3(EntityContext);
|
|
1432
|
-
|
|
2190
|
+
useEffect5(() => {
|
|
1433
2191
|
engine.ecs.addComponent(entityId, createTransform(x, y, rotation, scaleX, scaleY));
|
|
1434
2192
|
return () => engine.ecs.removeComponent(entityId, "Transform");
|
|
1435
2193
|
}, []);
|
|
1436
|
-
|
|
2194
|
+
useEffect5(() => {
|
|
1437
2195
|
const comp = engine.ecs.getComponent(entityId, "Transform");
|
|
1438
2196
|
if (comp) {
|
|
1439
2197
|
comp.x = x;
|
|
@@ -1446,7 +2204,7 @@ function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
|
|
|
1446
2204
|
return null;
|
|
1447
2205
|
}
|
|
1448
2206
|
// src/components/Sprite.tsx
|
|
1449
|
-
import { useEffect as
|
|
2207
|
+
import { useEffect as useEffect6, useContext as useContext4 } from "react";
|
|
1450
2208
|
function Sprite({
|
|
1451
2209
|
width,
|
|
1452
2210
|
height,
|
|
@@ -1469,7 +2227,7 @@ function Sprite({
|
|
|
1469
2227
|
const resolvedFrameIndex = atlas && frame != null ? atlas[frame] ?? 0 : frameIndex;
|
|
1470
2228
|
const engine = useContext4(EngineContext);
|
|
1471
2229
|
const entityId = useContext4(EntityContext);
|
|
1472
|
-
|
|
2230
|
+
useEffect6(() => {
|
|
1473
2231
|
const comp = createSprite({
|
|
1474
2232
|
width,
|
|
1475
2233
|
height,
|
|
@@ -1497,7 +2255,7 @@ function Sprite({
|
|
|
1497
2255
|
}
|
|
1498
2256
|
return () => engine.ecs.removeComponent(entityId, "Sprite");
|
|
1499
2257
|
}, []);
|
|
1500
|
-
|
|
2258
|
+
useEffect6(() => {
|
|
1501
2259
|
const comp = engine.ecs.getComponent(entityId, "Sprite");
|
|
1502
2260
|
if (!comp)
|
|
1503
2261
|
return;
|
|
@@ -1510,7 +2268,7 @@ function Sprite({
|
|
|
1510
2268
|
return null;
|
|
1511
2269
|
}
|
|
1512
2270
|
// src/components/RigidBody.tsx
|
|
1513
|
-
import { useEffect as
|
|
2271
|
+
import { useEffect as useEffect7, useContext as useContext5 } from "react";
|
|
1514
2272
|
function RigidBody({
|
|
1515
2273
|
mass = 1,
|
|
1516
2274
|
gravityScale = 1,
|
|
@@ -1518,30 +2276,34 @@ function RigidBody({
|
|
|
1518
2276
|
bounce = 0,
|
|
1519
2277
|
friction = 0.85,
|
|
1520
2278
|
vx = 0,
|
|
1521
|
-
vy = 0
|
|
2279
|
+
vy = 0,
|
|
2280
|
+
lockX = false,
|
|
2281
|
+
lockY = false
|
|
1522
2282
|
}) {
|
|
1523
2283
|
const engine = useContext5(EngineContext);
|
|
1524
2284
|
const entityId = useContext5(EntityContext);
|
|
1525
|
-
|
|
1526
|
-
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy }));
|
|
2285
|
+
useEffect7(() => {
|
|
2286
|
+
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY }));
|
|
1527
2287
|
return () => engine.ecs.removeComponent(entityId, "RigidBody");
|
|
1528
2288
|
}, []);
|
|
1529
2289
|
return null;
|
|
1530
2290
|
}
|
|
1531
2291
|
// src/components/BoxCollider.tsx
|
|
1532
|
-
import { useEffect as
|
|
2292
|
+
import { useEffect as useEffect8, useContext as useContext6 } from "react";
|
|
1533
2293
|
function BoxCollider({
|
|
1534
2294
|
width,
|
|
1535
2295
|
height,
|
|
1536
2296
|
offsetX = 0,
|
|
1537
2297
|
offsetY = 0,
|
|
1538
2298
|
isTrigger = false,
|
|
1539
|
-
layer = "default"
|
|
2299
|
+
layer = "default",
|
|
2300
|
+
mask = "*",
|
|
2301
|
+
oneWay = false
|
|
1540
2302
|
}) {
|
|
1541
2303
|
const engine = useContext6(EngineContext);
|
|
1542
2304
|
const entityId = useContext6(EntityContext);
|
|
1543
|
-
|
|
1544
|
-
engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer }));
|
|
2305
|
+
useEffect8(() => {
|
|
2306
|
+
engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer, mask, oneWay }));
|
|
1545
2307
|
const checkId = setTimeout(() => {
|
|
1546
2308
|
if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
|
|
1547
2309
|
console.warn(`[Cubeforge] BoxCollider on entity ${entityId} has no Transform. Physics requires Transform.`);
|
|
@@ -1554,12 +2316,30 @@ function BoxCollider({
|
|
|
1554
2316
|
}, []);
|
|
1555
2317
|
return null;
|
|
1556
2318
|
}
|
|
1557
|
-
// src/components/
|
|
1558
|
-
import { useEffect as
|
|
1559
|
-
function
|
|
2319
|
+
// src/components/CircleCollider.tsx
|
|
2320
|
+
import { useEffect as useEffect9, useContext as useContext7 } from "react";
|
|
2321
|
+
function CircleCollider({
|
|
2322
|
+
radius,
|
|
2323
|
+
offsetX = 0,
|
|
2324
|
+
offsetY = 0,
|
|
2325
|
+
isTrigger = false,
|
|
2326
|
+
layer = "default",
|
|
2327
|
+
mask = "*"
|
|
2328
|
+
}) {
|
|
1560
2329
|
const engine = useContext7(EngineContext);
|
|
1561
2330
|
const entityId = useContext7(EntityContext);
|
|
1562
|
-
|
|
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(() => {
|
|
1563
2343
|
if (init) {
|
|
1564
2344
|
try {
|
|
1565
2345
|
init(entityId, engine.ecs);
|
|
@@ -1573,17 +2353,19 @@ function Script({ init, update }) {
|
|
|
1573
2353
|
return null;
|
|
1574
2354
|
}
|
|
1575
2355
|
// src/components/Camera2D.tsx
|
|
1576
|
-
import { useEffect as
|
|
2356
|
+
import { useEffect as useEffect11, useContext as useContext9 } from "react";
|
|
1577
2357
|
function Camera2D({
|
|
1578
2358
|
followEntity,
|
|
1579
2359
|
zoom = 1,
|
|
1580
2360
|
smoothing = 0,
|
|
1581
2361
|
background = "#1a1a2e",
|
|
1582
2362
|
bounds,
|
|
1583
|
-
deadZone
|
|
2363
|
+
deadZone,
|
|
2364
|
+
followOffsetX = 0,
|
|
2365
|
+
followOffsetY = 0
|
|
1584
2366
|
}) {
|
|
1585
|
-
const engine =
|
|
1586
|
-
|
|
2367
|
+
const engine = useContext9(EngineContext);
|
|
2368
|
+
useEffect11(() => {
|
|
1587
2369
|
const entityId = engine.ecs.createEntity();
|
|
1588
2370
|
engine.ecs.addComponent(entityId, createCamera2D({
|
|
1589
2371
|
followEntityId: followEntity,
|
|
@@ -1591,11 +2373,13 @@ function Camera2D({
|
|
|
1591
2373
|
smoothing,
|
|
1592
2374
|
background,
|
|
1593
2375
|
bounds,
|
|
1594
|
-
deadZone
|
|
2376
|
+
deadZone,
|
|
2377
|
+
followOffsetX,
|
|
2378
|
+
followOffsetY
|
|
1595
2379
|
}));
|
|
1596
2380
|
return () => engine.ecs.destroyEntity(entityId);
|
|
1597
2381
|
}, []);
|
|
1598
|
-
|
|
2382
|
+
useEffect11(() => {
|
|
1599
2383
|
const camId = engine.ecs.queryOne("Camera2D");
|
|
1600
2384
|
if (camId === undefined)
|
|
1601
2385
|
return;
|
|
@@ -1606,15 +2390,17 @@ function Camera2D({
|
|
|
1606
2390
|
cam.background = background;
|
|
1607
2391
|
cam.bounds = bounds;
|
|
1608
2392
|
cam.deadZone = deadZone;
|
|
1609
|
-
|
|
2393
|
+
cam.followOffsetX = followOffsetX;
|
|
2394
|
+
cam.followOffsetY = followOffsetY;
|
|
2395
|
+
}, [followEntity, zoom, smoothing, background, bounds, deadZone, followOffsetX, followOffsetY, engine]);
|
|
1610
2396
|
return null;
|
|
1611
2397
|
}
|
|
1612
2398
|
// src/components/Animation.tsx
|
|
1613
|
-
import { useEffect as
|
|
1614
|
-
function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
1615
|
-
const engine =
|
|
1616
|
-
const entityId =
|
|
1617
|
-
|
|
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(() => {
|
|
1618
2404
|
const state = {
|
|
1619
2405
|
type: "AnimationState",
|
|
1620
2406
|
frames,
|
|
@@ -1622,29 +2408,39 @@ function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
|
1622
2408
|
loop,
|
|
1623
2409
|
playing,
|
|
1624
2410
|
currentIndex: 0,
|
|
1625
|
-
timer: 0
|
|
2411
|
+
timer: 0,
|
|
2412
|
+
_completed: false,
|
|
2413
|
+
onComplete
|
|
1626
2414
|
};
|
|
1627
2415
|
engine.ecs.addComponent(entityId, state);
|
|
1628
2416
|
return () => {
|
|
1629
2417
|
engine.ecs.removeComponent(entityId, "AnimationState");
|
|
1630
2418
|
};
|
|
1631
2419
|
}, []);
|
|
1632
|
-
|
|
2420
|
+
useEffect12(() => {
|
|
1633
2421
|
const anim = engine.ecs.getComponent(entityId, "AnimationState");
|
|
1634
2422
|
if (!anim)
|
|
1635
2423
|
return;
|
|
2424
|
+
const wasFramesChanged = anim.frames !== frames;
|
|
1636
2425
|
anim.playing = playing;
|
|
1637
2426
|
anim.fps = fps;
|
|
1638
2427
|
anim.loop = loop;
|
|
1639
|
-
|
|
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]);
|
|
1640
2436
|
return null;
|
|
1641
2437
|
}
|
|
1642
2438
|
// src/components/SquashStretch.tsx
|
|
1643
|
-
import { useEffect as
|
|
2439
|
+
import { useEffect as useEffect13, useContext as useContext11 } from "react";
|
|
1644
2440
|
function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
1645
|
-
const engine =
|
|
1646
|
-
const entityId =
|
|
1647
|
-
|
|
2441
|
+
const engine = useContext11(EngineContext);
|
|
2442
|
+
const entityId = useContext11(EntityContext);
|
|
2443
|
+
useEffect13(() => {
|
|
1648
2444
|
engine.ecs.addComponent(entityId, {
|
|
1649
2445
|
type: "SquashStretch",
|
|
1650
2446
|
intensity,
|
|
@@ -1657,7 +2453,7 @@ function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
|
1657
2453
|
return null;
|
|
1658
2454
|
}
|
|
1659
2455
|
// src/components/ParticleEmitter.tsx
|
|
1660
|
-
import { useEffect as
|
|
2456
|
+
import { useEffect as useEffect14, useContext as useContext12 } from "react";
|
|
1661
2457
|
|
|
1662
2458
|
// src/components/particlePresets.ts
|
|
1663
2459
|
var PARTICLE_PRESETS = {
|
|
@@ -1742,9 +2538,9 @@ function ParticleEmitter({
|
|
|
1742
2538
|
const resolvedColor = color ?? presetConfig.color ?? "#ffffff";
|
|
1743
2539
|
const resolvedGravity = gravity ?? presetConfig.gravity ?? 200;
|
|
1744
2540
|
const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
|
|
1745
|
-
const engine =
|
|
1746
|
-
const entityId =
|
|
1747
|
-
|
|
2541
|
+
const engine = useContext12(EngineContext);
|
|
2542
|
+
const entityId = useContext12(EntityContext);
|
|
2543
|
+
useEffect14(() => {
|
|
1748
2544
|
engine.ecs.addComponent(entityId, {
|
|
1749
2545
|
type: "ParticlePool",
|
|
1750
2546
|
particles: [],
|
|
@@ -1762,7 +2558,7 @@ function ParticleEmitter({
|
|
|
1762
2558
|
});
|
|
1763
2559
|
return () => engine.ecs.removeComponent(entityId, "ParticlePool");
|
|
1764
2560
|
}, []);
|
|
1765
|
-
|
|
2561
|
+
useEffect14(() => {
|
|
1766
2562
|
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
1767
2563
|
if (!pool)
|
|
1768
2564
|
return;
|
|
@@ -1771,7 +2567,7 @@ function ParticleEmitter({
|
|
|
1771
2567
|
return null;
|
|
1772
2568
|
}
|
|
1773
2569
|
// src/components/MovingPlatform.tsx
|
|
1774
|
-
import { jsxDEV as
|
|
2570
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
1775
2571
|
var platformPhases = new Map;
|
|
1776
2572
|
function MovingPlatform({
|
|
1777
2573
|
x1,
|
|
@@ -1783,26 +2579,26 @@ function MovingPlatform({
|
|
|
1783
2579
|
duration = 3,
|
|
1784
2580
|
color = "#37474f"
|
|
1785
2581
|
}) {
|
|
1786
|
-
return /* @__PURE__ */
|
|
2582
|
+
return /* @__PURE__ */ jsxDEV5(Entity, {
|
|
1787
2583
|
children: [
|
|
1788
|
-
/* @__PURE__ */
|
|
2584
|
+
/* @__PURE__ */ jsxDEV5(Transform, {
|
|
1789
2585
|
x: x1,
|
|
1790
2586
|
y: y1
|
|
1791
2587
|
}, undefined, false, undefined, this),
|
|
1792
|
-
/* @__PURE__ */
|
|
2588
|
+
/* @__PURE__ */ jsxDEV5(Sprite, {
|
|
1793
2589
|
width,
|
|
1794
2590
|
height,
|
|
1795
2591
|
color,
|
|
1796
2592
|
zIndex: 5
|
|
1797
2593
|
}, undefined, false, undefined, this),
|
|
1798
|
-
/* @__PURE__ */
|
|
2594
|
+
/* @__PURE__ */ jsxDEV5(RigidBody, {
|
|
1799
2595
|
isStatic: true
|
|
1800
2596
|
}, undefined, false, undefined, this),
|
|
1801
|
-
/* @__PURE__ */
|
|
2597
|
+
/* @__PURE__ */ jsxDEV5(BoxCollider, {
|
|
1802
2598
|
width,
|
|
1803
2599
|
height
|
|
1804
2600
|
}, undefined, false, undefined, this),
|
|
1805
|
-
/* @__PURE__ */
|
|
2601
|
+
/* @__PURE__ */ jsxDEV5(Script, {
|
|
1806
2602
|
init: () => {},
|
|
1807
2603
|
update: (id, world2, _input, dt) => {
|
|
1808
2604
|
if (!world2.hasEntity(id))
|
|
@@ -1821,7 +2617,69 @@ function MovingPlatform({
|
|
|
1821
2617
|
}, undefined, true, undefined, this);
|
|
1822
2618
|
}
|
|
1823
2619
|
// src/components/Checkpoint.tsx
|
|
1824
|
-
import {
|
|
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
|
+
|
|
2671
|
+
// src/components/Checkpoint.tsx
|
|
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
|
+
}
|
|
1825
2683
|
function Checkpoint({
|
|
1826
2684
|
x,
|
|
1827
2685
|
y,
|
|
@@ -1830,55 +2688,33 @@ function Checkpoint({
|
|
|
1830
2688
|
color = "#ffd54f",
|
|
1831
2689
|
onActivate
|
|
1832
2690
|
}) {
|
|
1833
|
-
return /* @__PURE__ */
|
|
2691
|
+
return /* @__PURE__ */ jsxDEV6(Entity, {
|
|
1834
2692
|
tags: ["checkpoint"],
|
|
1835
2693
|
children: [
|
|
1836
|
-
/* @__PURE__ */
|
|
2694
|
+
/* @__PURE__ */ jsxDEV6(Transform, {
|
|
1837
2695
|
x,
|
|
1838
2696
|
y
|
|
1839
2697
|
}, undefined, false, undefined, this),
|
|
1840
|
-
/* @__PURE__ */
|
|
2698
|
+
/* @__PURE__ */ jsxDEV6(Sprite, {
|
|
1841
2699
|
width,
|
|
1842
2700
|
height,
|
|
1843
2701
|
color,
|
|
1844
2702
|
zIndex: 5
|
|
1845
2703
|
}, undefined, false, undefined, this),
|
|
1846
|
-
/* @__PURE__ */
|
|
2704
|
+
/* @__PURE__ */ jsxDEV6(BoxCollider, {
|
|
1847
2705
|
width,
|
|
1848
2706
|
height,
|
|
1849
2707
|
isTrigger: true
|
|
1850
2708
|
}, undefined, false, undefined, this),
|
|
1851
|
-
/* @__PURE__ */
|
|
1852
|
-
|
|
1853
|
-
update: (id, world2) => {
|
|
1854
|
-
if (!world2.hasEntity(id))
|
|
1855
|
-
return;
|
|
1856
|
-
const ct = world2.getComponent(id, "Transform");
|
|
1857
|
-
if (!ct)
|
|
1858
|
-
return;
|
|
1859
|
-
for (const pid of world2.query("Tag")) {
|
|
1860
|
-
const tag2 = world2.getComponent(pid, "Tag");
|
|
1861
|
-
if (!tag2?.tags.includes("player"))
|
|
1862
|
-
continue;
|
|
1863
|
-
const pt = world2.getComponent(pid, "Transform");
|
|
1864
|
-
if (!pt)
|
|
1865
|
-
continue;
|
|
1866
|
-
const dx = Math.abs(pt.x - ct.x);
|
|
1867
|
-
const dy = Math.abs(pt.y - ct.y);
|
|
1868
|
-
if (dx < width / 2 + 16 && dy < height / 2 + 20) {
|
|
1869
|
-
onActivate?.();
|
|
1870
|
-
world2.destroyEntity(id);
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
2709
|
+
/* @__PURE__ */ jsxDEV6(CheckpointActivator, {
|
|
2710
|
+
onActivate
|
|
1875
2711
|
}, undefined, false, undefined, this)
|
|
1876
2712
|
]
|
|
1877
2713
|
}, undefined, true, undefined, this);
|
|
1878
2714
|
}
|
|
1879
2715
|
// src/components/Tilemap.tsx
|
|
1880
|
-
import { useEffect as
|
|
1881
|
-
import { jsxDEV as
|
|
2716
|
+
import { useEffect as useEffect16, useState as useState5, useContext as useContext14 } from "react";
|
|
2717
|
+
import { jsxDEV as jsxDEV7, Fragment as Fragment3 } from "react/jsx-dev-runtime";
|
|
1882
2718
|
var animatedTiles = new Map;
|
|
1883
2719
|
function getProperty(props, name) {
|
|
1884
2720
|
return props?.find((p) => p.name === name)?.value;
|
|
@@ -1901,9 +2737,9 @@ function Tilemap({
|
|
|
1901
2737
|
triggerLayer: triggerLayerName = "triggers",
|
|
1902
2738
|
onTileProperty
|
|
1903
2739
|
}) {
|
|
1904
|
-
const engine =
|
|
1905
|
-
const [spawnedNodes, setSpawnedNodes] =
|
|
1906
|
-
|
|
2740
|
+
const engine = useContext14(EngineContext);
|
|
2741
|
+
const [spawnedNodes, setSpawnedNodes] = useState5([]);
|
|
2742
|
+
useEffect16(() => {
|
|
1907
2743
|
if (!engine)
|
|
1908
2744
|
return;
|
|
1909
2745
|
const createdEntities = [];
|
|
@@ -2094,13 +2930,13 @@ function Tilemap({
|
|
|
2094
2930
|
}, [src]);
|
|
2095
2931
|
if (spawnedNodes.length === 0)
|
|
2096
2932
|
return null;
|
|
2097
|
-
return /* @__PURE__ */
|
|
2933
|
+
return /* @__PURE__ */ jsxDEV7(Fragment3, {
|
|
2098
2934
|
children: spawnedNodes
|
|
2099
2935
|
}, undefined, false, undefined, this);
|
|
2100
2936
|
}
|
|
2101
2937
|
// src/components/ParallaxLayer.tsx
|
|
2102
|
-
import { useEffect as
|
|
2103
|
-
import { jsxDEV as
|
|
2938
|
+
import { useEffect as useEffect17, useContext as useContext15 } from "react";
|
|
2939
|
+
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
2104
2940
|
function ParallaxLayerInner({
|
|
2105
2941
|
src,
|
|
2106
2942
|
speedX,
|
|
@@ -2111,9 +2947,9 @@ function ParallaxLayerInner({
|
|
|
2111
2947
|
offsetX,
|
|
2112
2948
|
offsetY
|
|
2113
2949
|
}) {
|
|
2114
|
-
const engine =
|
|
2115
|
-
const entityId =
|
|
2116
|
-
|
|
2950
|
+
const engine = useContext15(EngineContext);
|
|
2951
|
+
const entityId = useContext15(EntityContext);
|
|
2952
|
+
useEffect17(() => {
|
|
2117
2953
|
engine.ecs.addComponent(entityId, {
|
|
2118
2954
|
type: "ParallaxLayer",
|
|
2119
2955
|
src,
|
|
@@ -2129,7 +2965,7 @@ function ParallaxLayerInner({
|
|
|
2129
2965
|
});
|
|
2130
2966
|
return () => engine.ecs.removeComponent(entityId, "ParallaxLayer");
|
|
2131
2967
|
}, []);
|
|
2132
|
-
|
|
2968
|
+
useEffect17(() => {
|
|
2133
2969
|
const layer = engine.ecs.getComponent(entityId, "ParallaxLayer");
|
|
2134
2970
|
if (!layer)
|
|
2135
2971
|
return;
|
|
@@ -2154,13 +2990,13 @@ function ParallaxLayer({
|
|
|
2154
2990
|
offsetX = 0,
|
|
2155
2991
|
offsetY = 0
|
|
2156
2992
|
}) {
|
|
2157
|
-
return /* @__PURE__ */
|
|
2993
|
+
return /* @__PURE__ */ jsxDEV8(Entity, {
|
|
2158
2994
|
children: [
|
|
2159
|
-
/* @__PURE__ */
|
|
2995
|
+
/* @__PURE__ */ jsxDEV8(Transform, {
|
|
2160
2996
|
x: 0,
|
|
2161
2997
|
y: 0
|
|
2162
2998
|
}, undefined, false, undefined, this),
|
|
2163
|
-
/* @__PURE__ */
|
|
2999
|
+
/* @__PURE__ */ jsxDEV8(ParallaxLayerInner, {
|
|
2164
3000
|
src,
|
|
2165
3001
|
speedX,
|
|
2166
3002
|
speedY,
|
|
@@ -2175,7 +3011,7 @@ function ParallaxLayer({
|
|
|
2175
3011
|
}
|
|
2176
3012
|
// src/components/ScreenFlash.tsx
|
|
2177
3013
|
import { forwardRef, useImperativeHandle, useRef as useRef2 } from "react";
|
|
2178
|
-
import { jsxDEV as
|
|
3014
|
+
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
2179
3015
|
var ScreenFlash = forwardRef((_, ref) => {
|
|
2180
3016
|
const divRef = useRef2(null);
|
|
2181
3017
|
useImperativeHandle(ref, () => ({
|
|
@@ -2197,7 +3033,7 @@ var ScreenFlash = forwardRef((_, ref) => {
|
|
|
2197
3033
|
});
|
|
2198
3034
|
}
|
|
2199
3035
|
}));
|
|
2200
|
-
return /* @__PURE__ */
|
|
3036
|
+
return /* @__PURE__ */ jsxDEV9("div", {
|
|
2201
3037
|
ref: divRef,
|
|
2202
3038
|
style: {
|
|
2203
3039
|
position: "absolute",
|
|
@@ -2211,47 +3047,111 @@ var ScreenFlash = forwardRef((_, ref) => {
|
|
|
2211
3047
|
});
|
|
2212
3048
|
ScreenFlash.displayName = "ScreenFlash";
|
|
2213
3049
|
// src/hooks/useGame.ts
|
|
2214
|
-
import { useContext as
|
|
3050
|
+
import { useContext as useContext16 } from "react";
|
|
2215
3051
|
function useGame() {
|
|
2216
|
-
const engine =
|
|
3052
|
+
const engine = useContext16(EngineContext);
|
|
2217
3053
|
if (!engine)
|
|
2218
3054
|
throw new Error("useGame must be used inside <Game>");
|
|
2219
3055
|
return engine;
|
|
2220
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
|
+
}
|
|
2221
3104
|
// src/hooks/useEntity.ts
|
|
2222
|
-
import { useContext as
|
|
3105
|
+
import { useContext as useContext17 } from "react";
|
|
2223
3106
|
function useEntity() {
|
|
2224
|
-
const id =
|
|
3107
|
+
const id = useContext17(EntityContext);
|
|
2225
3108
|
if (id === null)
|
|
2226
3109
|
throw new Error("useEntity must be used inside <Entity>");
|
|
2227
3110
|
return id;
|
|
2228
3111
|
}
|
|
2229
3112
|
// src/hooks/useInput.ts
|
|
2230
|
-
import { useContext as
|
|
3113
|
+
import { useContext as useContext18 } from "react";
|
|
2231
3114
|
function useInput() {
|
|
2232
|
-
const engine =
|
|
3115
|
+
const engine = useContext18(EngineContext);
|
|
2233
3116
|
if (!engine)
|
|
2234
3117
|
throw new Error("useInput must be used inside <Game>");
|
|
2235
3118
|
return engine.input;
|
|
2236
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
|
+
}
|
|
2237
3137
|
// src/hooks/useEvents.ts
|
|
2238
|
-
import { useContext as
|
|
3138
|
+
import { useContext as useContext19, useEffect as useEffect18 } from "react";
|
|
2239
3139
|
function useEvents() {
|
|
2240
|
-
const engine =
|
|
3140
|
+
const engine = useContext19(EngineContext);
|
|
2241
3141
|
if (!engine)
|
|
2242
3142
|
throw new Error("useEvents must be used inside <Game>");
|
|
2243
3143
|
return engine.events;
|
|
2244
3144
|
}
|
|
2245
3145
|
function useEvent(event, handler) {
|
|
2246
3146
|
const events = useEvents();
|
|
2247
|
-
|
|
3147
|
+
useEffect18(() => {
|
|
2248
3148
|
return events.on(event, handler);
|
|
2249
3149
|
}, [events, event]);
|
|
2250
3150
|
}
|
|
2251
3151
|
// src/hooks/usePlatformerController.ts
|
|
2252
|
-
import { useContext as
|
|
3152
|
+
import { useContext as useContext20, useEffect as useEffect19 } from "react";
|
|
2253
3153
|
function usePlatformerController(entityId, opts = {}) {
|
|
2254
|
-
const engine =
|
|
3154
|
+
const engine = useContext20(EngineContext);
|
|
2255
3155
|
const {
|
|
2256
3156
|
speed = 200,
|
|
2257
3157
|
jumpForce = -500,
|
|
@@ -2259,7 +3159,7 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
2259
3159
|
coyoteTime = 0.08,
|
|
2260
3160
|
jumpBuffer = 0.08
|
|
2261
3161
|
} = opts;
|
|
2262
|
-
|
|
3162
|
+
useEffect19(() => {
|
|
2263
3163
|
const state = { coyoteTimer: 0, jumpBuffer: 0, jumpsLeft: maxJumps };
|
|
2264
3164
|
const updateFn = (id, world2, input, dt) => {
|
|
2265
3165
|
if (!world2.hasEntity(id))
|
|
@@ -2308,11 +3208,11 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
2308
3208
|
}, []);
|
|
2309
3209
|
}
|
|
2310
3210
|
// src/hooks/useTopDownMovement.ts
|
|
2311
|
-
import { useContext as
|
|
3211
|
+
import { useContext as useContext21, useEffect as useEffect20 } from "react";
|
|
2312
3212
|
function useTopDownMovement(entityId, opts = {}) {
|
|
2313
|
-
const engine =
|
|
3213
|
+
const engine = useContext21(EngineContext);
|
|
2314
3214
|
const { speed = 200, normalizeDiagonal = true } = opts;
|
|
2315
|
-
|
|
3215
|
+
useEffect20(() => {
|
|
2316
3216
|
const updateFn = (id, world2, input) => {
|
|
2317
3217
|
if (!world2.hasEntity(id))
|
|
2318
3218
|
return;
|
|
@@ -2346,15 +3246,28 @@ function createAtlas(names, _columns) {
|
|
|
2346
3246
|
return atlas;
|
|
2347
3247
|
}
|
|
2348
3248
|
export {
|
|
3249
|
+
useTriggerExit,
|
|
3250
|
+
useTriggerEnter,
|
|
2349
3251
|
useTopDownMovement,
|
|
3252
|
+
useSnapshot,
|
|
2350
3253
|
usePlatformerController,
|
|
3254
|
+
useInputMap,
|
|
2351
3255
|
useInput,
|
|
2352
3256
|
useGame,
|
|
2353
3257
|
useEvents,
|
|
2354
3258
|
useEvent,
|
|
2355
3259
|
useEntity,
|
|
3260
|
+
useCollisionExit,
|
|
3261
|
+
useCollisionEnter,
|
|
3262
|
+
useCircleExit,
|
|
3263
|
+
useCircleEnter,
|
|
3264
|
+
useCamera,
|
|
2356
3265
|
tween,
|
|
3266
|
+
raycast,
|
|
3267
|
+
overlapBox,
|
|
3268
|
+
findByTag,
|
|
2357
3269
|
definePlugin,
|
|
3270
|
+
createInputMap,
|
|
2358
3271
|
createAtlas,
|
|
2359
3272
|
World,
|
|
2360
3273
|
Transform,
|
|
@@ -2370,6 +3283,7 @@ export {
|
|
|
2370
3283
|
Game,
|
|
2371
3284
|
Entity,
|
|
2372
3285
|
Ease,
|
|
3286
|
+
CircleCollider,
|
|
2373
3287
|
Checkpoint,
|
|
2374
3288
|
Camera2D,
|
|
2375
3289
|
BoxCollider,
|