incanto 0.4.3 → 0.6.0
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/2d.d.ts +80 -6
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +200 -15
- package/dist/3d.js +4 -4
- package/dist/{audio-player-DqUR3XFs.d.ts → audio-player-DkBqRTs4.d.ts} +1 -1
- package/dist/{behavior-BAQq7HGM.d.ts → behavior-CWhW3oa6.d.ts} +54 -0
- package/dist/{create-game-CZHROKcT.js → create-game-B_sW_eiE.js} +59 -27
- package/dist/{create-game-DiTq3-fQ.js → create-game-DkbZCNaV.js} +292 -14
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-DP2WPYom.js → duplicate-BEvGBtb_.js} +1 -1
- package/dist/{gameplay-Ccruc3Wd.js → gameplay-CDFgSG6z.js} +293 -24
- package/dist/gameplay.d.ts +113 -2
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +139 -5
- package/dist/index.js +59 -6
- package/dist/{loader-CGs_G-r0.js → loader-B4OEXDZ8.js} +60 -0
- package/dist/{loader-Mo0KghCv.d.ts → loader-D2u0fVW2.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +1 -1
- package/dist/{particle-sim-DYuSUxvK.js → particle-sim-CFkILGwh.js} +84 -3
- package/dist/{particle-sim-CbN4YUuH.d.ts → particle-sim-CwJ5rI_P.d.ts} +3 -0
- package/dist/{physics-2d-KuMWPTf6.js → physics-2d-Cgli1aju.js} +95 -10
- package/dist/{physics-3d-DmNCeh58.js → physics-3d-CBAQ12LY.js} +121 -43
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-DPEV9_9t.js → register-BJCfuuZ2.js} +79 -5
- package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
- package/dist/{register-BRy8FNox.js → register-Dd7Juujf.js} +389 -49
- package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
- package/dist/test.d.ts +2 -2
- package/dist/test.js +10 -10
- package/editor/assets/{agent8-DryTXVd4.js → agent8-MciFwn0v.js} +1 -1
- package/editor/assets/index-mofVSmuA.js +7417 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +980 -126
- package/skills/incanto-3d-models.md +24 -1
- package/skills/incanto-building-2d-games.md +6 -0
- package/skills/incanto-building-3d-games.md +46 -1
- package/skills/incanto-gameplay-behaviors.md +60 -0
- package/skills/incanto-hud.md +58 -0
- package/skills/incanto-node-reference.md +148 -0
- package/skills/incanto-physics-and-input.md +47 -0
- package/editor/assets/index-N8se-PhP.js +0 -7365
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { _ as registerBehavior, m as Behavior } from "./loader-
|
|
1
|
+
import { _ as registerBehavior, m as Behavior, n as loadScene } from "./loader-B4OEXDZ8.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
|
-
import { t as
|
|
3
|
+
import { t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
4
|
+
import { t as duplicateNode } from "./duplicate-BEvGBtb_.js";
|
|
4
5
|
//#region src/gameplay/spatial.ts
|
|
5
6
|
/** Duck-type test: every spatial node (Node2D/Node3D) exposes `position: number[]`. */
|
|
6
7
|
function hasPosition$1(node) {
|
|
@@ -36,13 +37,6 @@ function add(a, b) {
|
|
|
36
37
|
for (let i = 0; i < n; i++) out[i] = (a[i] ?? 0) + (b[i] ?? 0);
|
|
37
38
|
return out;
|
|
38
39
|
}
|
|
39
|
-
/** Component-wise `a - b`. */
|
|
40
|
-
function sub(a, b) {
|
|
41
|
-
const n = Math.max(a.length, b.length);
|
|
42
|
-
const out = new Array(n);
|
|
43
|
-
for (let i = 0; i < n; i++) out[i] = (a[i] ?? 0) - (b[i] ?? 0);
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
40
|
/** Vector length. */
|
|
47
41
|
function length(v) {
|
|
48
42
|
let sum = 0;
|
|
@@ -60,15 +54,28 @@ function normalize(v) {
|
|
|
60
54
|
* whether the destination was reached this step (clamped — no overshoot).
|
|
61
55
|
*/
|
|
62
56
|
function moveToward(from, to, maxStep) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
57
|
+
const n = Math.max(from.length, to.length);
|
|
58
|
+
let sum = 0;
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
const d = (to[i] ?? 0) - (from[i] ?? 0);
|
|
61
|
+
sum += d * d;
|
|
62
|
+
}
|
|
63
|
+
const dist = Math.sqrt(sum);
|
|
64
|
+
const position = new Array(n);
|
|
65
|
+
if (dist <= maxStep || dist === 0) {
|
|
66
|
+
for (let i = 0; i < n; i++) position[i] = to[i] ?? 0;
|
|
67
|
+
return {
|
|
68
|
+
position,
|
|
69
|
+
reached: true
|
|
70
|
+
};
|
|
71
|
+
}
|
|
69
72
|
const f = maxStep / dist;
|
|
73
|
+
for (let i = 0; i < n; i++) {
|
|
74
|
+
const c = from[i] ?? 0;
|
|
75
|
+
position[i] = c + ((to[i] ?? 0) - c) * f;
|
|
76
|
+
}
|
|
70
77
|
return {
|
|
71
|
-
position
|
|
78
|
+
position,
|
|
72
79
|
reached: false
|
|
73
80
|
};
|
|
74
81
|
}
|
|
@@ -427,19 +434,128 @@ var FollowCamera = class extends Behavior {
|
|
|
427
434
|
if (this.deadzone < 0) throw new IncantoError("PROP_TYPE_MISMATCH", `FollowCamera on '${this.node.getPath()}': "deadzone" must be >= 0, got ${this.deadzone}.`, { prop: "deadzone" });
|
|
428
435
|
requirePosition(this);
|
|
429
436
|
}
|
|
437
|
+
shakeMag = 0;
|
|
438
|
+
shakeT = 0;
|
|
439
|
+
shakeFalloff = .28;
|
|
440
|
+
/** Kick the camera (impacts, explosions). Composes with the follow. */
|
|
441
|
+
shake(magnitude, seconds = .28) {
|
|
442
|
+
this.shakeMag = Math.max(this.shakeMag, magnitude);
|
|
443
|
+
this.shakeT = seconds;
|
|
444
|
+
this.shakeFalloff = seconds;
|
|
445
|
+
}
|
|
430
446
|
update(dt) {
|
|
431
447
|
const cam = this.node;
|
|
432
448
|
if (!hasPosition$1(cam)) return;
|
|
433
449
|
const target = cam.getNodeOrNull(this.target);
|
|
434
450
|
if (!target || !hasPosition$1(target)) return;
|
|
435
451
|
const desired = add(target.position, this.offset);
|
|
436
|
-
if (this.deadzone > 0 && distance$1(cam.position, desired) <= this.deadzone)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
cam.position = [
|
|
440
|
-
|
|
452
|
+
if (!(this.deadzone > 0 && distance$1(cam.position, desired) <= this.deadzone)) {
|
|
453
|
+
const f = smoothingFactor(this.smoothing, dt);
|
|
454
|
+
if (f >= 1) cam.position = [...desired];
|
|
455
|
+
else cam.position = cam.position.map((c, i) => c + ((desired[i] ?? c) - c) * f);
|
|
456
|
+
}
|
|
457
|
+
if (this.shakeT > 0) {
|
|
458
|
+
this.shakeT -= dt;
|
|
459
|
+
const k = this.shakeMag * Math.max(0, this.shakeT) / this.shakeFalloff;
|
|
460
|
+
if (this.shakeT <= 0) this.shakeMag = 0;
|
|
461
|
+
const jolted = cam.position.slice();
|
|
462
|
+
for (let i = 0; i < jolted.length; i++) {
|
|
463
|
+
const scale = jolted.length >= 3 && i === 1 ? .5 : 1;
|
|
464
|
+
jolted[i] = (jolted[i] ?? 0) + (this.engine.rng.next() * 2 - 1) * k * scale;
|
|
465
|
+
}
|
|
466
|
+
cam.position = jolted;
|
|
441
467
|
}
|
|
442
|
-
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/gameplay/game-flow.ts
|
|
472
|
+
/**
|
|
473
|
+
* Reload the CURRENT scene from its source JSON — fresh nodes, reset physics,
|
|
474
|
+
* rewired input. The restart primitive every game-over screen wants.
|
|
475
|
+
*/
|
|
476
|
+
function restartScene(engine) {
|
|
477
|
+
const source = engine.scene?.source;
|
|
478
|
+
if (!source) return;
|
|
479
|
+
engine.timeScale = 1;
|
|
480
|
+
engine.setScene(loadScene(jsonClone(source)));
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* The win/lose/restart state machine 6+ examples hand-rolled as `over`/`win`
|
|
484
|
+
* booleans. Attach to any node (the scene root is natural):
|
|
485
|
+
*
|
|
486
|
+
* const flow = root.behavior as GameFlow; // script: { "name": "GameFlow" }
|
|
487
|
+
* flow.gameOver('YOU DIED'); // freezes time, sticky banner
|
|
488
|
+
* flow.win('AREA CLEAR');
|
|
489
|
+
* flow.pause(); flow.resume();
|
|
490
|
+
*
|
|
491
|
+
* While in `gameover`/`won`, pressing `restartAction` (default action name
|
|
492
|
+
* 'restart' — declare it in the scene input map, or leave undeclared and call
|
|
493
|
+
* `flow.restart()` yourself) reloads the scene from source. Banners render
|
|
494
|
+
* through a `%Banner` UiBanner when one exists; otherwise states are silent
|
|
495
|
+
* (drive your own UI off the `flowChanged` signal).
|
|
496
|
+
*/
|
|
497
|
+
var GameFlow = class extends Behavior {
|
|
498
|
+
static signals = ["flowChanged"];
|
|
499
|
+
static props = {
|
|
500
|
+
restartAction: { default: "restart" },
|
|
501
|
+
freezeOnEnd: { default: true },
|
|
502
|
+
bannerPath: { default: "%Banner" }
|
|
503
|
+
};
|
|
504
|
+
/** Input action that restarts from gameover/won (declare it in `input{}`). */
|
|
505
|
+
restartAction = "restart";
|
|
506
|
+
/** Freeze `engine.timeScale` on gameover/won (banner UI keeps rendering). */
|
|
507
|
+
freezeOnEnd = true;
|
|
508
|
+
/** Where the flow looks for a UiBanner ('' = never). */
|
|
509
|
+
bannerPath = "%Banner";
|
|
510
|
+
state = "playing";
|
|
511
|
+
frozeScale = false;
|
|
512
|
+
gameOver(text = "GAME OVER", color = "#ef4444") {
|
|
513
|
+
this.transition("gameover", text, color);
|
|
514
|
+
}
|
|
515
|
+
win(text = "YOU WIN", color = "#4ade80") {
|
|
516
|
+
this.transition("won", text, color);
|
|
517
|
+
}
|
|
518
|
+
pause() {
|
|
519
|
+
if (this.state !== "playing") return;
|
|
520
|
+
this.state = "paused";
|
|
521
|
+
this.freeze();
|
|
522
|
+
this.node.emit("flowChanged", this.state);
|
|
523
|
+
}
|
|
524
|
+
resume() {
|
|
525
|
+
if (this.state !== "paused") return;
|
|
526
|
+
this.state = "playing";
|
|
527
|
+
this.thaw();
|
|
528
|
+
this.node.emit("flowChanged", this.state);
|
|
529
|
+
}
|
|
530
|
+
restart() {
|
|
531
|
+
this.thaw();
|
|
532
|
+
restartScene(this.engine);
|
|
533
|
+
}
|
|
534
|
+
transition(state, text, color) {
|
|
535
|
+
if (this.state === "gameover" || this.state === "won") return;
|
|
536
|
+
this.state = state;
|
|
537
|
+
if (this.freezeOnEnd) this.freeze();
|
|
538
|
+
if (this.bannerPath !== "") this.node.getNodeOrNull(this.bannerPath)?.show(text, {
|
|
539
|
+
color,
|
|
540
|
+
seconds: 0
|
|
541
|
+
});
|
|
542
|
+
this.node.emit("flowChanged", state);
|
|
543
|
+
}
|
|
544
|
+
freeze() {
|
|
545
|
+
if (this.frozeScale) return;
|
|
546
|
+
this.frozeScale = true;
|
|
547
|
+
this.engine.timeScale = 0;
|
|
548
|
+
}
|
|
549
|
+
thaw() {
|
|
550
|
+
if (!this.frozeScale) return;
|
|
551
|
+
this.frozeScale = false;
|
|
552
|
+
this.engine.timeScale = 1;
|
|
553
|
+
}
|
|
554
|
+
update() {
|
|
555
|
+
if (this.state !== "gameover" && this.state !== "won") return;
|
|
556
|
+
try {
|
|
557
|
+
if (this.engine.input.justPressed(this.restartAction)) this.restart();
|
|
558
|
+
} catch {}
|
|
443
559
|
}
|
|
444
560
|
};
|
|
445
561
|
//#endregion
|
|
@@ -508,6 +624,153 @@ function collectGroup(node, group) {
|
|
|
508
624
|
return out;
|
|
509
625
|
}
|
|
510
626
|
//#endregion
|
|
627
|
+
//#region src/gameplay/juice.ts
|
|
628
|
+
/**
|
|
629
|
+
* Game-feel primitives ("juice"): the weapon cooldown, camera shake, screen
|
|
630
|
+
* flash and hit-stop that action examples kept hand-rolling.
|
|
631
|
+
*/
|
|
632
|
+
/**
|
|
633
|
+
* A fire-rate / ability cooldown — replaces the copy-pasted
|
|
634
|
+
* `this.clock += dt * 1000; if (now > nextFire)` pattern:
|
|
635
|
+
*
|
|
636
|
+
* private gun = new Cooldown(0.2);
|
|
637
|
+
* override update(dt: number): void {
|
|
638
|
+
* this.gun.tick(dt);
|
|
639
|
+
* if (this.engine.input.isPressed('fire') && this.gun.tryUse()) this.shoot();
|
|
640
|
+
* }
|
|
641
|
+
*/
|
|
642
|
+
var Cooldown = class {
|
|
643
|
+
seconds;
|
|
644
|
+
remaining = 0;
|
|
645
|
+
constructor(seconds) {
|
|
646
|
+
this.seconds = seconds;
|
|
647
|
+
}
|
|
648
|
+
/** Advance time. Call once per update with the frame dt. */
|
|
649
|
+
tick(dt) {
|
|
650
|
+
if (this.remaining > 0) this.remaining -= dt;
|
|
651
|
+
}
|
|
652
|
+
get ready() {
|
|
653
|
+
return this.remaining <= 0;
|
|
654
|
+
}
|
|
655
|
+
/** 0..1 — how far through the cooldown we are (1 = ready). UiBar-friendly. */
|
|
656
|
+
get progress() {
|
|
657
|
+
return this.seconds <= 0 ? 1 : Math.min(1, Math.max(0, 1 - this.remaining / this.seconds));
|
|
658
|
+
}
|
|
659
|
+
/** Consume if ready. Returns whether the action should fire. */
|
|
660
|
+
tryUse() {
|
|
661
|
+
if (this.remaining > 0) return false;
|
|
662
|
+
this.remaining = this.seconds;
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
/** Force-ready (pickups that reset your reload). */
|
|
666
|
+
reset() {
|
|
667
|
+
this.remaining = 0;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
const SHAKE_FALLOFF_SECONDS = .28;
|
|
671
|
+
/**
|
|
672
|
+
* Camera shake as a standalone behavior for cameras WITHOUT another script.
|
|
673
|
+
* (FollowCamera has this built in — one script per node.) Composes with any
|
|
674
|
+
* other position writer by applying only the DELTA of its own offset, so the
|
|
675
|
+
* camera returns exactly to where the other writer left it.
|
|
676
|
+
*
|
|
677
|
+
* (camera.behavior as CameraShake).shake(8); // pixels (2D) / meters·100 feel (3D: use ~0.2)
|
|
678
|
+
*/
|
|
679
|
+
var CameraShake = class extends Behavior {
|
|
680
|
+
static props = { falloff: { default: SHAKE_FALLOFF_SECONDS } };
|
|
681
|
+
/** Seconds a shake takes to decay to zero. */
|
|
682
|
+
falloff = SHAKE_FALLOFF_SECONDS;
|
|
683
|
+
magnitude = 0;
|
|
684
|
+
t = 0;
|
|
685
|
+
prev = [];
|
|
686
|
+
shake(magnitude, seconds) {
|
|
687
|
+
this.magnitude = Math.max(this.magnitude, magnitude);
|
|
688
|
+
this.t = seconds ?? this.falloff;
|
|
689
|
+
}
|
|
690
|
+
update(dt) {
|
|
691
|
+
const node = requirePosition(this);
|
|
692
|
+
const pos = node.position;
|
|
693
|
+
for (let i = 0; i < this.prev.length; i++) pos[i] = (pos[i] ?? 0) - (this.prev[i] ?? 0);
|
|
694
|
+
if (this.t <= 0) {
|
|
695
|
+
if (this.prev.length > 0) {
|
|
696
|
+
node.position = pos.slice();
|
|
697
|
+
this.prev.length = 0;
|
|
698
|
+
}
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
this.t -= dt;
|
|
702
|
+
const k = this.magnitude * Math.max(0, this.t) / this.falloff;
|
|
703
|
+
if (this.t <= 0) this.magnitude = 0;
|
|
704
|
+
const next = pos.slice();
|
|
705
|
+
if (this.prev.length !== next.length) this.prev = new Array(next.length).fill(0);
|
|
706
|
+
for (let i = 0; i < next.length; i++) {
|
|
707
|
+
const scale = next.length >= 3 && i === 1 ? .5 : 1;
|
|
708
|
+
this.prev[i] = (this.engine.rng.next() * 2 - 1) * k * scale;
|
|
709
|
+
next[i] = (next[i] ?? 0) + this.prev[i];
|
|
710
|
+
}
|
|
711
|
+
node.position = next;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
/**
|
|
715
|
+
* Full-screen color flash (damage red, pickup white). DOM overlay above
|
|
716
|
+
* everything; headless no-op. Repeated calls restart the fade.
|
|
717
|
+
*/
|
|
718
|
+
function screenFlash(color = "#ffffff", opacity = .35, seconds = .25) {
|
|
719
|
+
if (typeof document === "undefined") return;
|
|
720
|
+
const id = "incanto-screen-flash";
|
|
721
|
+
let el = document.getElementById(id);
|
|
722
|
+
if (!el) {
|
|
723
|
+
el = document.createElement("div");
|
|
724
|
+
el.id = id;
|
|
725
|
+
el.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:9999;opacity:0;transition:opacity .05s;";
|
|
726
|
+
document.body.appendChild(el);
|
|
727
|
+
}
|
|
728
|
+
el.style.background = color;
|
|
729
|
+
el.style.transition = "opacity .05s";
|
|
730
|
+
el.style.opacity = String(opacity);
|
|
731
|
+
const fade = () => {
|
|
732
|
+
el.style.transition = `opacity ${seconds}s`;
|
|
733
|
+
el.style.opacity = "0";
|
|
734
|
+
};
|
|
735
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(fade);
|
|
736
|
+
else fade();
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Hit-stop: freeze game time for `seconds` of REAL time, then restore the
|
|
740
|
+
* previous timeScale. Stacking calls extend the freeze instead of fighting.
|
|
741
|
+
*/
|
|
742
|
+
function hitStop(engine, seconds = .08) {
|
|
743
|
+
const state = hitStops.get(engine);
|
|
744
|
+
if (state) {
|
|
745
|
+
state.until = Math.max(state.until, realNow() + seconds * 1e3);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const restore = engine.timeScale;
|
|
749
|
+
const entry = {
|
|
750
|
+
until: realNow() + seconds * 1e3,
|
|
751
|
+
restore
|
|
752
|
+
};
|
|
753
|
+
hitStops.set(engine, entry);
|
|
754
|
+
engine.timeScale = 0;
|
|
755
|
+
const pump = () => {
|
|
756
|
+
if (realNow() >= entry.until) {
|
|
757
|
+
engine.timeScale = entry.restore;
|
|
758
|
+
hitStops.delete(engine);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
schedule(pump);
|
|
762
|
+
};
|
|
763
|
+
schedule(pump);
|
|
764
|
+
}
|
|
765
|
+
const hitStops = /* @__PURE__ */ new Map();
|
|
766
|
+
function realNow() {
|
|
767
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
768
|
+
}
|
|
769
|
+
function schedule(fn) {
|
|
770
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(fn);
|
|
771
|
+
else setTimeout(fn, 16);
|
|
772
|
+
}
|
|
773
|
+
//#endregion
|
|
511
774
|
//#region src/gameplay/lifetime.ts
|
|
512
775
|
/**
|
|
513
776
|
* Self-destruct after a fixed time — bullets, particles, temporary spawns,
|
|
@@ -876,7 +1139,11 @@ var Projectile = class extends Behavior {
|
|
|
876
1139
|
update(dt) {
|
|
877
1140
|
const node = this.node;
|
|
878
1141
|
if (this.gravity !== 0) this.velocity[1] = (this.velocity[1] ?? 0) + this.gravity * dt;
|
|
879
|
-
|
|
1142
|
+
const pos = node.position;
|
|
1143
|
+
const n = Math.max(pos.length, this.velocity.length);
|
|
1144
|
+
const next = new Array(n);
|
|
1145
|
+
for (let i = 0; i < n; i++) next[i] = (pos[i] ?? 0) + (this.velocity[i] ?? 0) * dt;
|
|
1146
|
+
node.position = next;
|
|
880
1147
|
}
|
|
881
1148
|
/** Resolve `direction` to a unit vector, deriving 'forward' from rotation. */
|
|
882
1149
|
resolveDirection() {
|
|
@@ -1470,6 +1737,8 @@ var ZombieAI = class extends Behavior {
|
|
|
1470
1737
|
*/
|
|
1471
1738
|
/** Every built-in gameplay behavior, keyed by its registration name. */
|
|
1472
1739
|
const GAMEPLAY_BEHAVIORS = {
|
|
1740
|
+
CameraShake,
|
|
1741
|
+
GameFlow,
|
|
1473
1742
|
Health,
|
|
1474
1743
|
Lifetime,
|
|
1475
1744
|
ScoreKeeper,
|
|
@@ -1498,4 +1767,4 @@ function registerGameplayBehaviors(opts) {
|
|
|
1498
1767
|
for (const [name, ctor] of Object.entries(GAMEPLAY_BEHAVIORS)) registerBehavior(name, ctor, { replace });
|
|
1499
1768
|
}
|
|
1500
1769
|
//#endregion
|
|
1501
|
-
export { Health as _, Wander as a, Projectile as c, Oscillate as d, MoveTo as f,
|
|
1770
|
+
export { Health as C, DamageOnContact as S, Chase as T, screenFlash as _, Wander as a, restartScene as b, Projectile as c, Oscillate as d, MoveTo as f, hitStop as g, Cooldown as h, WaveSpawner as i, Pickup as l, CameraShake as m, registerGameplayBehaviors as n, Spawner as o, Lifetime as p, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Patrol as u, Interactable as v, Collector as w, FollowCamera as x, GameFlow as y };
|
package/dist/gameplay.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as JsonValue } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { T as Node, l as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-
|
|
2
|
+
import { T as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-CWhW3oa6.js";
|
|
3
3
|
|
|
4
4
|
//#region src/gameplay/chase.d.ts
|
|
5
5
|
/**
|
|
@@ -154,6 +154,11 @@ declare class FollowCamera extends Behavior {
|
|
|
154
154
|
smoothing: number;
|
|
155
155
|
deadzone: number;
|
|
156
156
|
override onReady(): void;
|
|
157
|
+
private shakeMag;
|
|
158
|
+
private shakeT;
|
|
159
|
+
private shakeFalloff;
|
|
160
|
+
/** Kick the camera (impacts, explosions). Composes with the follow. */
|
|
161
|
+
shake(magnitude: number, seconds?: number): void;
|
|
157
162
|
override update(dt: number): void;
|
|
158
163
|
}
|
|
159
164
|
//#endregion
|
|
@@ -528,6 +533,112 @@ declare class ZombieAI extends Behavior {
|
|
|
528
533
|
private pickGoal;
|
|
529
534
|
}
|
|
530
535
|
//#endregion
|
|
536
|
+
//#region src/gameplay/game-flow.d.ts
|
|
537
|
+
/**
|
|
538
|
+
* Reload the CURRENT scene from its source JSON — fresh nodes, reset physics,
|
|
539
|
+
* rewired input. The restart primitive every game-over screen wants.
|
|
540
|
+
*/
|
|
541
|
+
declare function restartScene(engine: Engine): void;
|
|
542
|
+
type GameFlowState = "playing" | "paused" | "gameover" | "won";
|
|
543
|
+
/**
|
|
544
|
+
* The win/lose/restart state machine 6+ examples hand-rolled as `over`/`win`
|
|
545
|
+
* booleans. Attach to any node (the scene root is natural):
|
|
546
|
+
*
|
|
547
|
+
* const flow = root.behavior as GameFlow; // script: { "name": "GameFlow" }
|
|
548
|
+
* flow.gameOver('YOU DIED'); // freezes time, sticky banner
|
|
549
|
+
* flow.win('AREA CLEAR');
|
|
550
|
+
* flow.pause(); flow.resume();
|
|
551
|
+
*
|
|
552
|
+
* While in `gameover`/`won`, pressing `restartAction` (default action name
|
|
553
|
+
* 'restart' — declare it in the scene input map, or leave undeclared and call
|
|
554
|
+
* `flow.restart()` yourself) reloads the scene from source. Banners render
|
|
555
|
+
* through a `%Banner` UiBanner when one exists; otherwise states are silent
|
|
556
|
+
* (drive your own UI off the `flowChanged` signal).
|
|
557
|
+
*/
|
|
558
|
+
declare class GameFlow extends Behavior {
|
|
559
|
+
static readonly signals: readonly string[];
|
|
560
|
+
static readonly props: Record<string, {
|
|
561
|
+
default: string | boolean;
|
|
562
|
+
}>;
|
|
563
|
+
/** Input action that restarts from gameover/won (declare it in `input{}`). */
|
|
564
|
+
restartAction: string;
|
|
565
|
+
/** Freeze `engine.timeScale` on gameover/won (banner UI keeps rendering). */
|
|
566
|
+
freezeOnEnd: boolean;
|
|
567
|
+
/** Where the flow looks for a UiBanner ('' = never). */
|
|
568
|
+
bannerPath: string;
|
|
569
|
+
state: GameFlowState;
|
|
570
|
+
private frozeScale;
|
|
571
|
+
gameOver(text?: string, color?: string): void;
|
|
572
|
+
win(text?: string, color?: string): void;
|
|
573
|
+
pause(): void;
|
|
574
|
+
resume(): void;
|
|
575
|
+
restart(): void;
|
|
576
|
+
private transition;
|
|
577
|
+
private freeze;
|
|
578
|
+
private thaw;
|
|
579
|
+
override update(): void;
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/gameplay/juice.d.ts
|
|
583
|
+
/**
|
|
584
|
+
* Game-feel primitives ("juice"): the weapon cooldown, camera shake, screen
|
|
585
|
+
* flash and hit-stop that action examples kept hand-rolling.
|
|
586
|
+
*/
|
|
587
|
+
/**
|
|
588
|
+
* A fire-rate / ability cooldown — replaces the copy-pasted
|
|
589
|
+
* `this.clock += dt * 1000; if (now > nextFire)` pattern:
|
|
590
|
+
*
|
|
591
|
+
* private gun = new Cooldown(0.2);
|
|
592
|
+
* override update(dt: number): void {
|
|
593
|
+
* this.gun.tick(dt);
|
|
594
|
+
* if (this.engine.input.isPressed('fire') && this.gun.tryUse()) this.shoot();
|
|
595
|
+
* }
|
|
596
|
+
*/
|
|
597
|
+
declare class Cooldown {
|
|
598
|
+
readonly seconds: number;
|
|
599
|
+
private remaining;
|
|
600
|
+
constructor(seconds: number);
|
|
601
|
+
/** Advance time. Call once per update with the frame dt. */
|
|
602
|
+
tick(dt: number): void;
|
|
603
|
+
get ready(): boolean;
|
|
604
|
+
/** 0..1 — how far through the cooldown we are (1 = ready). UiBar-friendly. */
|
|
605
|
+
get progress(): number;
|
|
606
|
+
/** Consume if ready. Returns whether the action should fire. */
|
|
607
|
+
tryUse(): boolean;
|
|
608
|
+
/** Force-ready (pickups that reset your reload). */
|
|
609
|
+
reset(): void;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Camera shake as a standalone behavior for cameras WITHOUT another script.
|
|
613
|
+
* (FollowCamera has this built in — one script per node.) Composes with any
|
|
614
|
+
* other position writer by applying only the DELTA of its own offset, so the
|
|
615
|
+
* camera returns exactly to where the other writer left it.
|
|
616
|
+
*
|
|
617
|
+
* (camera.behavior as CameraShake).shake(8); // pixels (2D) / meters·100 feel (3D: use ~0.2)
|
|
618
|
+
*/
|
|
619
|
+
declare class CameraShake extends Behavior {
|
|
620
|
+
static readonly props: Record<string, {
|
|
621
|
+
default: number;
|
|
622
|
+
}>;
|
|
623
|
+
/** Seconds a shake takes to decay to zero. */
|
|
624
|
+
falloff: number;
|
|
625
|
+
private magnitude;
|
|
626
|
+
private t;
|
|
627
|
+
private prev;
|
|
628
|
+
shake(magnitude: number, seconds?: number): void;
|
|
629
|
+
override update(dt: number): void;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Full-screen color flash (damage red, pickup white). DOM overlay above
|
|
633
|
+
* everything; headless no-op. Repeated calls restart the fade.
|
|
634
|
+
*/
|
|
635
|
+
declare function screenFlash(color?: string, opacity?: number, seconds?: number): void;
|
|
636
|
+
/**
|
|
637
|
+
* Hit-stop: freeze game time for `seconds` of REAL time, then restore the
|
|
638
|
+
* previous timeScale. Stacking calls extend the freeze instead of fighting.
|
|
639
|
+
*/
|
|
640
|
+
declare function hitStop(engine: Engine, seconds?: number): void;
|
|
641
|
+
//#endregion
|
|
531
642
|
//#region src/gameplay/index.d.ts
|
|
532
643
|
/** Every built-in gameplay behavior, keyed by its registration name. */
|
|
533
644
|
declare const GAMEPLAY_BEHAVIORS: Readonly<Record<string, BehaviorCtor>>;
|
|
@@ -540,4 +651,4 @@ declare function registerGameplayBehaviors(opts?: {
|
|
|
540
651
|
replace?: boolean;
|
|
541
652
|
}): void;
|
|
542
653
|
//#endregion
|
|
543
|
-
export { Chase, Collector, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, registerGameplayBehaviors };
|
|
654
|
+
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
|
package/dist/gameplay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { Chase, Collector, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, registerGameplayBehaviors };
|
|
1
|
+
import { C as Health, S as DamageOnContact, T as Chase, _ as screenFlash, a as Wander, b as restartScene, c as Projectile, d as Oscillate, f as MoveTo, g as hitStop, h as Cooldown, i as WaveSpawner, l as Pickup, m as CameraShake, n as registerGameplayBehaviors, o as Spawner, p as Lifetime, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as Interactable, w as Collector, x as FollowCamera, y as GameFlow } from "./gameplay-CDFgSG6z.js";
|
|
2
|
+
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
|