cubeforge 0.0.4 → 0.0.6
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/DevTools.d.ts +15 -0
- package/dist/components/Game.d.ts +7 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +527 -118
- package/package.json +2 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { WorldSnapshot } from '@cubeforge/core';
|
|
3
|
+
import type { ECSWorld, GameLoop } from '@cubeforge/core';
|
|
4
|
+
export declare const MAX_DEVTOOLS_FRAMES = 600;
|
|
5
|
+
export interface DevToolsHandle {
|
|
6
|
+
buffer: WorldSnapshot[];
|
|
7
|
+
onFrame?: () => void;
|
|
8
|
+
}
|
|
9
|
+
interface DevToolsProps {
|
|
10
|
+
handle: DevToolsHandle;
|
|
11
|
+
loop: GameLoop;
|
|
12
|
+
ecs: ECSWorld;
|
|
13
|
+
}
|
|
14
|
+
export declare function DevToolsOverlay({ handle, loop, ecs }: DevToolsProps): React.ReactPortal;
|
|
15
|
+
export {};
|
|
@@ -21,11 +21,17 @@ interface GameProps {
|
|
|
21
21
|
scale?: 'none' | 'contain' | 'pixel';
|
|
22
22
|
/** Called once the engine is ready — receives pause/resume/reset controls */
|
|
23
23
|
onReady?: (controls: GameControls) => void;
|
|
24
|
+
/** Enable time-travel debugging overlay (frame scrubber + entity inspector). */
|
|
25
|
+
devtools?: boolean;
|
|
26
|
+
/** Run the simulation in deterministic mode using a seeded RNG. */
|
|
27
|
+
deterministic?: boolean;
|
|
28
|
+
/** Seed for the deterministic RNG (default 0). Only used when deterministic=true. */
|
|
29
|
+
seed?: number;
|
|
24
30
|
/** Custom plugins to register after core systems. Each plugin's systems run after Render. */
|
|
25
31
|
plugins?: Plugin[];
|
|
26
32
|
style?: CSSProperties;
|
|
27
33
|
className?: string;
|
|
28
34
|
children?: React.ReactNode;
|
|
29
35
|
}
|
|
30
|
-
export declare function Game({ width, height, gravity, debug, scale, onReady, plugins, style, className, children, }: GameProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
export declare function Game({ width, height, gravity, debug, devtools, scale, deterministic, seed, onReady, plugins, style, className, children, }: GameProps): import("react/jsx-runtime").JSX.Element;
|
|
31
37
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -27,9 +27,10 @@ export type { SpriteAtlas } from './components/spriteAtlas';
|
|
|
27
27
|
export { createAtlas } from './components/spriteAtlas';
|
|
28
28
|
export type { EngineState } from './context';
|
|
29
29
|
export type { GameControls } from './components/Game';
|
|
30
|
+
export type { DevToolsHandle } from './components/DevTools';
|
|
30
31
|
export type { PlatformerControllerOptions } from './hooks/usePlatformerController';
|
|
31
32
|
export type { TopDownMovementOptions } from './hooks/useTopDownMovement';
|
|
32
|
-
export type { EntityId, ECSWorld, ScriptUpdateFn, Plugin } from '@cubeforge/core';
|
|
33
|
+
export type { EntityId, ECSWorld, ScriptUpdateFn, Plugin, WorldSnapshot } from '@cubeforge/core';
|
|
33
34
|
export { definePlugin } from '@cubeforge/core';
|
|
34
35
|
export type { InputManager } from '@cubeforge/input';
|
|
35
36
|
export type { TransformComponent } from '@cubeforge/core';
|
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,60 @@ 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
|
-
if (match)
|
|
79
|
-
return id;
|
|
80
114
|
}
|
|
81
115
|
return;
|
|
82
116
|
}
|
|
117
|
+
setDeterministicSeed(seed) {
|
|
118
|
+
this._rngState = seed >>> 0;
|
|
119
|
+
this._deterministic = true;
|
|
120
|
+
}
|
|
121
|
+
rng() {
|
|
122
|
+
if (!this._deterministic)
|
|
123
|
+
return Math.random();
|
|
124
|
+
this._rngState = Math.imul(this._rngState, 1664525) + 1013904223 >>> 0;
|
|
125
|
+
return this._rngState / 4294967296;
|
|
126
|
+
}
|
|
127
|
+
getSnapshot() {
|
|
128
|
+
const entities = [];
|
|
129
|
+
for (const [id, comps] of this.componentIndex) {
|
|
130
|
+
const components = [];
|
|
131
|
+
for (const comp of comps.values()) {
|
|
132
|
+
components.push(JSON.parse(JSON.stringify(comp)));
|
|
133
|
+
}
|
|
134
|
+
entities.push({ id, components });
|
|
135
|
+
}
|
|
136
|
+
return { nextId: this.nextId, rngState: this._rngState, entities };
|
|
137
|
+
}
|
|
138
|
+
restoreSnapshot(snapshot) {
|
|
139
|
+
this.clear();
|
|
140
|
+
this.nextId = snapshot.nextId;
|
|
141
|
+
this._rngState = snapshot.rngState;
|
|
142
|
+
for (const { id, components } of snapshot.entities) {
|
|
143
|
+
const compMap = new Map;
|
|
144
|
+
for (const comp of components)
|
|
145
|
+
compMap.set(comp.type, comp);
|
|
146
|
+
this.componentIndex.set(id, compMap);
|
|
147
|
+
const arch = this.getOrCreateArchetype(compMap.keys());
|
|
148
|
+
arch.entities.push(id);
|
|
149
|
+
this.entityArchetype.set(id, arch.key);
|
|
150
|
+
}
|
|
151
|
+
this.dirtyAll = true;
|
|
152
|
+
}
|
|
83
153
|
addSystem(system) {
|
|
84
154
|
this.systems.push(system);
|
|
85
155
|
}
|
|
@@ -110,15 +180,18 @@ class ECSWorld {
|
|
|
110
180
|
}
|
|
111
181
|
}
|
|
112
182
|
clear() {
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
183
|
+
this.componentIndex.clear();
|
|
184
|
+
this.archetypes.clear();
|
|
185
|
+
this.entityArchetype.clear();
|
|
115
186
|
this.queryCache.clear();
|
|
116
187
|
this.dirtyTypes.clear();
|
|
117
188
|
this.dirtyAll = false;
|
|
118
189
|
this.nextId = 0;
|
|
190
|
+
this._rngState = 0;
|
|
191
|
+
this._deterministic = false;
|
|
119
192
|
}
|
|
120
193
|
get entityCount() {
|
|
121
|
-
return this.
|
|
194
|
+
return this.componentIndex.size;
|
|
122
195
|
}
|
|
123
196
|
}
|
|
124
197
|
// ../core/src/loop/gameLoop.ts
|
|
@@ -673,8 +746,8 @@ class RenderSystem {
|
|
|
673
746
|
if (cam.shakeTimer < 0)
|
|
674
747
|
cam.shakeTimer = 0;
|
|
675
748
|
const progress = cam.shakeDuration > 0 ? cam.shakeTimer / cam.shakeDuration : 0;
|
|
676
|
-
shakeX = (
|
|
677
|
-
shakeY = (
|
|
749
|
+
shakeX = (world2.rng() * 2 - 1) * cam.shakeIntensity * progress;
|
|
750
|
+
shakeY = (world2.rng() * 2 - 1) * cam.shakeIntensity * progress;
|
|
678
751
|
}
|
|
679
752
|
camX = cam.x;
|
|
680
753
|
camY = cam.y;
|
|
@@ -755,10 +828,27 @@ class RenderSystem {
|
|
|
755
828
|
ctx.translate(canvas.width / 2 - camX * zoom + shakeX, canvas.height / 2 - camY * zoom + shakeY);
|
|
756
829
|
ctx.scale(zoom, zoom);
|
|
757
830
|
const renderables = world2.query("Transform", "Sprite");
|
|
831
|
+
const textureKey = (id) => {
|
|
832
|
+
const sprite = world2.getComponent(id, "Sprite");
|
|
833
|
+
if (sprite.image && sprite.image.src)
|
|
834
|
+
return sprite.image.src;
|
|
835
|
+
if (sprite.src)
|
|
836
|
+
return sprite.src;
|
|
837
|
+
return `__color__:${sprite.color}`;
|
|
838
|
+
};
|
|
758
839
|
renderables.sort((a, b) => {
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
|
|
840
|
+
const sa = world2.getComponent(a, "Sprite");
|
|
841
|
+
const sb = world2.getComponent(b, "Sprite");
|
|
842
|
+
const zDiff = sa.zIndex - sb.zIndex;
|
|
843
|
+
if (zDiff !== 0)
|
|
844
|
+
return zDiff;
|
|
845
|
+
const ka = textureKey(a);
|
|
846
|
+
const kb = textureKey(b);
|
|
847
|
+
if (ka < kb)
|
|
848
|
+
return -1;
|
|
849
|
+
if (ka > kb)
|
|
850
|
+
return 1;
|
|
851
|
+
return 0;
|
|
762
852
|
});
|
|
763
853
|
for (const id of renderables) {
|
|
764
854
|
const transform2 = world2.getComponent(id, "Transform");
|
|
@@ -809,8 +899,8 @@ class RenderSystem {
|
|
|
809
899
|
const spawnCount = Math.floor(pool.timer * pool.rate);
|
|
810
900
|
pool.timer -= spawnCount / pool.rate;
|
|
811
901
|
for (let i = 0;i < spawnCount && pool.particles.length < pool.maxParticles; i++) {
|
|
812
|
-
const angle = pool.angle + (
|
|
813
|
-
const speed = pool.speed * (0.5 +
|
|
902
|
+
const angle = pool.angle + (world2.rng() - 0.5) * pool.spread;
|
|
903
|
+
const speed = pool.speed * (0.5 + world2.rng() * 0.5);
|
|
814
904
|
pool.particles.push({
|
|
815
905
|
x: t.x,
|
|
816
906
|
y: t.y,
|
|
@@ -1253,14 +1343,318 @@ class DebugSystem {
|
|
|
1253
1343
|
}
|
|
1254
1344
|
}
|
|
1255
1345
|
|
|
1346
|
+
// src/components/DevTools.tsx
|
|
1347
|
+
import React from "react";
|
|
1348
|
+
import { createPortal } from "react-dom";
|
|
1349
|
+
import { useState, useEffect, useCallback } from "react";
|
|
1350
|
+
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
1351
|
+
var MAX_DEVTOOLS_FRAMES = 600;
|
|
1352
|
+
var css = {
|
|
1353
|
+
overlay: {
|
|
1354
|
+
position: "fixed",
|
|
1355
|
+
bottom: 0,
|
|
1356
|
+
left: 0,
|
|
1357
|
+
right: 0,
|
|
1358
|
+
zIndex: 99999,
|
|
1359
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', 'Courier New', monospace",
|
|
1360
|
+
fontSize: 11,
|
|
1361
|
+
color: "#cdd6f4",
|
|
1362
|
+
userSelect: "none",
|
|
1363
|
+
pointerEvents: "auto"
|
|
1364
|
+
},
|
|
1365
|
+
bar: {
|
|
1366
|
+
background: "rgba(11,13,20,0.97)",
|
|
1367
|
+
borderTop: "1px solid #2a3048",
|
|
1368
|
+
padding: "6px 12px",
|
|
1369
|
+
display: "flex",
|
|
1370
|
+
alignItems: "center",
|
|
1371
|
+
gap: 10,
|
|
1372
|
+
height: 40
|
|
1373
|
+
},
|
|
1374
|
+
badge: {
|
|
1375
|
+
fontSize: 9,
|
|
1376
|
+
fontWeight: 700,
|
|
1377
|
+
letterSpacing: "0.1em",
|
|
1378
|
+
color: "#4fc3f7",
|
|
1379
|
+
background: "rgba(79,195,247,0.1)",
|
|
1380
|
+
border: "1px solid rgba(79,195,247,0.2)",
|
|
1381
|
+
borderRadius: 4,
|
|
1382
|
+
padding: "2px 6px",
|
|
1383
|
+
flexShrink: 0
|
|
1384
|
+
},
|
|
1385
|
+
btn: (active = false, danger = false) => ({
|
|
1386
|
+
background: active ? "rgba(79,195,247,0.15)" : "rgba(255,255,255,0.04)",
|
|
1387
|
+
border: `1px solid ${active ? "rgba(79,195,247,0.3)" : "#2a3048"}`,
|
|
1388
|
+
borderRadius: 4,
|
|
1389
|
+
color: danger ? "#f38ba8" : active ? "#4fc3f7" : "#6b7a9e",
|
|
1390
|
+
cursor: "pointer",
|
|
1391
|
+
padding: "3px 8px",
|
|
1392
|
+
fontSize: 10,
|
|
1393
|
+
fontFamily: "inherit",
|
|
1394
|
+
display: "flex",
|
|
1395
|
+
alignItems: "center",
|
|
1396
|
+
gap: 4,
|
|
1397
|
+
transition: "all 0.1s",
|
|
1398
|
+
flexShrink: 0
|
|
1399
|
+
}),
|
|
1400
|
+
scrubber: {
|
|
1401
|
+
flex: 1,
|
|
1402
|
+
accentColor: "#4fc3f7",
|
|
1403
|
+
cursor: "pointer",
|
|
1404
|
+
height: 4
|
|
1405
|
+
},
|
|
1406
|
+
counter: {
|
|
1407
|
+
color: "#6b7a9e",
|
|
1408
|
+
fontSize: 10,
|
|
1409
|
+
whiteSpace: "nowrap",
|
|
1410
|
+
flexShrink: 0,
|
|
1411
|
+
minWidth: 80,
|
|
1412
|
+
textAlign: "right"
|
|
1413
|
+
},
|
|
1414
|
+
panel: {
|
|
1415
|
+
background: "rgba(11,13,20,0.97)",
|
|
1416
|
+
borderTop: "1px solid #1f2435",
|
|
1417
|
+
maxHeight: 260,
|
|
1418
|
+
overflowY: "auto",
|
|
1419
|
+
padding: "8px 0"
|
|
1420
|
+
},
|
|
1421
|
+
entityRow: (selected) => ({
|
|
1422
|
+
display: "flex",
|
|
1423
|
+
alignItems: "center",
|
|
1424
|
+
gap: 8,
|
|
1425
|
+
padding: "4px 12px",
|
|
1426
|
+
cursor: "pointer",
|
|
1427
|
+
background: selected ? "rgba(79,195,247,0.06)" : "transparent",
|
|
1428
|
+
borderLeft: `2px solid ${selected ? "#4fc3f7" : "transparent"}`
|
|
1429
|
+
}),
|
|
1430
|
+
entityId: {
|
|
1431
|
+
color: "#4fc3f7",
|
|
1432
|
+
minWidth: 28,
|
|
1433
|
+
fontSize: 10
|
|
1434
|
+
},
|
|
1435
|
+
compPill: {
|
|
1436
|
+
fontSize: 9,
|
|
1437
|
+
background: "rgba(79,195,247,0.08)",
|
|
1438
|
+
border: "1px solid rgba(79,195,247,0.12)",
|
|
1439
|
+
borderRadius: 3,
|
|
1440
|
+
padding: "1px 5px",
|
|
1441
|
+
color: "#6b7a9e"
|
|
1442
|
+
},
|
|
1443
|
+
detailPanel: {
|
|
1444
|
+
background: "rgba(18,21,31,0.98)",
|
|
1445
|
+
borderTop: "1px solid #1f2435",
|
|
1446
|
+
padding: "10px 14px",
|
|
1447
|
+
maxHeight: 200,
|
|
1448
|
+
overflowY: "auto"
|
|
1449
|
+
},
|
|
1450
|
+
kv: {
|
|
1451
|
+
display: "grid",
|
|
1452
|
+
gridTemplateColumns: "140px 1fr",
|
|
1453
|
+
gap: "2px 12px",
|
|
1454
|
+
lineHeight: 1.8
|
|
1455
|
+
},
|
|
1456
|
+
key: { color: "#6b7a9e" },
|
|
1457
|
+
val: { color: "#cdd6f4" }
|
|
1458
|
+
};
|
|
1459
|
+
function DevToolsOverlay({ handle, loop, ecs }) {
|
|
1460
|
+
const [, forceUpdate] = useState(0);
|
|
1461
|
+
const [paused, setPaused] = useState(false);
|
|
1462
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
1463
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
1464
|
+
const [selectedEntity, setSelectedEntity] = useState(null);
|
|
1465
|
+
useEffect(() => {
|
|
1466
|
+
handle.onFrame = () => {
|
|
1467
|
+
if (!paused) {
|
|
1468
|
+
forceUpdate((n) => n + 1);
|
|
1469
|
+
setSelectedIdx(Math.max(0, handle.buffer.length - 1));
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
return () => {
|
|
1473
|
+
handle.onFrame = undefined;
|
|
1474
|
+
};
|
|
1475
|
+
}, [handle, paused]);
|
|
1476
|
+
const totalFrames = handle.buffer.length;
|
|
1477
|
+
const currentSnap = handle.buffer[selectedIdx];
|
|
1478
|
+
const handlePauseResume = useCallback(() => {
|
|
1479
|
+
if (paused) {
|
|
1480
|
+
if (currentSnap)
|
|
1481
|
+
ecs.restoreSnapshot(currentSnap);
|
|
1482
|
+
loop.resume();
|
|
1483
|
+
setPaused(false);
|
|
1484
|
+
setSelectedEntity(null);
|
|
1485
|
+
} else {
|
|
1486
|
+
loop.pause();
|
|
1487
|
+
setPaused(true);
|
|
1488
|
+
setSelectedIdx(Math.max(0, handle.buffer.length - 1));
|
|
1489
|
+
}
|
|
1490
|
+
}, [paused, currentSnap, ecs, loop, handle]);
|
|
1491
|
+
const stepBack = useCallback(() => {
|
|
1492
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
1493
|
+
setSelectedEntity(null);
|
|
1494
|
+
}, []);
|
|
1495
|
+
const stepForward = useCallback(() => {
|
|
1496
|
+
setSelectedIdx((i) => Math.min(handle.buffer.length - 1, i + 1));
|
|
1497
|
+
setSelectedEntity(null);
|
|
1498
|
+
}, [handle]);
|
|
1499
|
+
const frameLabel = totalFrames === 0 ? "0 / 0" : `${selectedIdx + 1} / ${totalFrames}`;
|
|
1500
|
+
const entities = currentSnap?.entities ?? [];
|
|
1501
|
+
const selectedEntityData = selectedEntity !== null ? entities.find((e) => e.id === selectedEntity) : null;
|
|
1502
|
+
return createPortal(/* @__PURE__ */ jsxDEV("div", {
|
|
1503
|
+
style: css.overlay,
|
|
1504
|
+
children: [
|
|
1505
|
+
panelOpen && paused && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
1506
|
+
children: [
|
|
1507
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1508
|
+
style: css.panel,
|
|
1509
|
+
children: [
|
|
1510
|
+
entities.length === 0 && /* @__PURE__ */ jsxDEV("div", {
|
|
1511
|
+
style: { padding: "4px 14px", color: "#3d4666" },
|
|
1512
|
+
children: "No entities"
|
|
1513
|
+
}, undefined, false, undefined, this),
|
|
1514
|
+
entities.map((e) => /* @__PURE__ */ jsxDEV("div", {
|
|
1515
|
+
style: css.entityRow(selectedEntity === e.id),
|
|
1516
|
+
onClick: () => setSelectedEntity((s) => s === e.id ? null : e.id),
|
|
1517
|
+
children: [
|
|
1518
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1519
|
+
style: css.entityId,
|
|
1520
|
+
children: [
|
|
1521
|
+
"#",
|
|
1522
|
+
e.id
|
|
1523
|
+
]
|
|
1524
|
+
}, undefined, true, undefined, this),
|
|
1525
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1526
|
+
style: { display: "flex", gap: 4, flexWrap: "wrap" },
|
|
1527
|
+
children: e.components.map((c) => /* @__PURE__ */ jsxDEV("span", {
|
|
1528
|
+
style: css.compPill,
|
|
1529
|
+
children: c.type
|
|
1530
|
+
}, c.type, false, undefined, this))
|
|
1531
|
+
}, undefined, false, undefined, this)
|
|
1532
|
+
]
|
|
1533
|
+
}, e.id, true, undefined, this))
|
|
1534
|
+
]
|
|
1535
|
+
}, undefined, true, undefined, this),
|
|
1536
|
+
selectedEntityData && /* @__PURE__ */ jsxDEV("div", {
|
|
1537
|
+
style: css.detailPanel,
|
|
1538
|
+
children: selectedEntityData.components.map((comp) => /* @__PURE__ */ jsxDEV("div", {
|
|
1539
|
+
style: { marginBottom: 10 },
|
|
1540
|
+
children: [
|
|
1541
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1542
|
+
style: { color: "#4fc3f7", fontSize: 10, fontWeight: 700, marginBottom: 4 },
|
|
1543
|
+
children: comp.type
|
|
1544
|
+
}, undefined, false, undefined, this),
|
|
1545
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1546
|
+
style: css.kv,
|
|
1547
|
+
children: Object.entries(comp).filter(([k]) => k !== "type").map(([k, v]) => /* @__PURE__ */ jsxDEV(React.Fragment, {
|
|
1548
|
+
children: [
|
|
1549
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1550
|
+
style: css.key,
|
|
1551
|
+
children: k
|
|
1552
|
+
}, undefined, false, undefined, this),
|
|
1553
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1554
|
+
style: css.val,
|
|
1555
|
+
children: formatValue(v)
|
|
1556
|
+
}, undefined, false, undefined, this)
|
|
1557
|
+
]
|
|
1558
|
+
}, k, true, undefined, this))
|
|
1559
|
+
}, undefined, false, undefined, this)
|
|
1560
|
+
]
|
|
1561
|
+
}, comp.type, true, undefined, this))
|
|
1562
|
+
}, undefined, false, undefined, this)
|
|
1563
|
+
]
|
|
1564
|
+
}, undefined, true, undefined, this),
|
|
1565
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1566
|
+
style: css.bar,
|
|
1567
|
+
children: [
|
|
1568
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1569
|
+
style: css.badge,
|
|
1570
|
+
children: "DEVTOOLS"
|
|
1571
|
+
}, undefined, false, undefined, this),
|
|
1572
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1573
|
+
style: css.btn(paused),
|
|
1574
|
+
onClick: handlePauseResume,
|
|
1575
|
+
children: paused ? "▶ Resume" : "⏸ Pause"
|
|
1576
|
+
}, undefined, false, undefined, this),
|
|
1577
|
+
paused && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
1578
|
+
children: [
|
|
1579
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1580
|
+
style: css.btn(),
|
|
1581
|
+
onClick: stepBack,
|
|
1582
|
+
children: "◀◀"
|
|
1583
|
+
}, undefined, false, undefined, this),
|
|
1584
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1585
|
+
style: css.btn(),
|
|
1586
|
+
onClick: stepForward,
|
|
1587
|
+
children: "▶▶"
|
|
1588
|
+
}, undefined, false, undefined, this)
|
|
1589
|
+
]
|
|
1590
|
+
}, undefined, true, undefined, this),
|
|
1591
|
+
/* @__PURE__ */ jsxDEV("input", {
|
|
1592
|
+
type: "range",
|
|
1593
|
+
min: 0,
|
|
1594
|
+
max: Math.max(0, totalFrames - 1),
|
|
1595
|
+
value: selectedIdx,
|
|
1596
|
+
style: css.scrubber,
|
|
1597
|
+
onChange: (e) => {
|
|
1598
|
+
const idx = Number(e.target.value);
|
|
1599
|
+
setSelectedIdx(idx);
|
|
1600
|
+
setSelectedEntity(null);
|
|
1601
|
+
if (!paused) {
|
|
1602
|
+
loop.pause();
|
|
1603
|
+
setPaused(true);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}, undefined, false, undefined, this),
|
|
1607
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1608
|
+
style: css.counter,
|
|
1609
|
+
children: frameLabel
|
|
1610
|
+
}, undefined, false, undefined, this),
|
|
1611
|
+
paused && /* @__PURE__ */ jsxDEV("button", {
|
|
1612
|
+
style: css.btn(panelOpen),
|
|
1613
|
+
onClick: () => setPanelOpen((o) => !o),
|
|
1614
|
+
children: [
|
|
1615
|
+
panelOpen ? "▾" : "▸",
|
|
1616
|
+
" Entities (",
|
|
1617
|
+
entities.length,
|
|
1618
|
+
")"
|
|
1619
|
+
]
|
|
1620
|
+
}, undefined, true, undefined, this),
|
|
1621
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
1622
|
+
style: css.btn(false, true),
|
|
1623
|
+
onClick: () => {
|
|
1624
|
+
handle.buffer.length = 0;
|
|
1625
|
+
setSelectedIdx(0);
|
|
1626
|
+
forceUpdate((n) => n + 1);
|
|
1627
|
+
},
|
|
1628
|
+
children: "Clear"
|
|
1629
|
+
}, undefined, false, undefined, this)
|
|
1630
|
+
]
|
|
1631
|
+
}, undefined, true, undefined, this)
|
|
1632
|
+
]
|
|
1633
|
+
}, undefined, true, undefined, this), document.body);
|
|
1634
|
+
}
|
|
1635
|
+
function formatValue(v) {
|
|
1636
|
+
if (typeof v === "number")
|
|
1637
|
+
return v.toFixed(2);
|
|
1638
|
+
if (typeof v === "boolean")
|
|
1639
|
+
return v ? "true" : "false";
|
|
1640
|
+
if (v === null || v === undefined)
|
|
1641
|
+
return "—";
|
|
1642
|
+
if (typeof v === "object")
|
|
1643
|
+
return JSON.stringify(v).slice(0, 60);
|
|
1644
|
+
return String(v);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1256
1647
|
// src/components/Game.tsx
|
|
1257
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1648
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
1258
1649
|
function Game({
|
|
1259
1650
|
width = 800,
|
|
1260
1651
|
height = 600,
|
|
1261
1652
|
gravity = 980,
|
|
1262
1653
|
debug = false,
|
|
1654
|
+
devtools = false,
|
|
1263
1655
|
scale = "none",
|
|
1656
|
+
deterministic = false,
|
|
1657
|
+
seed = 0,
|
|
1264
1658
|
onReady,
|
|
1265
1659
|
plugins,
|
|
1266
1660
|
style,
|
|
@@ -1269,10 +1663,13 @@ function Game({
|
|
|
1269
1663
|
}) {
|
|
1270
1664
|
const canvasRef = useRef(null);
|
|
1271
1665
|
const wrapperRef = useRef(null);
|
|
1272
|
-
const [engine, setEngine] =
|
|
1273
|
-
|
|
1666
|
+
const [engine, setEngine] = useState2(null);
|
|
1667
|
+
const devtoolsHandle = useRef({ buffer: [] });
|
|
1668
|
+
useEffect2(() => {
|
|
1274
1669
|
const canvas = canvasRef.current;
|
|
1275
1670
|
const ecs = new ECSWorld;
|
|
1671
|
+
if (deterministic)
|
|
1672
|
+
ecs.setDeterministicSeed(seed);
|
|
1276
1673
|
const input = new InputManager;
|
|
1277
1674
|
const renderer = new Canvas2DRenderer(canvas);
|
|
1278
1675
|
const events = new EventBus;
|
|
@@ -1294,6 +1691,13 @@ function Game({
|
|
|
1294
1691
|
const loop = new GameLoop((dt) => {
|
|
1295
1692
|
ecs.update(dt);
|
|
1296
1693
|
input.flush();
|
|
1694
|
+
if (devtools) {
|
|
1695
|
+
const handle = devtoolsHandle.current;
|
|
1696
|
+
handle.buffer.push(ecs.getSnapshot());
|
|
1697
|
+
if (handle.buffer.length > MAX_DEVTOOLS_FRAMES)
|
|
1698
|
+
handle.buffer.shift();
|
|
1699
|
+
handle.onFrame?.();
|
|
1700
|
+
}
|
|
1297
1701
|
});
|
|
1298
1702
|
const state = { ecs, input, renderer, physics, events, assets, loop, canvas, entityIds };
|
|
1299
1703
|
setEngine(state);
|
|
@@ -1339,7 +1743,7 @@ function Game({
|
|
|
1339
1743
|
resizeObserver?.disconnect();
|
|
1340
1744
|
};
|
|
1341
1745
|
}, []);
|
|
1342
|
-
|
|
1746
|
+
useEffect2(() => {
|
|
1343
1747
|
engine?.physics.setGravity(gravity);
|
|
1344
1748
|
}, [gravity, engine]);
|
|
1345
1749
|
const canvasStyle = {
|
|
@@ -1349,13 +1753,13 @@ function Game({
|
|
|
1349
1753
|
...style
|
|
1350
1754
|
};
|
|
1351
1755
|
const wrapperStyle = scale === "contain" ? { position: "relative", width, height, overflow: "visible" } : {};
|
|
1352
|
-
return /* @__PURE__ */
|
|
1756
|
+
return /* @__PURE__ */ jsxDEV2(EngineContext.Provider, {
|
|
1353
1757
|
value: engine,
|
|
1354
1758
|
children: [
|
|
1355
|
-
/* @__PURE__ */
|
|
1759
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
1356
1760
|
ref: wrapperRef,
|
|
1357
1761
|
style: wrapperStyle,
|
|
1358
|
-
children: /* @__PURE__ */
|
|
1762
|
+
children: /* @__PURE__ */ jsxDEV2("canvas", {
|
|
1359
1763
|
ref: canvasRef,
|
|
1360
1764
|
width,
|
|
1361
1765
|
height,
|
|
@@ -1363,22 +1767,27 @@ function Game({
|
|
|
1363
1767
|
className
|
|
1364
1768
|
}, undefined, false, undefined, this)
|
|
1365
1769
|
}, undefined, false, undefined, this),
|
|
1366
|
-
engine && children
|
|
1770
|
+
engine && children,
|
|
1771
|
+
engine && devtools && /* @__PURE__ */ jsxDEV2(DevToolsOverlay, {
|
|
1772
|
+
handle: devtoolsHandle.current,
|
|
1773
|
+
loop: engine.loop,
|
|
1774
|
+
ecs: engine.ecs
|
|
1775
|
+
}, undefined, false, undefined, this)
|
|
1367
1776
|
]
|
|
1368
1777
|
}, undefined, true, undefined, this);
|
|
1369
1778
|
}
|
|
1370
1779
|
// src/components/World.tsx
|
|
1371
|
-
import { useEffect as
|
|
1372
|
-
import { jsxDEV as
|
|
1780
|
+
import { useEffect as useEffect3, useContext } from "react";
|
|
1781
|
+
import { jsxDEV as jsxDEV3, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
1373
1782
|
function World({ gravity, background = "#1a1a2e", children }) {
|
|
1374
1783
|
const engine = useContext(EngineContext);
|
|
1375
|
-
|
|
1784
|
+
useEffect3(() => {
|
|
1376
1785
|
if (!engine)
|
|
1377
1786
|
return;
|
|
1378
1787
|
if (gravity !== undefined)
|
|
1379
1788
|
engine.physics.setGravity(gravity);
|
|
1380
1789
|
}, [gravity, engine]);
|
|
1381
|
-
|
|
1790
|
+
useEffect3(() => {
|
|
1382
1791
|
if (!engine)
|
|
1383
1792
|
return;
|
|
1384
1793
|
const camId = engine.ecs.queryOne("Camera2D");
|
|
@@ -1390,17 +1799,17 @@ function World({ gravity, background = "#1a1a2e", children }) {
|
|
|
1390
1799
|
engine.canvas.style.background = background;
|
|
1391
1800
|
}
|
|
1392
1801
|
}, [background, engine]);
|
|
1393
|
-
return /* @__PURE__ */
|
|
1802
|
+
return /* @__PURE__ */ jsxDEV3(Fragment2, {
|
|
1394
1803
|
children
|
|
1395
1804
|
}, undefined, false, undefined, this);
|
|
1396
1805
|
}
|
|
1397
1806
|
// src/components/Entity.tsx
|
|
1398
|
-
import { useEffect as
|
|
1399
|
-
import { jsxDEV as
|
|
1807
|
+
import { useEffect as useEffect4, useContext as useContext2, useState as useState3 } from "react";
|
|
1808
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
1400
1809
|
function Entity({ id, tags = [], children }) {
|
|
1401
1810
|
const engine = useContext2(EngineContext);
|
|
1402
|
-
const [entityId, setEntityId] =
|
|
1403
|
-
|
|
1811
|
+
const [entityId, setEntityId] = useState3(null);
|
|
1812
|
+
useEffect4(() => {
|
|
1404
1813
|
const eid = engine.ecs.createEntity();
|
|
1405
1814
|
if (id) {
|
|
1406
1815
|
if (engine.entityIds.has(id)) {
|
|
@@ -1419,21 +1828,21 @@ function Entity({ id, tags = [], children }) {
|
|
|
1419
1828
|
}, []);
|
|
1420
1829
|
if (entityId === null)
|
|
1421
1830
|
return null;
|
|
1422
|
-
return /* @__PURE__ */
|
|
1831
|
+
return /* @__PURE__ */ jsxDEV4(EntityContext.Provider, {
|
|
1423
1832
|
value: entityId,
|
|
1424
1833
|
children
|
|
1425
1834
|
}, undefined, false, undefined, this);
|
|
1426
1835
|
}
|
|
1427
1836
|
// src/components/Transform.tsx
|
|
1428
|
-
import { useEffect as
|
|
1837
|
+
import { useEffect as useEffect5, useContext as useContext3 } from "react";
|
|
1429
1838
|
function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
|
|
1430
1839
|
const engine = useContext3(EngineContext);
|
|
1431
1840
|
const entityId = useContext3(EntityContext);
|
|
1432
|
-
|
|
1841
|
+
useEffect5(() => {
|
|
1433
1842
|
engine.ecs.addComponent(entityId, createTransform(x, y, rotation, scaleX, scaleY));
|
|
1434
1843
|
return () => engine.ecs.removeComponent(entityId, "Transform");
|
|
1435
1844
|
}, []);
|
|
1436
|
-
|
|
1845
|
+
useEffect5(() => {
|
|
1437
1846
|
const comp = engine.ecs.getComponent(entityId, "Transform");
|
|
1438
1847
|
if (comp) {
|
|
1439
1848
|
comp.x = x;
|
|
@@ -1446,7 +1855,7 @@ function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
|
|
|
1446
1855
|
return null;
|
|
1447
1856
|
}
|
|
1448
1857
|
// src/components/Sprite.tsx
|
|
1449
|
-
import { useEffect as
|
|
1858
|
+
import { useEffect as useEffect6, useContext as useContext4 } from "react";
|
|
1450
1859
|
function Sprite({
|
|
1451
1860
|
width,
|
|
1452
1861
|
height,
|
|
@@ -1469,7 +1878,7 @@ function Sprite({
|
|
|
1469
1878
|
const resolvedFrameIndex = atlas && frame != null ? atlas[frame] ?? 0 : frameIndex;
|
|
1470
1879
|
const engine = useContext4(EngineContext);
|
|
1471
1880
|
const entityId = useContext4(EntityContext);
|
|
1472
|
-
|
|
1881
|
+
useEffect6(() => {
|
|
1473
1882
|
const comp = createSprite({
|
|
1474
1883
|
width,
|
|
1475
1884
|
height,
|
|
@@ -1497,7 +1906,7 @@ function Sprite({
|
|
|
1497
1906
|
}
|
|
1498
1907
|
return () => engine.ecs.removeComponent(entityId, "Sprite");
|
|
1499
1908
|
}, []);
|
|
1500
|
-
|
|
1909
|
+
useEffect6(() => {
|
|
1501
1910
|
const comp = engine.ecs.getComponent(entityId, "Sprite");
|
|
1502
1911
|
if (!comp)
|
|
1503
1912
|
return;
|
|
@@ -1510,7 +1919,7 @@ function Sprite({
|
|
|
1510
1919
|
return null;
|
|
1511
1920
|
}
|
|
1512
1921
|
// src/components/RigidBody.tsx
|
|
1513
|
-
import { useEffect as
|
|
1922
|
+
import { useEffect as useEffect7, useContext as useContext5 } from "react";
|
|
1514
1923
|
function RigidBody({
|
|
1515
1924
|
mass = 1,
|
|
1516
1925
|
gravityScale = 1,
|
|
@@ -1522,14 +1931,14 @@ function RigidBody({
|
|
|
1522
1931
|
}) {
|
|
1523
1932
|
const engine = useContext5(EngineContext);
|
|
1524
1933
|
const entityId = useContext5(EntityContext);
|
|
1525
|
-
|
|
1934
|
+
useEffect7(() => {
|
|
1526
1935
|
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy }));
|
|
1527
1936
|
return () => engine.ecs.removeComponent(entityId, "RigidBody");
|
|
1528
1937
|
}, []);
|
|
1529
1938
|
return null;
|
|
1530
1939
|
}
|
|
1531
1940
|
// src/components/BoxCollider.tsx
|
|
1532
|
-
import { useEffect as
|
|
1941
|
+
import { useEffect as useEffect8, useContext as useContext6 } from "react";
|
|
1533
1942
|
function BoxCollider({
|
|
1534
1943
|
width,
|
|
1535
1944
|
height,
|
|
@@ -1540,7 +1949,7 @@ function BoxCollider({
|
|
|
1540
1949
|
}) {
|
|
1541
1950
|
const engine = useContext6(EngineContext);
|
|
1542
1951
|
const entityId = useContext6(EntityContext);
|
|
1543
|
-
|
|
1952
|
+
useEffect8(() => {
|
|
1544
1953
|
engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer }));
|
|
1545
1954
|
const checkId = setTimeout(() => {
|
|
1546
1955
|
if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
|
|
@@ -1555,11 +1964,11 @@ function BoxCollider({
|
|
|
1555
1964
|
return null;
|
|
1556
1965
|
}
|
|
1557
1966
|
// src/components/Script.tsx
|
|
1558
|
-
import { useEffect as
|
|
1967
|
+
import { useEffect as useEffect9, useContext as useContext7 } from "react";
|
|
1559
1968
|
function Script({ init, update }) {
|
|
1560
1969
|
const engine = useContext7(EngineContext);
|
|
1561
1970
|
const entityId = useContext7(EntityContext);
|
|
1562
|
-
|
|
1971
|
+
useEffect9(() => {
|
|
1563
1972
|
if (init) {
|
|
1564
1973
|
try {
|
|
1565
1974
|
init(entityId, engine.ecs);
|
|
@@ -1573,7 +1982,7 @@ function Script({ init, update }) {
|
|
|
1573
1982
|
return null;
|
|
1574
1983
|
}
|
|
1575
1984
|
// src/components/Camera2D.tsx
|
|
1576
|
-
import { useEffect as
|
|
1985
|
+
import { useEffect as useEffect10, useContext as useContext8 } from "react";
|
|
1577
1986
|
function Camera2D({
|
|
1578
1987
|
followEntity,
|
|
1579
1988
|
zoom = 1,
|
|
@@ -1583,7 +1992,7 @@ function Camera2D({
|
|
|
1583
1992
|
deadZone
|
|
1584
1993
|
}) {
|
|
1585
1994
|
const engine = useContext8(EngineContext);
|
|
1586
|
-
|
|
1995
|
+
useEffect10(() => {
|
|
1587
1996
|
const entityId = engine.ecs.createEntity();
|
|
1588
1997
|
engine.ecs.addComponent(entityId, createCamera2D({
|
|
1589
1998
|
followEntityId: followEntity,
|
|
@@ -1595,7 +2004,7 @@ function Camera2D({
|
|
|
1595
2004
|
}));
|
|
1596
2005
|
return () => engine.ecs.destroyEntity(entityId);
|
|
1597
2006
|
}, []);
|
|
1598
|
-
|
|
2007
|
+
useEffect10(() => {
|
|
1599
2008
|
const camId = engine.ecs.queryOne("Camera2D");
|
|
1600
2009
|
if (camId === undefined)
|
|
1601
2010
|
return;
|
|
@@ -1610,11 +2019,11 @@ function Camera2D({
|
|
|
1610
2019
|
return null;
|
|
1611
2020
|
}
|
|
1612
2021
|
// src/components/Animation.tsx
|
|
1613
|
-
import { useEffect as
|
|
2022
|
+
import { useEffect as useEffect11, useContext as useContext9 } from "react";
|
|
1614
2023
|
function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
1615
2024
|
const engine = useContext9(EngineContext);
|
|
1616
2025
|
const entityId = useContext9(EntityContext);
|
|
1617
|
-
|
|
2026
|
+
useEffect11(() => {
|
|
1618
2027
|
const state = {
|
|
1619
2028
|
type: "AnimationState",
|
|
1620
2029
|
frames,
|
|
@@ -1629,7 +2038,7 @@ function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
|
1629
2038
|
engine.ecs.removeComponent(entityId, "AnimationState");
|
|
1630
2039
|
};
|
|
1631
2040
|
}, []);
|
|
1632
|
-
|
|
2041
|
+
useEffect11(() => {
|
|
1633
2042
|
const anim = engine.ecs.getComponent(entityId, "AnimationState");
|
|
1634
2043
|
if (!anim)
|
|
1635
2044
|
return;
|
|
@@ -1640,11 +2049,11 @@ function Animation({ frames, fps = 12, loop = true, playing = true }) {
|
|
|
1640
2049
|
return null;
|
|
1641
2050
|
}
|
|
1642
2051
|
// src/components/SquashStretch.tsx
|
|
1643
|
-
import { useEffect as
|
|
2052
|
+
import { useEffect as useEffect12, useContext as useContext10 } from "react";
|
|
1644
2053
|
function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
1645
2054
|
const engine = useContext10(EngineContext);
|
|
1646
2055
|
const entityId = useContext10(EntityContext);
|
|
1647
|
-
|
|
2056
|
+
useEffect12(() => {
|
|
1648
2057
|
engine.ecs.addComponent(entityId, {
|
|
1649
2058
|
type: "SquashStretch",
|
|
1650
2059
|
intensity,
|
|
@@ -1657,7 +2066,7 @@ function SquashStretch({ intensity = 0.2, recovery = 8 }) {
|
|
|
1657
2066
|
return null;
|
|
1658
2067
|
}
|
|
1659
2068
|
// src/components/ParticleEmitter.tsx
|
|
1660
|
-
import { useEffect as
|
|
2069
|
+
import { useEffect as useEffect13, useContext as useContext11 } from "react";
|
|
1661
2070
|
|
|
1662
2071
|
// src/components/particlePresets.ts
|
|
1663
2072
|
var PARTICLE_PRESETS = {
|
|
@@ -1744,7 +2153,7 @@ function ParticleEmitter({
|
|
|
1744
2153
|
const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
|
|
1745
2154
|
const engine = useContext11(EngineContext);
|
|
1746
2155
|
const entityId = useContext11(EntityContext);
|
|
1747
|
-
|
|
2156
|
+
useEffect13(() => {
|
|
1748
2157
|
engine.ecs.addComponent(entityId, {
|
|
1749
2158
|
type: "ParticlePool",
|
|
1750
2159
|
particles: [],
|
|
@@ -1762,7 +2171,7 @@ function ParticleEmitter({
|
|
|
1762
2171
|
});
|
|
1763
2172
|
return () => engine.ecs.removeComponent(entityId, "ParticlePool");
|
|
1764
2173
|
}, []);
|
|
1765
|
-
|
|
2174
|
+
useEffect13(() => {
|
|
1766
2175
|
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
1767
2176
|
if (!pool)
|
|
1768
2177
|
return;
|
|
@@ -1771,7 +2180,7 @@ function ParticleEmitter({
|
|
|
1771
2180
|
return null;
|
|
1772
2181
|
}
|
|
1773
2182
|
// src/components/MovingPlatform.tsx
|
|
1774
|
-
import { jsxDEV as
|
|
2183
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
1775
2184
|
var platformPhases = new Map;
|
|
1776
2185
|
function MovingPlatform({
|
|
1777
2186
|
x1,
|
|
@@ -1783,26 +2192,26 @@ function MovingPlatform({
|
|
|
1783
2192
|
duration = 3,
|
|
1784
2193
|
color = "#37474f"
|
|
1785
2194
|
}) {
|
|
1786
|
-
return /* @__PURE__ */
|
|
2195
|
+
return /* @__PURE__ */ jsxDEV5(Entity, {
|
|
1787
2196
|
children: [
|
|
1788
|
-
/* @__PURE__ */
|
|
2197
|
+
/* @__PURE__ */ jsxDEV5(Transform, {
|
|
1789
2198
|
x: x1,
|
|
1790
2199
|
y: y1
|
|
1791
2200
|
}, undefined, false, undefined, this),
|
|
1792
|
-
/* @__PURE__ */
|
|
2201
|
+
/* @__PURE__ */ jsxDEV5(Sprite, {
|
|
1793
2202
|
width,
|
|
1794
2203
|
height,
|
|
1795
2204
|
color,
|
|
1796
2205
|
zIndex: 5
|
|
1797
2206
|
}, undefined, false, undefined, this),
|
|
1798
|
-
/* @__PURE__ */
|
|
2207
|
+
/* @__PURE__ */ jsxDEV5(RigidBody, {
|
|
1799
2208
|
isStatic: true
|
|
1800
2209
|
}, undefined, false, undefined, this),
|
|
1801
|
-
/* @__PURE__ */
|
|
2210
|
+
/* @__PURE__ */ jsxDEV5(BoxCollider, {
|
|
1802
2211
|
width,
|
|
1803
2212
|
height
|
|
1804
2213
|
}, undefined, false, undefined, this),
|
|
1805
|
-
/* @__PURE__ */
|
|
2214
|
+
/* @__PURE__ */ jsxDEV5(Script, {
|
|
1806
2215
|
init: () => {},
|
|
1807
2216
|
update: (id, world2, _input, dt) => {
|
|
1808
2217
|
if (!world2.hasEntity(id))
|
|
@@ -1821,7 +2230,7 @@ function MovingPlatform({
|
|
|
1821
2230
|
}, undefined, true, undefined, this);
|
|
1822
2231
|
}
|
|
1823
2232
|
// src/components/Checkpoint.tsx
|
|
1824
|
-
import { jsxDEV as
|
|
2233
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
1825
2234
|
function Checkpoint({
|
|
1826
2235
|
x,
|
|
1827
2236
|
y,
|
|
@@ -1830,25 +2239,25 @@ function Checkpoint({
|
|
|
1830
2239
|
color = "#ffd54f",
|
|
1831
2240
|
onActivate
|
|
1832
2241
|
}) {
|
|
1833
|
-
return /* @__PURE__ */
|
|
2242
|
+
return /* @__PURE__ */ jsxDEV6(Entity, {
|
|
1834
2243
|
tags: ["checkpoint"],
|
|
1835
2244
|
children: [
|
|
1836
|
-
/* @__PURE__ */
|
|
2245
|
+
/* @__PURE__ */ jsxDEV6(Transform, {
|
|
1837
2246
|
x,
|
|
1838
2247
|
y
|
|
1839
2248
|
}, undefined, false, undefined, this),
|
|
1840
|
-
/* @__PURE__ */
|
|
2249
|
+
/* @__PURE__ */ jsxDEV6(Sprite, {
|
|
1841
2250
|
width,
|
|
1842
2251
|
height,
|
|
1843
2252
|
color,
|
|
1844
2253
|
zIndex: 5
|
|
1845
2254
|
}, undefined, false, undefined, this),
|
|
1846
|
-
/* @__PURE__ */
|
|
2255
|
+
/* @__PURE__ */ jsxDEV6(BoxCollider, {
|
|
1847
2256
|
width,
|
|
1848
2257
|
height,
|
|
1849
2258
|
isTrigger: true
|
|
1850
2259
|
}, undefined, false, undefined, this),
|
|
1851
|
-
/* @__PURE__ */
|
|
2260
|
+
/* @__PURE__ */ jsxDEV6(Script, {
|
|
1852
2261
|
init: () => {},
|
|
1853
2262
|
update: (id, world2) => {
|
|
1854
2263
|
if (!world2.hasEntity(id))
|
|
@@ -1877,8 +2286,8 @@ function Checkpoint({
|
|
|
1877
2286
|
}, undefined, true, undefined, this);
|
|
1878
2287
|
}
|
|
1879
2288
|
// src/components/Tilemap.tsx
|
|
1880
|
-
import { useEffect as
|
|
1881
|
-
import { jsxDEV as
|
|
2289
|
+
import { useEffect as useEffect14, useState as useState4, useContext as useContext12 } from "react";
|
|
2290
|
+
import { jsxDEV as jsxDEV7, Fragment as Fragment3 } from "react/jsx-dev-runtime";
|
|
1882
2291
|
var animatedTiles = new Map;
|
|
1883
2292
|
function getProperty(props, name) {
|
|
1884
2293
|
return props?.find((p) => p.name === name)?.value;
|
|
@@ -1902,8 +2311,8 @@ function Tilemap({
|
|
|
1902
2311
|
onTileProperty
|
|
1903
2312
|
}) {
|
|
1904
2313
|
const engine = useContext12(EngineContext);
|
|
1905
|
-
const [spawnedNodes, setSpawnedNodes] =
|
|
1906
|
-
|
|
2314
|
+
const [spawnedNodes, setSpawnedNodes] = useState4([]);
|
|
2315
|
+
useEffect14(() => {
|
|
1907
2316
|
if (!engine)
|
|
1908
2317
|
return;
|
|
1909
2318
|
const createdEntities = [];
|
|
@@ -2094,13 +2503,13 @@ function Tilemap({
|
|
|
2094
2503
|
}, [src]);
|
|
2095
2504
|
if (spawnedNodes.length === 0)
|
|
2096
2505
|
return null;
|
|
2097
|
-
return /* @__PURE__ */
|
|
2506
|
+
return /* @__PURE__ */ jsxDEV7(Fragment3, {
|
|
2098
2507
|
children: spawnedNodes
|
|
2099
2508
|
}, undefined, false, undefined, this);
|
|
2100
2509
|
}
|
|
2101
2510
|
// src/components/ParallaxLayer.tsx
|
|
2102
|
-
import { useEffect as
|
|
2103
|
-
import { jsxDEV as
|
|
2511
|
+
import { useEffect as useEffect15, useContext as useContext13 } from "react";
|
|
2512
|
+
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
2104
2513
|
function ParallaxLayerInner({
|
|
2105
2514
|
src,
|
|
2106
2515
|
speedX,
|
|
@@ -2113,7 +2522,7 @@ function ParallaxLayerInner({
|
|
|
2113
2522
|
}) {
|
|
2114
2523
|
const engine = useContext13(EngineContext);
|
|
2115
2524
|
const entityId = useContext13(EntityContext);
|
|
2116
|
-
|
|
2525
|
+
useEffect15(() => {
|
|
2117
2526
|
engine.ecs.addComponent(entityId, {
|
|
2118
2527
|
type: "ParallaxLayer",
|
|
2119
2528
|
src,
|
|
@@ -2129,7 +2538,7 @@ function ParallaxLayerInner({
|
|
|
2129
2538
|
});
|
|
2130
2539
|
return () => engine.ecs.removeComponent(entityId, "ParallaxLayer");
|
|
2131
2540
|
}, []);
|
|
2132
|
-
|
|
2541
|
+
useEffect15(() => {
|
|
2133
2542
|
const layer = engine.ecs.getComponent(entityId, "ParallaxLayer");
|
|
2134
2543
|
if (!layer)
|
|
2135
2544
|
return;
|
|
@@ -2154,13 +2563,13 @@ function ParallaxLayer({
|
|
|
2154
2563
|
offsetX = 0,
|
|
2155
2564
|
offsetY = 0
|
|
2156
2565
|
}) {
|
|
2157
|
-
return /* @__PURE__ */
|
|
2566
|
+
return /* @__PURE__ */ jsxDEV8(Entity, {
|
|
2158
2567
|
children: [
|
|
2159
|
-
/* @__PURE__ */
|
|
2568
|
+
/* @__PURE__ */ jsxDEV8(Transform, {
|
|
2160
2569
|
x: 0,
|
|
2161
2570
|
y: 0
|
|
2162
2571
|
}, undefined, false, undefined, this),
|
|
2163
|
-
/* @__PURE__ */
|
|
2572
|
+
/* @__PURE__ */ jsxDEV8(ParallaxLayerInner, {
|
|
2164
2573
|
src,
|
|
2165
2574
|
speedX,
|
|
2166
2575
|
speedY,
|
|
@@ -2175,7 +2584,7 @@ function ParallaxLayer({
|
|
|
2175
2584
|
}
|
|
2176
2585
|
// src/components/ScreenFlash.tsx
|
|
2177
2586
|
import { forwardRef, useImperativeHandle, useRef as useRef2 } from "react";
|
|
2178
|
-
import { jsxDEV as
|
|
2587
|
+
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
2179
2588
|
var ScreenFlash = forwardRef((_, ref) => {
|
|
2180
2589
|
const divRef = useRef2(null);
|
|
2181
2590
|
useImperativeHandle(ref, () => ({
|
|
@@ -2197,7 +2606,7 @@ var ScreenFlash = forwardRef((_, ref) => {
|
|
|
2197
2606
|
});
|
|
2198
2607
|
}
|
|
2199
2608
|
}));
|
|
2200
|
-
return /* @__PURE__ */
|
|
2609
|
+
return /* @__PURE__ */ jsxDEV9("div", {
|
|
2201
2610
|
ref: divRef,
|
|
2202
2611
|
style: {
|
|
2203
2612
|
position: "absolute",
|
|
@@ -2235,7 +2644,7 @@ function useInput() {
|
|
|
2235
2644
|
return engine.input;
|
|
2236
2645
|
}
|
|
2237
2646
|
// src/hooks/useEvents.ts
|
|
2238
|
-
import { useContext as useContext17, useEffect as
|
|
2647
|
+
import { useContext as useContext17, useEffect as useEffect16 } from "react";
|
|
2239
2648
|
function useEvents() {
|
|
2240
2649
|
const engine = useContext17(EngineContext);
|
|
2241
2650
|
if (!engine)
|
|
@@ -2244,12 +2653,12 @@ function useEvents() {
|
|
|
2244
2653
|
}
|
|
2245
2654
|
function useEvent(event, handler) {
|
|
2246
2655
|
const events = useEvents();
|
|
2247
|
-
|
|
2656
|
+
useEffect16(() => {
|
|
2248
2657
|
return events.on(event, handler);
|
|
2249
2658
|
}, [events, event]);
|
|
2250
2659
|
}
|
|
2251
2660
|
// src/hooks/usePlatformerController.ts
|
|
2252
|
-
import { useContext as useContext18, useEffect as
|
|
2661
|
+
import { useContext as useContext18, useEffect as useEffect17 } from "react";
|
|
2253
2662
|
function usePlatformerController(entityId, opts = {}) {
|
|
2254
2663
|
const engine = useContext18(EngineContext);
|
|
2255
2664
|
const {
|
|
@@ -2259,7 +2668,7 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
2259
2668
|
coyoteTime = 0.08,
|
|
2260
2669
|
jumpBuffer = 0.08
|
|
2261
2670
|
} = opts;
|
|
2262
|
-
|
|
2671
|
+
useEffect17(() => {
|
|
2263
2672
|
const state = { coyoteTimer: 0, jumpBuffer: 0, jumpsLeft: maxJumps };
|
|
2264
2673
|
const updateFn = (id, world2, input, dt) => {
|
|
2265
2674
|
if (!world2.hasEntity(id))
|
|
@@ -2308,11 +2717,11 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
2308
2717
|
}, []);
|
|
2309
2718
|
}
|
|
2310
2719
|
// src/hooks/useTopDownMovement.ts
|
|
2311
|
-
import { useContext as useContext19, useEffect as
|
|
2720
|
+
import { useContext as useContext19, useEffect as useEffect18 } from "react";
|
|
2312
2721
|
function useTopDownMovement(entityId, opts = {}) {
|
|
2313
2722
|
const engine = useContext19(EngineContext);
|
|
2314
2723
|
const { speed = 200, normalizeDiagonal = true } = opts;
|
|
2315
|
-
|
|
2724
|
+
useEffect18(() => {
|
|
2316
2725
|
const updateFn = (id, world2, input) => {
|
|
2317
2726
|
if (!world2.hasEntity(id))
|
|
2318
2727
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cubeforge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "React-first 2D browser game engine",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"types": "./dist/index.d.ts",
|
|
40
40
|
"exports": {
|
|
41
|
-
"
|
|
41
|
+
".": {
|
|
42
42
|
"import": "./dist/index.js",
|
|
43
43
|
"types": "./dist/index.d.ts"
|
|
44
44
|
}
|