cubeforge 0.3.13 → 0.3.14
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/README.md +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +426 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ npm install cubeforge react react-dom
|
|
|
32
32
|
## What's included
|
|
33
33
|
|
|
34
34
|
- **ECS** — archetype-based entity-component-system with query caching
|
|
35
|
-
- **Physics** — two-pass AABB,
|
|
36
|
-
- **Renderer** — WebGL2 instanced renderer by default
|
|
35
|
+
- **Physics** — two-pass AABB, kinematic bodies, one-way platforms, 60 Hz fixed timestep
|
|
36
|
+
- **Renderer** — WebGL2 instanced renderer by default
|
|
37
37
|
- **Input** — keyboard, mouse, gamepad, per-player input maps, input contexts, recording/playback
|
|
38
38
|
- **Audio** — Web Audio API with volume groups, fade, crossfade, ducking (`useSound`)
|
|
39
39
|
- **Gameplay hooks** — `usePlatformerController`, `useTopDownMovement`, `useHealth`, `useSave`, `useGameStateMachine`, `useLevelTransition`, `usePathfinding`, `useAISteering`, and more
|
package/dist/index.d.ts
CHANGED
|
@@ -153,8 +153,10 @@ interface RigidBodyProps {
|
|
|
153
153
|
lockX?: boolean;
|
|
154
154
|
/** Prevent any vertical movement — velocity.y is zeroed every frame (disables gravity) */
|
|
155
155
|
lockY?: boolean;
|
|
156
|
+
/** Enable continuous collision detection to prevent tunneling through thin colliders */
|
|
157
|
+
ccd?: boolean;
|
|
156
158
|
}
|
|
157
|
-
declare function RigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY, }: RigidBodyProps): null;
|
|
159
|
+
declare function RigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY, ccd, }: RigidBodyProps): null;
|
|
158
160
|
|
|
159
161
|
interface BoxColliderProps {
|
|
160
162
|
width: number;
|
package/dist/index.js
CHANGED
|
@@ -249,14 +249,16 @@ function findByTag(world, tag) {
|
|
|
249
249
|
|
|
250
250
|
// ../../packages/core/src/loop/gameLoop.ts
|
|
251
251
|
var GameLoop = class {
|
|
252
|
-
constructor(onTick) {
|
|
252
|
+
constructor(onTick, options) {
|
|
253
253
|
this.onTick = onTick;
|
|
254
|
+
this.fixedDt = options?.fixedDt;
|
|
254
255
|
}
|
|
255
256
|
rafId = 0;
|
|
256
257
|
lastTime = 0;
|
|
257
258
|
running = false;
|
|
258
259
|
paused = false;
|
|
259
260
|
hitPauseTimer = 0;
|
|
261
|
+
fixedDt;
|
|
260
262
|
hitPause(duration) {
|
|
261
263
|
this.hitPauseTimer = duration;
|
|
262
264
|
}
|
|
@@ -292,8 +294,9 @@ var GameLoop = class {
|
|
|
292
294
|
}
|
|
293
295
|
frame = (time) => {
|
|
294
296
|
if (!this.running) return;
|
|
295
|
-
const
|
|
297
|
+
const rawDt = Math.min((time - this.lastTime) / 1e3, 0.1);
|
|
296
298
|
this.lastTime = time;
|
|
299
|
+
const dt = this.fixedDt ?? rawDt;
|
|
297
300
|
if (this.hitPauseTimer > 0) {
|
|
298
301
|
this.hitPauseTimer -= dt;
|
|
299
302
|
} else {
|
|
@@ -947,11 +950,16 @@ var Mouse = class {
|
|
|
947
950
|
var InputManager = class {
|
|
948
951
|
keyboard = new Keyboard();
|
|
949
952
|
mouse = new Mouse();
|
|
953
|
+
_attachedElement = null;
|
|
950
954
|
attach(canvas) {
|
|
955
|
+
if (this._attachedElement === canvas) return;
|
|
956
|
+
if (this._attachedElement) this.detach();
|
|
957
|
+
this._attachedElement = canvas;
|
|
951
958
|
this.keyboard.attach(window);
|
|
952
959
|
this.mouse.attach(canvas);
|
|
953
960
|
}
|
|
954
961
|
detach() {
|
|
962
|
+
this._attachedElement = null;
|
|
955
963
|
this.keyboard.detach();
|
|
956
964
|
this.mouse.detach();
|
|
957
965
|
}
|
|
@@ -1391,6 +1399,7 @@ function parseCSSColor(css) {
|
|
|
1391
1399
|
// ../../packages/renderer/src/webglRenderSystem.ts
|
|
1392
1400
|
var FLOATS_PER_INSTANCE = 18;
|
|
1393
1401
|
var MAX_INSTANCES = 8192;
|
|
1402
|
+
var MAX_SPRITE_TEXTURES = 512;
|
|
1394
1403
|
var MAX_TEXT_CACHE = 200;
|
|
1395
1404
|
function compileShader(gl, type, src) {
|
|
1396
1405
|
const shader = gl.createShader(type);
|
|
@@ -1571,6 +1580,8 @@ var RenderSystem = class {
|
|
|
1571
1580
|
instanceData;
|
|
1572
1581
|
whiteTexture;
|
|
1573
1582
|
textures = /* @__PURE__ */ new Map();
|
|
1583
|
+
/** Tracks texture access order for LRU eviction (most recent at end). */
|
|
1584
|
+
textureLRU = [];
|
|
1574
1585
|
imageCache = /* @__PURE__ */ new Map();
|
|
1575
1586
|
// Cached uniform locations — sprite program
|
|
1576
1587
|
uCamPos;
|
|
@@ -1603,6 +1614,21 @@ var RenderSystem = class {
|
|
|
1603
1614
|
getDefaultSampling() {
|
|
1604
1615
|
return this._defaultSampling;
|
|
1605
1616
|
}
|
|
1617
|
+
// ── Sprite texture LRU helpers ──────────────────────────────────────────
|
|
1618
|
+
/** Record a texture key as recently used; evict LRU entries if over limit. */
|
|
1619
|
+
touchTexture(key) {
|
|
1620
|
+
const idx = this.textureLRU.indexOf(key);
|
|
1621
|
+
if (idx !== -1) this.textureLRU.splice(idx, 1);
|
|
1622
|
+
this.textureLRU.push(key);
|
|
1623
|
+
while (this.textureLRU.length > MAX_SPRITE_TEXTURES) {
|
|
1624
|
+
const evict = this.textureLRU.shift();
|
|
1625
|
+
const tex = this.textures.get(evict);
|
|
1626
|
+
if (tex) {
|
|
1627
|
+
this.gl.deleteTexture(tex);
|
|
1628
|
+
this.textures.delete(evict);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1606
1632
|
// ── Debug overlays ──────────────────────────────────────────────────────
|
|
1607
1633
|
debugNavGrid = null;
|
|
1608
1634
|
contactFlashPoints = [];
|
|
@@ -1620,7 +1646,10 @@ var RenderSystem = class {
|
|
|
1620
1646
|
// ── Texture management (sprite textures — CLAMP_TO_EDGE) ──────────────────
|
|
1621
1647
|
loadTexture(src) {
|
|
1622
1648
|
const cached = this.textures.get(src);
|
|
1623
|
-
if (cached)
|
|
1649
|
+
if (cached) {
|
|
1650
|
+
this.touchTexture(src);
|
|
1651
|
+
return cached;
|
|
1652
|
+
}
|
|
1624
1653
|
let imgSrc = src;
|
|
1625
1654
|
const sampIdx = imgSrc.indexOf(":s=");
|
|
1626
1655
|
if (sampIdx !== -1) imgSrc = imgSrc.slice(0, sampIdx);
|
|
@@ -1636,6 +1665,7 @@ var RenderSystem = class {
|
|
|
1636
1665
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1637
1666
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1638
1667
|
this.textures.set(src, tex);
|
|
1668
|
+
this.touchTexture(src);
|
|
1639
1669
|
return tex;
|
|
1640
1670
|
}
|
|
1641
1671
|
if (!existing) {
|
|
@@ -1653,6 +1683,11 @@ var RenderSystem = class {
|
|
|
1653
1683
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
|
|
1654
1684
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
|
|
1655
1685
|
this.textures.set(src, tex);
|
|
1686
|
+
this.touchTexture(src);
|
|
1687
|
+
};
|
|
1688
|
+
img.onerror = () => {
|
|
1689
|
+
console.warn(`[WebGLRenderSystem] Failed to load image: ${imgSrc}`);
|
|
1690
|
+
this.imageCache.delete(imgSrc);
|
|
1656
1691
|
};
|
|
1657
1692
|
this.imageCache.set(imgSrc, img);
|
|
1658
1693
|
}
|
|
@@ -1943,6 +1978,9 @@ var RenderSystem = class {
|
|
|
1943
1978
|
gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_WRAP_S, wrap);
|
|
1944
1979
|
gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_WRAP_T, wrap);
|
|
1945
1980
|
this.textures.set(cacheKey, tex);
|
|
1981
|
+
this.touchTexture(cacheKey);
|
|
1982
|
+
} else if (cacheKey) {
|
|
1983
|
+
this.touchTexture(cacheKey);
|
|
1946
1984
|
}
|
|
1947
1985
|
} else if (sprite.src && !sprite.image) {
|
|
1948
1986
|
let img = this.imageCache.get(sprite.src);
|
|
@@ -1958,6 +1996,11 @@ var RenderSystem = class {
|
|
|
1958
1996
|
gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MIN_FILTER, gl2.NEAREST);
|
|
1959
1997
|
gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MAG_FILTER, gl2.NEAREST);
|
|
1960
1998
|
this.textures.set(img.src, tex);
|
|
1999
|
+
this.touchTexture(img.src);
|
|
2000
|
+
};
|
|
2001
|
+
img.onerror = () => {
|
|
2002
|
+
console.warn(`[WebGLRenderSystem] Failed to load image: ${img.src}`);
|
|
2003
|
+
this.imageCache.delete(img.src);
|
|
1961
2004
|
};
|
|
1962
2005
|
}
|
|
1963
2006
|
sprite.image = img;
|
|
@@ -2266,6 +2309,7 @@ function createRigidBody(opts) {
|
|
|
2266
2309
|
lockY: false,
|
|
2267
2310
|
isKinematic: false,
|
|
2268
2311
|
dropThrough: 0,
|
|
2312
|
+
ccd: false,
|
|
2269
2313
|
...opts
|
|
2270
2314
|
};
|
|
2271
2315
|
}
|
|
@@ -2337,6 +2381,14 @@ function getAABB(transform, collider) {
|
|
|
2337
2381
|
hh: collider.height / 2
|
|
2338
2382
|
};
|
|
2339
2383
|
}
|
|
2384
|
+
function getCapsuleAABB(transform, capsule) {
|
|
2385
|
+
return {
|
|
2386
|
+
cx: transform.x + capsule.offsetX,
|
|
2387
|
+
cy: transform.y + capsule.offsetY,
|
|
2388
|
+
hw: capsule.width / 2,
|
|
2389
|
+
hh: capsule.height / 2
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2340
2392
|
function getOverlap(a, b) {
|
|
2341
2393
|
const dx = a.cx - b.cx;
|
|
2342
2394
|
const dy = a.cy - b.cy;
|
|
@@ -2439,6 +2491,36 @@ function getCompoundBounds(tx, ty, shapes) {
|
|
|
2439
2491
|
function canInteractGeneric(aLayer, aMask, bLayer, bMask) {
|
|
2440
2492
|
return maskAllows(aMask, bLayer) && maskAllows(bMask, aLayer);
|
|
2441
2493
|
}
|
|
2494
|
+
function sweepAABB(aCx, aCy, aHw, aHh, dx, dy, b) {
|
|
2495
|
+
const eHw = b.hw + aHw;
|
|
2496
|
+
const eHh = b.hh + aHh;
|
|
2497
|
+
const left = b.cx - eHw;
|
|
2498
|
+
const right = b.cx + eHw;
|
|
2499
|
+
const top = b.cy - eHh;
|
|
2500
|
+
const bottom = b.cy + eHh;
|
|
2501
|
+
let tmin = -Infinity;
|
|
2502
|
+
let tmax = Infinity;
|
|
2503
|
+
if (dx !== 0) {
|
|
2504
|
+
const t1 = (left - aCx) / dx;
|
|
2505
|
+
const t2 = (right - aCx) / dx;
|
|
2506
|
+
tmin = Math.max(tmin, Math.min(t1, t2));
|
|
2507
|
+
tmax = Math.min(tmax, Math.max(t1, t2));
|
|
2508
|
+
} else if (aCx < left || aCx > right) {
|
|
2509
|
+
return null;
|
|
2510
|
+
}
|
|
2511
|
+
if (dy !== 0) {
|
|
2512
|
+
const t1 = (top - aCy) / dy;
|
|
2513
|
+
const t2 = (bottom - aCy) / dy;
|
|
2514
|
+
tmin = Math.max(tmin, Math.min(t1, t2));
|
|
2515
|
+
tmax = Math.min(tmax, Math.max(t1, t2));
|
|
2516
|
+
} else if (aCy < top || aCy > bottom) {
|
|
2517
|
+
return null;
|
|
2518
|
+
}
|
|
2519
|
+
if (tmax < 0 || tmin > tmax || tmin > 1) return null;
|
|
2520
|
+
const t = Math.max(0, tmin);
|
|
2521
|
+
if (t > 1) return null;
|
|
2522
|
+
return t;
|
|
2523
|
+
}
|
|
2442
2524
|
function pairKey(a, b) {
|
|
2443
2525
|
return a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
2444
2526
|
}
|
|
@@ -2449,11 +2531,15 @@ var PhysicsSystem = class {
|
|
|
2449
2531
|
}
|
|
2450
2532
|
accumulator = 0;
|
|
2451
2533
|
FIXED_DT = 1 / 60;
|
|
2534
|
+
/** Maximum accumulated time (seconds). Prevents hundreds of sub-steps when
|
|
2535
|
+
* the tab is backgrounded and dt spikes on resume. 0.1s ≈ 6 steps at 60Hz. */
|
|
2536
|
+
MAX_ACCUMULATOR = 0.1;
|
|
2452
2537
|
// Active contact sets — updated each physics step.
|
|
2453
2538
|
activeTriggerPairs = /* @__PURE__ */ new Map();
|
|
2454
2539
|
activeCollisionPairs = /* @__PURE__ */ new Map();
|
|
2455
2540
|
activeCirclePairs = /* @__PURE__ */ new Map();
|
|
2456
2541
|
activeCompoundPairs = /* @__PURE__ */ new Map();
|
|
2542
|
+
activeCapsulePairs = /* @__PURE__ */ new Map();
|
|
2457
2543
|
// Previous-frame positions of static entities — used to compute platform carry delta.
|
|
2458
2544
|
staticPrevPos = /* @__PURE__ */ new Map();
|
|
2459
2545
|
setGravity(g) {
|
|
@@ -2461,8 +2547,8 @@ var PhysicsSystem = class {
|
|
|
2461
2547
|
}
|
|
2462
2548
|
update(world, dt) {
|
|
2463
2549
|
this.accumulator += dt;
|
|
2464
|
-
if (this.accumulator >
|
|
2465
|
-
this.accumulator =
|
|
2550
|
+
if (this.accumulator > this.MAX_ACCUMULATOR) {
|
|
2551
|
+
this.accumulator = this.MAX_ACCUMULATOR;
|
|
2466
2552
|
}
|
|
2467
2553
|
while (this.accumulator >= this.FIXED_DT) {
|
|
2468
2554
|
this.step(world, this.FIXED_DT);
|
|
@@ -2490,6 +2576,12 @@ var PhysicsSystem = class {
|
|
|
2490
2576
|
if (rb.isStatic) statics.push(id);
|
|
2491
2577
|
else dynamics.push(id);
|
|
2492
2578
|
}
|
|
2579
|
+
const allCapsule = world.query("Transform", "RigidBody", "CapsuleCollider");
|
|
2580
|
+
const capsuleDynamics = [];
|
|
2581
|
+
for (const id of allCapsule) {
|
|
2582
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2583
|
+
if (!rb.isStatic) capsuleDynamics.push(id);
|
|
2584
|
+
}
|
|
2493
2585
|
for (const [key, [a, b]] of this.activeTriggerPairs) {
|
|
2494
2586
|
if (!world.hasEntity(a) || !world.hasEntity(b)) {
|
|
2495
2587
|
this.events?.emit("triggerExit", { a, b });
|
|
@@ -2514,6 +2606,12 @@ var PhysicsSystem = class {
|
|
|
2514
2606
|
this.activeCompoundPairs.delete(key);
|
|
2515
2607
|
}
|
|
2516
2608
|
}
|
|
2609
|
+
for (const [key, [a, b]] of this.activeCapsulePairs) {
|
|
2610
|
+
if (!world.hasEntity(a) || !world.hasEntity(b)) {
|
|
2611
|
+
this.events?.emit("capsuleExit", { a, b });
|
|
2612
|
+
this.activeCapsulePairs.delete(key);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2517
2615
|
const staticDelta = /* @__PURE__ */ new Map();
|
|
2518
2616
|
for (const sid of statics) {
|
|
2519
2617
|
const st = world.getComponent(sid, "Transform");
|
|
@@ -2548,6 +2646,24 @@ var PhysicsSystem = class {
|
|
|
2548
2646
|
if (rb.lockY) rb.vy = 0;
|
|
2549
2647
|
if (rb.dropThrough > 0) rb.dropThrough--;
|
|
2550
2648
|
}
|
|
2649
|
+
const ccdPrev = /* @__PURE__ */ new Map();
|
|
2650
|
+
for (const id of dynamics) {
|
|
2651
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2652
|
+
if (rb.ccd) {
|
|
2653
|
+
const t = world.getComponent(id, "Transform");
|
|
2654
|
+
ccdPrev.set(id, { x: t.x, y: t.y });
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
for (const id of capsuleDynamics) {
|
|
2658
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2659
|
+
rb.onGround = false;
|
|
2660
|
+
rb.isNearGround = false;
|
|
2661
|
+
if (rb.isKinematic) continue;
|
|
2662
|
+
if (!rb.lockY) rb.vy += this.gravity * rb.gravityScale * dt;
|
|
2663
|
+
if (rb.lockX) rb.vx = 0;
|
|
2664
|
+
if (rb.lockY) rb.vy = 0;
|
|
2665
|
+
if (rb.dropThrough > 0) rb.dropThrough--;
|
|
2666
|
+
}
|
|
2551
2667
|
for (const id of dynamics) {
|
|
2552
2668
|
const transform = world.getComponent(id, "Transform");
|
|
2553
2669
|
const rb = world.getComponent(id, "RigidBody");
|
|
@@ -2578,6 +2694,36 @@ var PhysicsSystem = class {
|
|
|
2578
2694
|
}
|
|
2579
2695
|
}
|
|
2580
2696
|
}
|
|
2697
|
+
for (const id of capsuleDynamics) {
|
|
2698
|
+
const transform = world.getComponent(id, "Transform");
|
|
2699
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2700
|
+
const cap = world.getComponent(id, "CapsuleCollider");
|
|
2701
|
+
transform.x += rb.vx * dt;
|
|
2702
|
+
if (!cap.isTrigger) {
|
|
2703
|
+
const dynAABB = getCapsuleAABB(transform, cap);
|
|
2704
|
+
const candidateCells = this.getCells(dynAABB.cx, dynAABB.cy, dynAABB.hw, dynAABB.hh);
|
|
2705
|
+
const checked = /* @__PURE__ */ new Set();
|
|
2706
|
+
for (const cell of candidateCells) {
|
|
2707
|
+
const bucket = staticGrid.get(cell);
|
|
2708
|
+
if (!bucket) continue;
|
|
2709
|
+
for (const sid of bucket) {
|
|
2710
|
+
if (checked.has(sid)) continue;
|
|
2711
|
+
checked.add(sid);
|
|
2712
|
+
const st = world.getComponent(sid, "Transform");
|
|
2713
|
+
const sc = world.getComponent(sid, "BoxCollider");
|
|
2714
|
+
if (sc.isTrigger) continue;
|
|
2715
|
+
if (sc.slope !== 0) continue;
|
|
2716
|
+
if (!canInteractGeneric(cap.layer, cap.mask, sc.layer, sc.mask)) continue;
|
|
2717
|
+
const ov = getOverlap(getCapsuleAABB(transform, cap), getAABB(st, sc));
|
|
2718
|
+
if (!ov) continue;
|
|
2719
|
+
if (Math.abs(ov.x) < Math.abs(ov.y)) {
|
|
2720
|
+
transform.x += ov.x;
|
|
2721
|
+
rb.vx = rb.bounce > 0 ? -rb.vx * rb.bounce : 0;
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2581
2727
|
for (const id of dynamics) {
|
|
2582
2728
|
const transform = world.getComponent(id, "Transform");
|
|
2583
2729
|
const rb = world.getComponent(id, "RigidBody");
|
|
@@ -2637,6 +2783,127 @@ var PhysicsSystem = class {
|
|
|
2637
2783
|
}
|
|
2638
2784
|
}
|
|
2639
2785
|
}
|
|
2786
|
+
for (const [id, prev] of ccdPrev) {
|
|
2787
|
+
const transform = world.getComponent(id, "Transform");
|
|
2788
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2789
|
+
const col = world.getComponent(id, "BoxCollider");
|
|
2790
|
+
const totalDx = transform.x - prev.x;
|
|
2791
|
+
const totalDy = transform.y - prev.y;
|
|
2792
|
+
const moveLen = Math.abs(totalDx) + Math.abs(totalDy);
|
|
2793
|
+
const halfSize = Math.min(col.width, col.height) / 2;
|
|
2794
|
+
if (moveLen <= halfSize) continue;
|
|
2795
|
+
const startCx = prev.x + col.offsetX;
|
|
2796
|
+
const startCy = prev.y + col.offsetY;
|
|
2797
|
+
const sweepDx = totalDx;
|
|
2798
|
+
const sweepDy = totalDy;
|
|
2799
|
+
const hw = col.width / 2;
|
|
2800
|
+
const hh = col.height / 2;
|
|
2801
|
+
let earliestT = 1;
|
|
2802
|
+
let hitSid = null;
|
|
2803
|
+
const endCx = startCx + sweepDx;
|
|
2804
|
+
const endCy = startCy + sweepDy;
|
|
2805
|
+
const minCx = Math.min(startCx, endCx);
|
|
2806
|
+
const maxCx = Math.max(startCx, endCx);
|
|
2807
|
+
const minCy = Math.min(startCy, endCy);
|
|
2808
|
+
const maxCy = Math.max(startCy, endCy);
|
|
2809
|
+
const sweepCells = this.getCells(
|
|
2810
|
+
(minCx + maxCx) / 2,
|
|
2811
|
+
(minCy + maxCy) / 2,
|
|
2812
|
+
(maxCx - minCx) / 2 + hw,
|
|
2813
|
+
(maxCy - minCy) / 2 + hh
|
|
2814
|
+
);
|
|
2815
|
+
const checked = /* @__PURE__ */ new Set();
|
|
2816
|
+
for (const cell of sweepCells) {
|
|
2817
|
+
const bucket = staticGrid.get(cell);
|
|
2818
|
+
if (!bucket) continue;
|
|
2819
|
+
for (const sid of bucket) {
|
|
2820
|
+
if (checked.has(sid)) continue;
|
|
2821
|
+
checked.add(sid);
|
|
2822
|
+
const st = world.getComponent(sid, "Transform");
|
|
2823
|
+
const sc = world.getComponent(sid, "BoxCollider");
|
|
2824
|
+
if (sc.isTrigger) continue;
|
|
2825
|
+
if (!canInteract(col, sc)) continue;
|
|
2826
|
+
const staticAABB = getAABB(st, sc);
|
|
2827
|
+
const t = sweepAABB(startCx, startCy, hw, hh, sweepDx, sweepDy, staticAABB);
|
|
2828
|
+
if (t !== null && t < earliestT) {
|
|
2829
|
+
earliestT = t;
|
|
2830
|
+
hitSid = sid;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
if (hitSid !== null && earliestT < 1) {
|
|
2835
|
+
const eps = 0.01;
|
|
2836
|
+
const clampedT = Math.max(0, earliestT - eps / (Math.hypot(sweepDx, sweepDy) || 1));
|
|
2837
|
+
transform.x = prev.x + totalDx * clampedT;
|
|
2838
|
+
transform.y = prev.y + totalDy * clampedT;
|
|
2839
|
+
const st = world.getComponent(hitSid, "Transform");
|
|
2840
|
+
const sc = world.getComponent(hitSid, "BoxCollider");
|
|
2841
|
+
const staticAABB = getAABB(st, sc);
|
|
2842
|
+
const contactCx = prev.x + col.offsetX + totalDx * earliestT;
|
|
2843
|
+
const contactCy = prev.y + col.offsetY + totalDy * earliestT;
|
|
2844
|
+
const dxFromCenter = contactCx - staticAABB.cx;
|
|
2845
|
+
const dyFromCenter = contactCy - staticAABB.cy;
|
|
2846
|
+
const overlapX = hw + staticAABB.hw - Math.abs(dxFromCenter);
|
|
2847
|
+
const overlapY = hh + staticAABB.hh - Math.abs(dyFromCenter);
|
|
2848
|
+
if (overlapX > overlapY) {
|
|
2849
|
+
rb.vy = rb.bounce > 0 ? -rb.vy * rb.bounce : 0;
|
|
2850
|
+
if (dyFromCenter < 0) {
|
|
2851
|
+
rb.onGround = true;
|
|
2852
|
+
if (rb.friction < 1) rb.vx *= rb.friction;
|
|
2853
|
+
}
|
|
2854
|
+
} else {
|
|
2855
|
+
rb.vx = rb.bounce > 0 ? -rb.vx * rb.bounce : 0;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
for (const id of capsuleDynamics) {
|
|
2860
|
+
const transform = world.getComponent(id, "Transform");
|
|
2861
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2862
|
+
const cap = world.getComponent(id, "CapsuleCollider");
|
|
2863
|
+
transform.y += rb.vy * dt;
|
|
2864
|
+
if (!cap.isTrigger) {
|
|
2865
|
+
const dynAABB = getCapsuleAABB(transform, cap);
|
|
2866
|
+
const candidateCells = this.getCells(dynAABB.cx, dynAABB.cy, dynAABB.hw, dynAABB.hh);
|
|
2867
|
+
const checked = /* @__PURE__ */ new Set();
|
|
2868
|
+
for (const cell of candidateCells) {
|
|
2869
|
+
const bucket = staticGrid.get(cell);
|
|
2870
|
+
if (!bucket) continue;
|
|
2871
|
+
for (const sid of bucket) {
|
|
2872
|
+
if (checked.has(sid)) continue;
|
|
2873
|
+
checked.add(sid);
|
|
2874
|
+
const st = world.getComponent(sid, "Transform");
|
|
2875
|
+
const sc = world.getComponent(sid, "BoxCollider");
|
|
2876
|
+
if (sc.isTrigger) continue;
|
|
2877
|
+
if (!canInteractGeneric(cap.layer, cap.mask, sc.layer, sc.mask)) continue;
|
|
2878
|
+
if (sc.slope !== 0) continue;
|
|
2879
|
+
const ov = getOverlap(getCapsuleAABB(transform, cap), getAABB(st, sc));
|
|
2880
|
+
if (!ov) continue;
|
|
2881
|
+
if (Math.abs(ov.y) <= Math.abs(ov.x)) {
|
|
2882
|
+
if (sc.oneWay) {
|
|
2883
|
+
if (rb.dropThrough > 0) continue;
|
|
2884
|
+
if (ov.y >= 0) continue;
|
|
2885
|
+
const platformTop = st.y + sc.offsetY - sc.height / 2;
|
|
2886
|
+
const prevEntityBottom = transform.y - rb.vy * dt + cap.offsetY + cap.height / 2;
|
|
2887
|
+
if (prevEntityBottom > platformTop) continue;
|
|
2888
|
+
}
|
|
2889
|
+
transform.y += ov.y;
|
|
2890
|
+
if (ov.y < 0) {
|
|
2891
|
+
rb.onGround = true;
|
|
2892
|
+
if (rb.friction < 1) rb.vx *= rb.friction;
|
|
2893
|
+
const delta = staticDelta.get(sid);
|
|
2894
|
+
if (delta) {
|
|
2895
|
+
transform.x += delta.dx;
|
|
2896
|
+
if (delta.dy < 0) transform.y += delta.dy;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
rb.vy = rb.bounce > 0 ? -rb.vy * rb.bounce : 0;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
const POSITION_SLOP = 0.5;
|
|
2906
|
+
const CORRECTION_FACTOR = 0.4;
|
|
2640
2907
|
const currentCollisionPairs = /* @__PURE__ */ new Map();
|
|
2641
2908
|
for (let i = 0; i < dynamics.length; i++) {
|
|
2642
2909
|
for (let j = i + 1; j < dynamics.length; j++) {
|
|
@@ -2667,10 +2934,14 @@ var PhysicsSystem = class {
|
|
|
2667
2934
|
rba.onGround = true;
|
|
2668
2935
|
}
|
|
2669
2936
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2937
|
+
const absOx = Math.abs(ov.x);
|
|
2938
|
+
const absOy = Math.abs(ov.y);
|
|
2939
|
+
const corrX = absOx > POSITION_SLOP ? Math.sign(ov.x) * (absOx - POSITION_SLOP) * CORRECTION_FACTOR : 0;
|
|
2940
|
+
const corrY = absOy > POSITION_SLOP ? Math.sign(ov.y) * (absOy - POSITION_SLOP) * CORRECTION_FACTOR : 0;
|
|
2941
|
+
ta.x += corrX / 2;
|
|
2942
|
+
ta.y += corrY / 2;
|
|
2943
|
+
tb.x -= corrX / 2;
|
|
2944
|
+
tb.y -= corrY / 2;
|
|
2674
2945
|
const key = pairKey(ia, ib);
|
|
2675
2946
|
currentCollisionPairs.set(key, [ia, ib]);
|
|
2676
2947
|
}
|
|
@@ -2723,6 +2994,40 @@ var PhysicsSystem = class {
|
|
|
2723
2994
|
}
|
|
2724
2995
|
}
|
|
2725
2996
|
}
|
|
2997
|
+
for (const id of capsuleDynamics) {
|
|
2998
|
+
const rb = world.getComponent(id, "RigidBody");
|
|
2999
|
+
if (rb.onGround) {
|
|
3000
|
+
rb.isNearGround = true;
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
const transform = world.getComponent(id, "Transform");
|
|
3004
|
+
const cap = world.getComponent(id, "CapsuleCollider");
|
|
3005
|
+
const probeAABB = {
|
|
3006
|
+
cx: transform.x + cap.offsetX,
|
|
3007
|
+
cy: transform.y + cap.offsetY + 2,
|
|
3008
|
+
hw: cap.width / 2,
|
|
3009
|
+
hh: cap.height / 2
|
|
3010
|
+
};
|
|
3011
|
+
const candidateCells = this.getCells(probeAABB.cx, probeAABB.cy, probeAABB.hw, probeAABB.hh);
|
|
3012
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3013
|
+
outerCapsule: for (const cell of candidateCells) {
|
|
3014
|
+
const bucket = staticGrid.get(cell);
|
|
3015
|
+
if (!bucket) continue;
|
|
3016
|
+
for (const sid of bucket) {
|
|
3017
|
+
if (checked.has(sid)) continue;
|
|
3018
|
+
checked.add(sid);
|
|
3019
|
+
const st = world.getComponent(sid, "Transform");
|
|
3020
|
+
const sc = world.getComponent(sid, "BoxCollider");
|
|
3021
|
+
if (sc.isTrigger) continue;
|
|
3022
|
+
if (!canInteractGeneric(cap.layer, cap.mask, sc.layer, sc.mask)) continue;
|
|
3023
|
+
const ov = getOverlap(probeAABB, getAABB(st, sc));
|
|
3024
|
+
if (ov && Math.abs(ov.y) <= Math.abs(ov.x) && ov.y < 0) {
|
|
3025
|
+
rb.isNearGround = true;
|
|
3026
|
+
break outerCapsule;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
2726
3031
|
const allWithCollider = world.query("Transform", "BoxCollider");
|
|
2727
3032
|
const currentTriggerPairs = /* @__PURE__ */ new Map();
|
|
2728
3033
|
for (let i = 0; i < allWithCollider.length; i++) {
|
|
@@ -2911,6 +3216,55 @@ var PhysicsSystem = class {
|
|
|
2911
3216
|
}
|
|
2912
3217
|
this.activeCompoundPairs = /* @__PURE__ */ new Map();
|
|
2913
3218
|
}
|
|
3219
|
+
if (allCapsule.length > 0) {
|
|
3220
|
+
const currentCapsulePairs = /* @__PURE__ */ new Map();
|
|
3221
|
+
const allBoxForCapsule = world.query("Transform", "BoxCollider");
|
|
3222
|
+
for (const cid of allCapsule) {
|
|
3223
|
+
const cc = world.getComponent(cid, "CapsuleCollider");
|
|
3224
|
+
const ct = world.getComponent(cid, "Transform");
|
|
3225
|
+
const capsuleAABB = getCapsuleAABB(ct, cc);
|
|
3226
|
+
for (const bid of allBoxForCapsule) {
|
|
3227
|
+
if (bid === cid) continue;
|
|
3228
|
+
const bc = world.getComponent(bid, "BoxCollider");
|
|
3229
|
+
if (!canInteractGeneric(cc.layer, cc.mask, bc.layer, bc.mask)) continue;
|
|
3230
|
+
const bt = world.getComponent(bid, "Transform");
|
|
3231
|
+
const ov = getOverlap(capsuleAABB, getAABB(bt, bc));
|
|
3232
|
+
if (ov) currentCapsulePairs.set(pairKey(cid, bid), [cid, bid]);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
for (let i = 0; i < allCapsule.length; i++) {
|
|
3236
|
+
for (let j = i + 1; j < allCapsule.length; j++) {
|
|
3237
|
+
const ia = allCapsule[i];
|
|
3238
|
+
const ib = allCapsule[j];
|
|
3239
|
+
const ca = world.getComponent(ia, "CapsuleCollider");
|
|
3240
|
+
const cb = world.getComponent(ib, "CapsuleCollider");
|
|
3241
|
+
if (!canInteractGeneric(ca.layer, ca.mask, cb.layer, cb.mask)) continue;
|
|
3242
|
+
const ta = world.getComponent(ia, "Transform");
|
|
3243
|
+
const tb = world.getComponent(ib, "Transform");
|
|
3244
|
+
const ov = getOverlap(getCapsuleAABB(ta, ca), getCapsuleAABB(tb, cb));
|
|
3245
|
+
if (ov) currentCapsulePairs.set(pairKey(ia, ib), [ia, ib]);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
for (const [key, [a, b]] of currentCapsulePairs) {
|
|
3249
|
+
if (!this.activeCapsulePairs.has(key)) {
|
|
3250
|
+
this.events?.emit("capsuleEnter", { a, b });
|
|
3251
|
+
} else {
|
|
3252
|
+
this.events?.emit("capsuleStay", { a, b });
|
|
3253
|
+
}
|
|
3254
|
+
this.events?.emit("capsule", { a, b });
|
|
3255
|
+
}
|
|
3256
|
+
for (const [key, [a, b]] of this.activeCapsulePairs) {
|
|
3257
|
+
if (!currentCapsulePairs.has(key)) {
|
|
3258
|
+
this.events?.emit("capsuleExit", { a, b });
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
this.activeCapsulePairs = currentCapsulePairs;
|
|
3262
|
+
} else if (this.activeCapsulePairs.size > 0) {
|
|
3263
|
+
for (const [, [a, b]] of this.activeCapsulePairs) {
|
|
3264
|
+
this.events?.emit("capsuleExit", { a, b });
|
|
3265
|
+
}
|
|
3266
|
+
this.activeCapsulePairs = /* @__PURE__ */ new Map();
|
|
3267
|
+
}
|
|
2914
3268
|
}
|
|
2915
3269
|
};
|
|
2916
3270
|
|
|
@@ -3928,7 +4282,7 @@ function Game({
|
|
|
3928
4282
|
if (handle.buffer.length > MAX_DEVTOOLS_FRAMES) handle.buffer.shift();
|
|
3929
4283
|
handle.onFrame?.();
|
|
3930
4284
|
}
|
|
3931
|
-
});
|
|
4285
|
+
}, deterministic ? { fixedDt: 1 / 60 } : void 0);
|
|
3932
4286
|
const state = {
|
|
3933
4287
|
ecs,
|
|
3934
4288
|
input,
|
|
@@ -4019,6 +4373,12 @@ function Game({
|
|
|
4019
4373
|
cancelled = true;
|
|
4020
4374
|
};
|
|
4021
4375
|
}, [engine]);
|
|
4376
|
+
useEffect3(() => {
|
|
4377
|
+
if (!engine) return;
|
|
4378
|
+
const canvas = engine.canvas;
|
|
4379
|
+
if (canvas.width !== width) canvas.width = width;
|
|
4380
|
+
if (canvas.height !== height) canvas.height = height;
|
|
4381
|
+
}, [width, height, engine]);
|
|
4022
4382
|
useEffect3(() => {
|
|
4023
4383
|
engine?.physics.setGravity(gravity);
|
|
4024
4384
|
}, [gravity, engine]);
|
|
@@ -4139,7 +4499,7 @@ function Entity({ id, tags = [], children }) {
|
|
|
4139
4499
|
setEntityId(eid);
|
|
4140
4500
|
return () => {
|
|
4141
4501
|
engine.ecs.destroyEntity(eid);
|
|
4142
|
-
if (id) engine.entityIds.delete(id);
|
|
4502
|
+
if (id && engine.entityIds.get(id) === eid) engine.entityIds.delete(id);
|
|
4143
4503
|
};
|
|
4144
4504
|
}, []);
|
|
4145
4505
|
if (entityId === null) return null;
|
|
@@ -4301,12 +4661,13 @@ function RigidBody({
|
|
|
4301
4661
|
vx = 0,
|
|
4302
4662
|
vy = 0,
|
|
4303
4663
|
lockX = false,
|
|
4304
|
-
lockY = false
|
|
4664
|
+
lockY = false,
|
|
4665
|
+
ccd = false
|
|
4305
4666
|
}) {
|
|
4306
4667
|
const engine = useContext7(EngineContext);
|
|
4307
4668
|
const entityId = useContext7(EntityContext);
|
|
4308
4669
|
useEffect9(() => {
|
|
4309
|
-
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY }));
|
|
4670
|
+
engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY, ccd }));
|
|
4310
4671
|
return () => engine.ecs.removeComponent(entityId, "RigidBody");
|
|
4311
4672
|
}, []);
|
|
4312
4673
|
return null;
|
|
@@ -4406,19 +4767,24 @@ function CompoundCollider({
|
|
|
4406
4767
|
}
|
|
4407
4768
|
|
|
4408
4769
|
// src/components/Script.tsx
|
|
4409
|
-
import { useEffect as useEffect14, useContext as useContext12 } from "react";
|
|
4770
|
+
import { useEffect as useEffect14, useContext as useContext12, useRef as useRef4 } from "react";
|
|
4410
4771
|
function Script({ init, update }) {
|
|
4411
4772
|
const engine = useContext12(EngineContext);
|
|
4412
4773
|
const entityId = useContext12(EntityContext);
|
|
4774
|
+
const initRef = useRef4(init);
|
|
4775
|
+
initRef.current = init;
|
|
4776
|
+
const updateRef = useRef4(update);
|
|
4777
|
+
updateRef.current = update;
|
|
4413
4778
|
useEffect14(() => {
|
|
4414
|
-
if (
|
|
4779
|
+
if (initRef.current) {
|
|
4415
4780
|
try {
|
|
4416
|
-
|
|
4781
|
+
initRef.current(entityId, engine.ecs);
|
|
4417
4782
|
} catch (err) {
|
|
4418
4783
|
console.error(`[Cubeforge] Script init error on entity ${entityId}:`, err);
|
|
4419
4784
|
}
|
|
4420
4785
|
}
|
|
4421
|
-
|
|
4786
|
+
const stableUpdate = (id, world, input, dt) => updateRef.current(id, world, input, dt);
|
|
4787
|
+
engine.ecs.addComponent(entityId, createScript(stableUpdate));
|
|
4422
4788
|
return () => engine.ecs.removeComponent(entityId, "Script");
|
|
4423
4789
|
}, []);
|
|
4424
4790
|
return null;
|
|
@@ -4648,7 +5014,7 @@ function ParticleEmitter({
|
|
|
4648
5014
|
}
|
|
4649
5015
|
|
|
4650
5016
|
// src/components/VirtualJoystick.tsx
|
|
4651
|
-
import { useRef as
|
|
5017
|
+
import { useRef as useRef5 } from "react";
|
|
4652
5018
|
|
|
4653
5019
|
// src/hooks/useVirtualInput.ts
|
|
4654
5020
|
var _axes = { x: 0, y: 0 };
|
|
@@ -4682,10 +5048,10 @@ function VirtualJoystick({
|
|
|
4682
5048
|
actionLabel = "A",
|
|
4683
5049
|
actionName = "action"
|
|
4684
5050
|
}) {
|
|
4685
|
-
const baseRef =
|
|
4686
|
-
const stickRef =
|
|
4687
|
-
const activePtr =
|
|
4688
|
-
const baseCenterRef =
|
|
5051
|
+
const baseRef = useRef5(null);
|
|
5052
|
+
const stickRef = useRef5(null);
|
|
5053
|
+
const activePtr = useRef5(null);
|
|
5054
|
+
const baseCenterRef = useRef5({ x: 0, y: 0 });
|
|
4689
5055
|
const radius = size / 2 - 16;
|
|
4690
5056
|
const applyStickPosition = (dx, dy) => {
|
|
4691
5057
|
if (!stickRef.current) return;
|
|
@@ -5164,10 +5530,10 @@ function ParallaxLayer({
|
|
|
5164
5530
|
}
|
|
5165
5531
|
|
|
5166
5532
|
// src/components/ScreenFlash.tsx
|
|
5167
|
-
import { forwardRef, useImperativeHandle, useRef as
|
|
5533
|
+
import { forwardRef, useImperativeHandle, useRef as useRef6 } from "react";
|
|
5168
5534
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
5169
5535
|
var ScreenFlash = forwardRef((_, ref) => {
|
|
5170
|
-
const divRef =
|
|
5536
|
+
const divRef = useRef6(null);
|
|
5171
5537
|
useImperativeHandle(ref, () => ({
|
|
5172
5538
|
flash(color, duration) {
|
|
5173
5539
|
const el = divRef.current;
|
|
@@ -5203,7 +5569,7 @@ var ScreenFlash = forwardRef((_, ref) => {
|
|
|
5203
5569
|
ScreenFlash.displayName = "ScreenFlash";
|
|
5204
5570
|
|
|
5205
5571
|
// src/components/CameraZone.tsx
|
|
5206
|
-
import { useEffect as useEffect21, useContext as useContext19, useRef as
|
|
5572
|
+
import { useEffect as useEffect21, useContext as useContext19, useRef as useRef7 } from "react";
|
|
5207
5573
|
import { Fragment as Fragment5, jsx as jsx11 } from "react/jsx-runtime";
|
|
5208
5574
|
function CameraZone({
|
|
5209
5575
|
x,
|
|
@@ -5216,8 +5582,8 @@ function CameraZone({
|
|
|
5216
5582
|
children
|
|
5217
5583
|
}) {
|
|
5218
5584
|
const engine = useContext19(EngineContext);
|
|
5219
|
-
const prevFollowRef =
|
|
5220
|
-
const activeRef =
|
|
5585
|
+
const prevFollowRef = useRef7(void 0);
|
|
5586
|
+
const activeRef = useRef7(false);
|
|
5221
5587
|
useEffect21(() => {
|
|
5222
5588
|
const eid = engine.ecs.createEntity();
|
|
5223
5589
|
engine.ecs.addComponent(eid, createScript(() => {
|
|
@@ -5458,7 +5824,7 @@ function useInputMap(bindings) {
|
|
|
5458
5824
|
}
|
|
5459
5825
|
|
|
5460
5826
|
// src/hooks/useEvents.ts
|
|
5461
|
-
import { useContext as useContext27, useEffect as useEffect26, useRef as
|
|
5827
|
+
import { useContext as useContext27, useEffect as useEffect26, useRef as useRef8 } from "react";
|
|
5462
5828
|
function useEvents() {
|
|
5463
5829
|
const engine = useContext27(EngineContext);
|
|
5464
5830
|
if (!engine) throw new Error("useEvents must be used inside <Game>");
|
|
@@ -5466,7 +5832,7 @@ function useEvents() {
|
|
|
5466
5832
|
}
|
|
5467
5833
|
function useEvent(event, handler) {
|
|
5468
5834
|
const events = useEvents();
|
|
5469
|
-
const handlerRef =
|
|
5835
|
+
const handlerRef = useRef8(handler);
|
|
5470
5836
|
handlerRef.current = handler;
|
|
5471
5837
|
useEffect26(() => {
|
|
5472
5838
|
return events.on(event, (data) => handlerRef.current(data));
|
|
@@ -5544,11 +5910,11 @@ function useInputRecorder() {
|
|
|
5544
5910
|
}
|
|
5545
5911
|
|
|
5546
5912
|
// src/hooks/useGamepad.ts
|
|
5547
|
-
import { useEffect as useEffect28, useRef as
|
|
5913
|
+
import { useEffect as useEffect28, useRef as useRef9, useState as useState7 } from "react";
|
|
5548
5914
|
var EMPTY_STATE = { connected: false, axes: [], buttons: [] };
|
|
5549
5915
|
function useGamepad(playerIndex = 0) {
|
|
5550
5916
|
const [state, setState] = useState7(EMPTY_STATE);
|
|
5551
|
-
const rafRef =
|
|
5917
|
+
const rafRef = useRef9(0);
|
|
5552
5918
|
useEffect28(() => {
|
|
5553
5919
|
const poll = () => {
|
|
5554
5920
|
const gp = navigator.getGamepads()[playerIndex];
|
|
@@ -5662,12 +6028,12 @@ function useDropThrough(frames = 8) {
|
|
|
5662
6028
|
}
|
|
5663
6029
|
|
|
5664
6030
|
// ../gameplay/src/hooks/useGameStateMachine.ts
|
|
5665
|
-
import { useState as useState10, useRef as
|
|
6031
|
+
import { useState as useState10, useRef as useRef10, useCallback as useCallback8, useEffect as useEffect29, useContext as useContext32 } from "react";
|
|
5666
6032
|
function useGameStateMachine(states, initial) {
|
|
5667
6033
|
const engine = useContext32(EngineContext);
|
|
5668
6034
|
const [state, setState] = useState10(initial);
|
|
5669
|
-
const stateRef =
|
|
5670
|
-
const statesRef =
|
|
6035
|
+
const stateRef = useRef10(initial);
|
|
6036
|
+
const statesRef = useRef10(states);
|
|
5671
6037
|
statesRef.current = states;
|
|
5672
6038
|
useEffect29(() => {
|
|
5673
6039
|
statesRef.current[initial]?.onEnter?.();
|
|
@@ -5693,22 +6059,22 @@ function useGameStateMachine(states, initial) {
|
|
|
5693
6059
|
}
|
|
5694
6060
|
|
|
5695
6061
|
// ../gameplay/src/hooks/useHealth.ts
|
|
5696
|
-
import { useRef as
|
|
6062
|
+
import { useRef as useRef11, useEffect as useEffect30, useContext as useContext33, useCallback as useCallback9 } from "react";
|
|
5697
6063
|
function useHealth(maxHp, opts = {}) {
|
|
5698
6064
|
const engine = useContext33(EngineContext);
|
|
5699
6065
|
const entityId = useContext33(EntityContext);
|
|
5700
|
-
const hpRef =
|
|
5701
|
-
const invincibleRef =
|
|
6066
|
+
const hpRef = useRef11(maxHp);
|
|
6067
|
+
const invincibleRef = useRef11(false);
|
|
5702
6068
|
const iFrameDuration = opts.iFrames ?? 1;
|
|
5703
|
-
const onDeathRef =
|
|
5704
|
-
const onDamageRef =
|
|
6069
|
+
const onDeathRef = useRef11(opts.onDeath);
|
|
6070
|
+
const onDamageRef = useRef11(opts.onDamage);
|
|
5705
6071
|
useEffect30(() => {
|
|
5706
6072
|
onDeathRef.current = opts.onDeath;
|
|
5707
6073
|
});
|
|
5708
6074
|
useEffect30(() => {
|
|
5709
6075
|
onDamageRef.current = opts.onDamage;
|
|
5710
6076
|
});
|
|
5711
|
-
const timerRef =
|
|
6077
|
+
const timerRef = useRef11(
|
|
5712
6078
|
createTimer(iFrameDuration, () => {
|
|
5713
6079
|
invincibleRef.current = false;
|
|
5714
6080
|
})
|
|
@@ -5723,7 +6089,7 @@ function useHealth(maxHp, opts = {}) {
|
|
|
5723
6089
|
}
|
|
5724
6090
|
if (hpRef.current <= 0) onDeathRef.current?.();
|
|
5725
6091
|
}, [iFrameDuration]);
|
|
5726
|
-
const takeDamageRef =
|
|
6092
|
+
const takeDamageRef = useRef11(takeDamage);
|
|
5727
6093
|
useEffect30(() => {
|
|
5728
6094
|
takeDamageRef.current = takeDamage;
|
|
5729
6095
|
}, [takeDamage]);
|
|
@@ -5810,11 +6176,11 @@ function useKinematicBody() {
|
|
|
5810
6176
|
}
|
|
5811
6177
|
|
|
5812
6178
|
// ../gameplay/src/hooks/useLevelTransition.ts
|
|
5813
|
-
import { useState as useState11, useRef as
|
|
6179
|
+
import { useState as useState11, useRef as useRef12, useCallback as useCallback11 } from "react";
|
|
5814
6180
|
function useLevelTransition(initial) {
|
|
5815
6181
|
const [currentLevel, setCurrentLevel] = useState11(initial);
|
|
5816
6182
|
const [isTransitioning, setIsTransitioning] = useState11(false);
|
|
5817
|
-
const overlayRef =
|
|
6183
|
+
const overlayRef = useRef12(null);
|
|
5818
6184
|
const transitionTo = useCallback11((level, opts = {}) => {
|
|
5819
6185
|
const { duration = 0.4, type = "fade" } = opts;
|
|
5820
6186
|
if (type === "instant") {
|
|
@@ -5993,10 +6359,10 @@ function useRestart() {
|
|
|
5993
6359
|
}
|
|
5994
6360
|
|
|
5995
6361
|
// ../gameplay/src/hooks/useSave.ts
|
|
5996
|
-
import { useCallback as useCallback15, useRef as
|
|
6362
|
+
import { useCallback as useCallback15, useRef as useRef13 } from "react";
|
|
5997
6363
|
function useSave(key, defaultValue, opts = {}) {
|
|
5998
6364
|
const version = opts.version ?? 1;
|
|
5999
|
-
const dataRef =
|
|
6365
|
+
const dataRef = useRef13(defaultValue);
|
|
6000
6366
|
const save = useCallback15((value) => {
|
|
6001
6367
|
dataRef.current = value;
|
|
6002
6368
|
const slot = { version, data: value };
|
|
@@ -6071,11 +6437,11 @@ function useTopDownMovement(entityId, opts = {}) {
|
|
|
6071
6437
|
}
|
|
6072
6438
|
|
|
6073
6439
|
// ../gameplay/src/hooks/useDialogue.ts
|
|
6074
|
-
import { useState as useState14, useCallback as useCallback16, useRef as
|
|
6440
|
+
import { useState as useState14, useCallback as useCallback16, useRef as useRef14 } from "react";
|
|
6075
6441
|
function useDialogue() {
|
|
6076
6442
|
const [active, setActive] = useState14(false);
|
|
6077
6443
|
const [currentId, setCurrentId] = useState14(null);
|
|
6078
|
-
const scriptRef =
|
|
6444
|
+
const scriptRef = useRef14(null);
|
|
6079
6445
|
const start = useCallback16((script, startId) => {
|
|
6080
6446
|
scriptRef.current = script;
|
|
6081
6447
|
const id = startId ?? Object.keys(script)[0];
|
|
@@ -6119,16 +6485,16 @@ function useDialogue() {
|
|
|
6119
6485
|
}
|
|
6120
6486
|
|
|
6121
6487
|
// ../gameplay/src/hooks/useCutscene.ts
|
|
6122
|
-
import { useState as useState15, useCallback as useCallback17, useRef as
|
|
6488
|
+
import { useState as useState15, useCallback as useCallback17, useRef as useRef15, useEffect as useEffect33, useContext as useContext38 } from "react";
|
|
6123
6489
|
function useCutscene() {
|
|
6124
6490
|
const engine = useContext38(EngineContext);
|
|
6125
6491
|
const [playing, setPlaying] = useState15(false);
|
|
6126
6492
|
const [stepIndex, setStepIndex] = useState15(0);
|
|
6127
|
-
const stepsRef =
|
|
6128
|
-
const timerRef =
|
|
6129
|
-
const idxRef =
|
|
6130
|
-
const playingRef =
|
|
6131
|
-
const entityRef =
|
|
6493
|
+
const stepsRef = useRef15([]);
|
|
6494
|
+
const timerRef = useRef15(0);
|
|
6495
|
+
const idxRef = useRef15(0);
|
|
6496
|
+
const playingRef = useRef15(false);
|
|
6497
|
+
const entityRef = useRef15(null);
|
|
6132
6498
|
const finish = useCallback17(() => {
|
|
6133
6499
|
playingRef.current = false;
|
|
6134
6500
|
setPlaying(false);
|
|
@@ -6253,7 +6619,7 @@ function useGameStore(key, initialState) {
|
|
|
6253
6619
|
}
|
|
6254
6620
|
|
|
6255
6621
|
// ../../packages/audio/src/useSound.ts
|
|
6256
|
-
import { useEffect as useEffect34, useRef as
|
|
6622
|
+
import { useEffect as useEffect34, useRef as useRef16 } from "react";
|
|
6257
6623
|
var _audioCtx = null;
|
|
6258
6624
|
function getAudioCtx() {
|
|
6259
6625
|
if (!_audioCtx) _audioCtx = new AudioContext();
|
|
@@ -6320,12 +6686,12 @@ async function loadBuffer(src) {
|
|
|
6320
6686
|
return buf;
|
|
6321
6687
|
}
|
|
6322
6688
|
function useSound(src, opts = {}) {
|
|
6323
|
-
const bufferRef =
|
|
6324
|
-
const sourceRef =
|
|
6325
|
-
const gainRef =
|
|
6326
|
-
const volRef =
|
|
6327
|
-
const loopRef =
|
|
6328
|
-
const groupRef =
|
|
6689
|
+
const bufferRef = useRef16(null);
|
|
6690
|
+
const sourceRef = useRef16(null);
|
|
6691
|
+
const gainRef = useRef16(null);
|
|
6692
|
+
const volRef = useRef16(opts.volume ?? 1);
|
|
6693
|
+
const loopRef = useRef16(opts.loop ?? false);
|
|
6694
|
+
const groupRef = useRef16(opts.group);
|
|
6329
6695
|
useEffect34(() => {
|
|
6330
6696
|
let cancelled = false;
|
|
6331
6697
|
loadBuffer(src).then((buf) => {
|