cubeforge 0.4.11 → 0.4.13
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/index.d.ts +8 -1
- package/dist/index.js +115 -30
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -531,6 +531,13 @@ interface ParticleEmitterProps {
|
|
|
531
531
|
* - `'screen'` — lightens, softer than additive
|
|
532
532
|
*/
|
|
533
533
|
blendMode?: 'normal' | 'additive' | 'multiply' | 'screen';
|
|
534
|
+
/**
|
|
535
|
+
* Visual shape of each particle.
|
|
536
|
+
* - `'soft'` (default) — radial gradient with glow halo; pairs well with `blendMode="additive"`
|
|
537
|
+
* - `'circle'` — hard-edged anti-aliased circle, no glow falloff
|
|
538
|
+
* - `'square'` — solid quad (fastest, no texture lookup)
|
|
539
|
+
*/
|
|
540
|
+
particleShape?: 'soft' | 'circle' | 'square';
|
|
534
541
|
/**
|
|
535
542
|
* Formation mode: particles lerp toward fixed target positions instead of
|
|
536
543
|
* being emitted with velocity. Enables logo reveals, constellations, shape morphing.
|
|
@@ -559,7 +566,7 @@ interface ParticleEmitterProps {
|
|
|
559
566
|
/** Duration of the global color transition in seconds. Default 0.5. */
|
|
560
567
|
colorTransitionDuration?: number;
|
|
561
568
|
}
|
|
562
|
-
declare function ParticleEmitter({ active, preset, rate, speed, spread, angle, particleLife, particleSize, color, gravity, maxParticles, burstCount, emitShape, emitRadius, emitWidth, emitHeight, textureSrc, enableRotation, rotationSpeedRange, sizeOverLife, attractors, colorOverLife, blendMode, mode, formationPoints, seekStrength, targetColor, colorTransitionDuration, }: ParticleEmitterProps): null;
|
|
569
|
+
declare function ParticleEmitter({ active, preset, rate, speed, spread, angle, particleLife, particleSize, color, gravity, maxParticles, burstCount, emitShape, emitRadius, emitWidth, emitHeight, textureSrc, enableRotation, rotationSpeedRange, sizeOverLife, attractors, colorOverLife, blendMode, mode, formationPoints, seekStrength, targetColor, colorTransitionDuration, particleShape, }: ParticleEmitterProps): null;
|
|
563
570
|
|
|
564
571
|
interface VirtualJoystickProps {
|
|
565
572
|
/** Diameter of the joystick base in pixels (default 120) */
|
package/dist/index.js
CHANGED
|
@@ -2793,6 +2793,8 @@ var RenderSystem = class {
|
|
|
2793
2793
|
this.uTexture = gl.getUniformLocation(this.program, "u_texture");
|
|
2794
2794
|
this.uUseTexture = gl.getUniformLocation(this.program, "u_useTexture");
|
|
2795
2795
|
this.whiteTexture = createWhiteTexture(gl);
|
|
2796
|
+
this.particleTextureSoft = this._createParticleTexture("soft");
|
|
2797
|
+
this.particleTextureCircle = this._createParticleTexture("circle");
|
|
2796
2798
|
this.parallaxProgram = createProgram(gl, PARALLAX_VERT_SRC, PARALLAX_FRAG_SRC);
|
|
2797
2799
|
const fsVerts = new Float32Array([-1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1]);
|
|
2798
2800
|
this.parallaxVAO = gl.createVertexArray();
|
|
@@ -2819,6 +2821,8 @@ var RenderSystem = class {
|
|
|
2819
2821
|
instanceBuffer;
|
|
2820
2822
|
instanceData;
|
|
2821
2823
|
whiteTexture;
|
|
2824
|
+
particleTextureSoft;
|
|
2825
|
+
particleTextureCircle;
|
|
2822
2826
|
textures = /* @__PURE__ */ new Map();
|
|
2823
2827
|
/** Tracks texture access order for LRU eviction (most recent at end). */
|
|
2824
2828
|
textureLRU = [];
|
|
@@ -3041,6 +3045,38 @@ var RenderSystem = class {
|
|
|
3041
3045
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
3042
3046
|
gl.activeTexture(gl.TEXTURE0);
|
|
3043
3047
|
}
|
|
3048
|
+
/** Pre-baked particle shape textures: white alpha mask, tinted by instance color at render time. */
|
|
3049
|
+
_createParticleTexture(shape) {
|
|
3050
|
+
const { gl } = this;
|
|
3051
|
+
const SIZE = 64;
|
|
3052
|
+
const canvas = document.createElement("canvas");
|
|
3053
|
+
canvas.width = SIZE;
|
|
3054
|
+
canvas.height = SIZE;
|
|
3055
|
+
const ctx = canvas.getContext("2d");
|
|
3056
|
+
const cx = SIZE / 2;
|
|
3057
|
+
if (shape === "soft") {
|
|
3058
|
+
const g = ctx.createRadialGradient(cx, cx, 0, cx, cx, cx);
|
|
3059
|
+
g.addColorStop(0, "rgba(255,255,255,1)");
|
|
3060
|
+
g.addColorStop(0.25, "rgba(255,255,255,0.9)");
|
|
3061
|
+
g.addColorStop(0.5, "rgba(255,255,255,0.2)");
|
|
3062
|
+
g.addColorStop(1, "rgba(255,255,255,0)");
|
|
3063
|
+
ctx.fillStyle = g;
|
|
3064
|
+
ctx.fillRect(0, 0, SIZE, SIZE);
|
|
3065
|
+
} else {
|
|
3066
|
+
ctx.fillStyle = "white";
|
|
3067
|
+
ctx.beginPath();
|
|
3068
|
+
ctx.arc(cx, cx, cx - 1, 0, Math.PI * 2);
|
|
3069
|
+
ctx.fill();
|
|
3070
|
+
}
|
|
3071
|
+
const tex = gl.createTexture();
|
|
3072
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
3073
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
|
3074
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
3075
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
3076
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
3077
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
3078
|
+
return tex;
|
|
3079
|
+
}
|
|
3044
3080
|
// ── Texture management (sprite textures — CLAMP_TO_EDGE) ──────────────────
|
|
3045
3081
|
loadTexture(src) {
|
|
3046
3082
|
const cached = this.textures.get(src);
|
|
@@ -3283,6 +3319,8 @@ var RenderSystem = class {
|
|
|
3283
3319
|
const { gl, canvas } = this;
|
|
3284
3320
|
const W = canvas.width;
|
|
3285
3321
|
const H = canvas.height;
|
|
3322
|
+
const Wl = canvas.clientWidth || W;
|
|
3323
|
+
const Hl = canvas.clientHeight || H;
|
|
3286
3324
|
let camX = 0, camY = 0, zoom = 1;
|
|
3287
3325
|
let background = this.defaultBackground;
|
|
3288
3326
|
let shakeX = 0, shakeY = 0;
|
|
@@ -3472,7 +3510,7 @@ var RenderSystem = class {
|
|
|
3472
3510
|
gl.useProgram(this.program);
|
|
3473
3511
|
gl.uniform2f(this.uCamPos, camX, camY);
|
|
3474
3512
|
gl.uniform1f(this.uZoom, zoom);
|
|
3475
|
-
gl.uniform2f(this.uCanvasSize,
|
|
3513
|
+
gl.uniform2f(this.uCanvasSize, Wl, Hl);
|
|
3476
3514
|
gl.uniform2f(this.uShake, shakeX, shakeY);
|
|
3477
3515
|
gl.uniform1i(this.uTexture, 0);
|
|
3478
3516
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -3660,9 +3698,9 @@ var RenderSystem = class {
|
|
|
3660
3698
|
const ease = ct * ct * (3 - 2 * ct);
|
|
3661
3699
|
const [cr0, cg0, cb0] = parseCSSColor(pool._colorTransitionFrom);
|
|
3662
3700
|
const [cr1, cg1, cb1] = parseCSSColor(pool.targetColor);
|
|
3663
|
-
const ri = Math.round(cr0 + (cr1 - cr0) * ease);
|
|
3664
|
-
const gi = Math.round(cg0 + (cg1 - cg0) * ease);
|
|
3665
|
-
const bi = Math.round(cb0 + (cb1 - cb0) * ease);
|
|
3701
|
+
const ri = Math.round((cr0 + (cr1 - cr0) * ease) * 255);
|
|
3702
|
+
const gi = Math.round((cg0 + (cg1 - cg0) * ease) * 255);
|
|
3703
|
+
const bi = Math.round((cb0 + (cb1 - cb0) * ease) * 255);
|
|
3666
3704
|
pool.color = `rgb(${ri},${gi},${bi})`;
|
|
3667
3705
|
if (ct >= 1) {
|
|
3668
3706
|
pool._colorTransitionFrom = void 0;
|
|
@@ -3672,27 +3710,23 @@ var RenderSystem = class {
|
|
|
3672
3710
|
const isFormation = pool.mode === "formation";
|
|
3673
3711
|
pool.particles = pool.particles.filter((p) => {
|
|
3674
3712
|
if (isFormation) {
|
|
3713
|
+
if (p.targetX !== void 0 && p.targetY !== void 0) {
|
|
3714
|
+
const seek2 = pool.seekStrength ?? 0.055;
|
|
3715
|
+
p.x += (p.targetX - p.x) * seek2;
|
|
3716
|
+
p.y += (p.targetY - p.y) * seek2;
|
|
3717
|
+
}
|
|
3675
3718
|
if (pool.attractors) {
|
|
3676
3719
|
for (const attr of pool.attractors) {
|
|
3677
|
-
const adx =
|
|
3678
|
-
const ady =
|
|
3720
|
+
const adx = p.x - attr.x;
|
|
3721
|
+
const ady = p.y - attr.y;
|
|
3679
3722
|
const dist = Math.sqrt(adx * adx + ady * ady);
|
|
3680
3723
|
if (dist < attr.radius && dist > 0) {
|
|
3681
|
-
const
|
|
3682
|
-
p.
|
|
3683
|
-
p.
|
|
3724
|
+
const magnitude = -attr.strength * (1 - dist / attr.radius) * dt;
|
|
3725
|
+
p.x += adx / dist * magnitude;
|
|
3726
|
+
p.y += ady / dist * magnitude;
|
|
3684
3727
|
}
|
|
3685
3728
|
}
|
|
3686
3729
|
}
|
|
3687
|
-
p.vx *= 0.82;
|
|
3688
|
-
p.vy *= 0.82;
|
|
3689
|
-
p.x += p.vx * dt;
|
|
3690
|
-
p.y += p.vy * dt;
|
|
3691
|
-
if (p.targetX !== void 0 && p.targetY !== void 0) {
|
|
3692
|
-
const seek2 = pool.seekStrength ?? 0.055;
|
|
3693
|
-
p.x += (p.targetX - p.x) * seek2;
|
|
3694
|
-
p.y += (p.targetY - p.y) * seek2;
|
|
3695
|
-
}
|
|
3696
3730
|
return true;
|
|
3697
3731
|
}
|
|
3698
3732
|
p.life -= dt;
|
|
@@ -3787,11 +3821,22 @@ var RenderSystem = class {
|
|
|
3787
3821
|
}
|
|
3788
3822
|
}
|
|
3789
3823
|
let pCount = 0;
|
|
3790
|
-
const
|
|
3824
|
+
const pShape = pool.particleShape ?? "soft";
|
|
3791
3825
|
const pBlend = pool.blendMode ?? "normal";
|
|
3826
|
+
const flushParticles = (n) => {
|
|
3827
|
+
if (n === 0) return;
|
|
3828
|
+
if (pBlend !== "normal") this.applyBlendMode(pBlend);
|
|
3829
|
+
if (pShape === "square") {
|
|
3830
|
+
this.flush(n, "__color__");
|
|
3831
|
+
} else {
|
|
3832
|
+
const tex = pShape === "circle" ? this.particleTextureCircle : this.particleTextureSoft;
|
|
3833
|
+
this.flushWithTex(n, tex, true);
|
|
3834
|
+
}
|
|
3835
|
+
if (pBlend !== "normal") this.applyBlendMode("normal");
|
|
3836
|
+
};
|
|
3792
3837
|
for (const p of pool.particles) {
|
|
3793
3838
|
if (pCount >= MAX_INSTANCES) {
|
|
3794
|
-
|
|
3839
|
+
flushParticles(pCount);
|
|
3795
3840
|
pCount = 0;
|
|
3796
3841
|
}
|
|
3797
3842
|
const lifeFrac = p.life / p.maxLife;
|
|
@@ -3838,7 +3883,7 @@ var RenderSystem = class {
|
|
|
3838
3883
|
);
|
|
3839
3884
|
pCount++;
|
|
3840
3885
|
}
|
|
3841
|
-
if (pCount > 0)
|
|
3886
|
+
if (pCount > 0) flushParticles(pCount);
|
|
3842
3887
|
}
|
|
3843
3888
|
for (const id of world.query("Transform", "Trail")) {
|
|
3844
3889
|
const t = world.getComponent(id, "Transform");
|
|
@@ -15637,7 +15682,8 @@ function ParticleEmitter({
|
|
|
15637
15682
|
formationPoints,
|
|
15638
15683
|
seekStrength,
|
|
15639
15684
|
targetColor,
|
|
15640
|
-
colorTransitionDuration
|
|
15685
|
+
colorTransitionDuration,
|
|
15686
|
+
particleShape
|
|
15641
15687
|
}) {
|
|
15642
15688
|
const presetConfig = preset ? PARTICLE_PRESETS[preset] : {};
|
|
15643
15689
|
const resolvedRate = rate ?? presetConfig.rate ?? 20;
|
|
@@ -15681,7 +15727,8 @@ function ParticleEmitter({
|
|
|
15681
15727
|
mode,
|
|
15682
15728
|
formationPoints,
|
|
15683
15729
|
seekStrength,
|
|
15684
|
-
colorTransitionDuration
|
|
15730
|
+
colorTransitionDuration,
|
|
15731
|
+
particleShape
|
|
15685
15732
|
});
|
|
15686
15733
|
return () => engine.ecs.removeComponent(entityId, "ParticlePool");
|
|
15687
15734
|
}, []);
|
|
@@ -15690,6 +15737,12 @@ function ParticleEmitter({
|
|
|
15690
15737
|
if (!pool) return;
|
|
15691
15738
|
pool.active = active;
|
|
15692
15739
|
}, [active, engine, entityId]);
|
|
15740
|
+
useEffect28(() => {
|
|
15741
|
+
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
15742
|
+
if (!pool) return;
|
|
15743
|
+
pool.blendMode = blendMode;
|
|
15744
|
+
pool.particleShape = particleShape;
|
|
15745
|
+
}, [blendMode, particleShape, engine, entityId]);
|
|
15693
15746
|
useEffect28(() => {
|
|
15694
15747
|
const pool = engine.ecs.getComponent(entityId, "ParticlePool");
|
|
15695
15748
|
if (!pool) return;
|
|
@@ -18174,6 +18227,9 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
18174
18227
|
coyoteTime = 0.08,
|
|
18175
18228
|
jumpBuffer = 0.08,
|
|
18176
18229
|
jumpCooldown = 0.18,
|
|
18230
|
+
acceleration = Infinity,
|
|
18231
|
+
groundFriction = 0.6,
|
|
18232
|
+
airFriction = 0.92,
|
|
18177
18233
|
gamepadIndex = 0,
|
|
18178
18234
|
gamepadDeadZone = 0.2,
|
|
18179
18235
|
bindings
|
|
@@ -18211,9 +18267,18 @@ function usePlatformerController(entityId, opts = {}) {
|
|
|
18211
18267
|
else if (!jumpKeys.some((k) => input.isDown(k)) && !gpJump) state.jumpBuffer = Math.max(0, state.jumpBuffer - dt);
|
|
18212
18268
|
const left = leftKeys.some((k) => input.isDown(k)) || gpLeft;
|
|
18213
18269
|
const right = rightKeys.some((k) => input.isDown(k)) || gpRight;
|
|
18214
|
-
if (left
|
|
18215
|
-
|
|
18216
|
-
|
|
18270
|
+
if (left || right) {
|
|
18271
|
+
const targetVx = left ? -speed : speed;
|
|
18272
|
+
if (acceleration === Infinity) {
|
|
18273
|
+
rb.vx = targetVx;
|
|
18274
|
+
} else {
|
|
18275
|
+
const diff = targetVx - rb.vx;
|
|
18276
|
+
const maxDelta = acceleration * dt;
|
|
18277
|
+
rb.vx += Math.abs(diff) <= maxDelta ? diff : Math.sign(diff) * maxDelta;
|
|
18278
|
+
}
|
|
18279
|
+
} else {
|
|
18280
|
+
rb.vx *= rb.onGround ? groundFriction : airFriction;
|
|
18281
|
+
}
|
|
18217
18282
|
const sprite = world.getComponent(id, "Sprite");
|
|
18218
18283
|
if (sprite) {
|
|
18219
18284
|
if (left) sprite.flipX = true;
|
|
@@ -18455,11 +18520,16 @@ function useIDBSave(key, defaultValue, opts = {}) {
|
|
|
18455
18520
|
|
|
18456
18521
|
// ../gameplay/src/hooks/useTopDownMovement.ts
|
|
18457
18522
|
import { useContext as useContext60, useEffect as useEffect65 } from "react";
|
|
18523
|
+
function moveToward(current, target, maxDelta) {
|
|
18524
|
+
const diff = target - current;
|
|
18525
|
+
if (Math.abs(diff) <= maxDelta) return target;
|
|
18526
|
+
return current + Math.sign(diff) * maxDelta;
|
|
18527
|
+
}
|
|
18458
18528
|
function useTopDownMovement(entityId, opts = {}) {
|
|
18459
18529
|
const engine = useContext60(EngineContext);
|
|
18460
|
-
const { speed = 200, normalizeDiagonal = true } = opts;
|
|
18530
|
+
const { speed = 200, normalizeDiagonal = true, acceleration = Infinity, deceleration = acceleration } = opts;
|
|
18461
18531
|
useEffect65(() => {
|
|
18462
|
-
const updateFn = (id, world, input) => {
|
|
18532
|
+
const updateFn = (id, world, input, dt) => {
|
|
18463
18533
|
if (!world.hasEntity(id)) return;
|
|
18464
18534
|
const rb = world.getComponent(id, "RigidBody");
|
|
18465
18535
|
if (!rb) return;
|
|
@@ -18474,8 +18544,23 @@ function useTopDownMovement(entityId, opts = {}) {
|
|
|
18474
18544
|
dx /= len2;
|
|
18475
18545
|
dy /= len2;
|
|
18476
18546
|
}
|
|
18477
|
-
|
|
18478
|
-
|
|
18547
|
+
const targetVx = dx * speed;
|
|
18548
|
+
const targetVy = dy * speed;
|
|
18549
|
+
if (acceleration === Infinity && deceleration === Infinity) {
|
|
18550
|
+
rb.vx = targetVx;
|
|
18551
|
+
rb.vy = targetVy;
|
|
18552
|
+
} else {
|
|
18553
|
+
const hasInput = dx !== 0 || dy !== 0;
|
|
18554
|
+
const rate = hasInput ? acceleration : deceleration;
|
|
18555
|
+
if (rate === Infinity) {
|
|
18556
|
+
rb.vx = targetVx;
|
|
18557
|
+
rb.vy = targetVy;
|
|
18558
|
+
} else {
|
|
18559
|
+
const maxDelta = rate * dt;
|
|
18560
|
+
rb.vx = moveToward(rb.vx, targetVx, maxDelta);
|
|
18561
|
+
rb.vy = moveToward(rb.vy, targetVy, maxDelta);
|
|
18562
|
+
}
|
|
18563
|
+
}
|
|
18479
18564
|
};
|
|
18480
18565
|
engine.ecs.addComponent(entityId, createScript(updateFn));
|
|
18481
18566
|
return () => engine.ecs.removeComponent(entityId, "Script");
|