zx-kit 0.12.0 → 0.13.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/README.md CHANGED
@@ -140,6 +140,8 @@ requestAnimationFrame(loop)
140
140
  | [`sprite.ts`](#spritets--free-roaming-sprites) | Sprites: position, velocity, gravity, flip, render |
141
141
  | [`collision.ts`](#collisionts--aabb-collision) | AABB overlap tests, tile-map wall resolution |
142
142
  | [`animation.ts`](#animationts--frame-timer--tween) | Frame-timer for sprite strips, position tween between two points |
143
+ | [`camera.ts`](#camerats--scrolling-camera) | Viewport that follows a target with lerp + deadzone, world-bounds clamping |
144
+ | [`scene.ts`](#scenets--scene-manager) | Stack-based scene manager with onEnter/onExit/onPause/onResume hooks |
143
145
  | [`tilemap.ts`](#tilemapts--tile-map-engine) | Scrollable maps, solid tiles, O(1) id-index, background swap |
144
146
  | [`palette.ts`](#palettets--color-constants) | 15 Spectrum colors, `SpectrumColor` type, `CELL`, `SCALE` |
145
147
  | [`font.ts`](#fontts--rom-bitmap-font) | 96-character ROM font, raw bitmap access |
@@ -993,6 +995,145 @@ state.blink = blink
993
995
 
994
996
  ---
995
997
 
998
+ ## `camera.ts` — Scrolling Camera
999
+
1000
+ A 2D viewport that maps a window of world space onto the screen. The camera follows a target point (typically the player) with frame-rate-independent smoothing and an optional deadzone, and clamps to world bounds so the viewport never sees outside the map.
1001
+
1002
+ ```ts
1003
+ import { createCamera, setCameraTarget, tickCamera, worldToScreen, isInView } from 'zx-kit'
1004
+
1005
+ const cam = createCamera({
1006
+ viewW: 256, viewH: 192, // game canvas size
1007
+ worldW: 2048, worldH: 192, // a long horizontal level
1008
+ lerp: 0.15, // smooth follow (15% of remaining distance per 16.67 ms)
1009
+ deadzoneW: 64, deadzoneH: 0, // ±32 px horizontal slack before the camera scrolls
1010
+ })
1011
+
1012
+ // In your game loop:
1013
+ setCameraTarget(cam, player.x, player.y)
1014
+ tickCamera(cam, dt)
1015
+
1016
+ // Render anything via worldToScreen — sprites stay aligned to the camera:
1017
+ for (const e of enemies) {
1018
+ if (!isInView(cam, e.x, e.y, 8, 8)) continue // cull off-screen
1019
+ const s = worldToScreen(cam, e.x, e.y)
1020
+ drawSprite(ctx, ENEMY, s.x, s.y, C.B_RED, C.BLACK)
1021
+ }
1022
+ ```
1023
+
1024
+ ### `Camera` interface
1025
+
1026
+ | Field | Description |
1027
+ |-------|-------------|
1028
+ | `x`, `y` | Current viewport top-left in world pixels (mutated by `tickCamera`) |
1029
+ | `viewW`, `viewH` | Viewport size in pixels |
1030
+ | `worldW`, `worldH` | World size in pixels — camera clamps so `x ∈ [0, worldW-viewW]` |
1031
+ | `lerp` | `(0..1]` — fraction of remaining distance covered per 60 fps frame. `1` = snap. |
1032
+ | `deadzoneW`, `deadzoneH` | Deadzone size — target may move ±`deadzoneW/2` from centre before scrolling |
1033
+ | `targetX`, `targetY` | Current follow target (set via `setCameraTarget`) |
1034
+
1035
+ ### `createCamera(opts): Camera`
1036
+
1037
+ Creates a camera at world origin `(0, 0)`. `lerp` defaults to `1` (snap), deadzones default to `0`.
1038
+
1039
+ ### `setCameraTarget(cam, x, y): void`
1040
+
1041
+ Sets the world-space follow target. Does not move the camera — `tickCamera` does that.
1042
+
1043
+ ### `tickCamera(cam, dt): void`
1044
+
1045
+ Advances the camera one frame:
1046
+ 1. Computes the desired viewport position from the target, honouring the deadzone
1047
+ 2. Eases `cam.x` / `cam.y` toward the desired position using `lerp` (frame-rate-corrected by `dt`)
1048
+ 3. Clamps to world bounds so the viewport never sees outside the world
1049
+
1050
+ The lerp is dt-independent: one `tickCamera(cam, 33.34)` call produces (within floating-point precision) the same result as two `tickCamera(cam, 16.67)` calls. If the world is smaller than the viewport the camera pins to `(0, 0)`.
1051
+
1052
+ ### `worldToScreen(cam, wx, wy): { x, y }`
1053
+
1054
+ Converts a world coordinate to a screen (viewport-relative) coordinate. Subtracts `cam.x` / `cam.y`.
1055
+
1056
+ ### `isInView(cam, wx, wy, w?, h?): boolean`
1057
+
1058
+ Returns `true` when a world rectangle of size `w × h` (default `0 × 0` for a point test) overlaps the camera viewport. Use to cull off-screen sprites before drawing.
1059
+
1060
+ ---
1061
+
1062
+ ## `scene.ts` — Scene Manager
1063
+
1064
+ A stack-based scene manager with full lifecycle hooks. Replaces ad-hoc phase enums (`'intro' | 'playing' | 'gameover'`) with a clean push / pop / replace API. Only the **top** scene receives `update`, so pushing a pause overlay freezes everything beneath; **all** scenes receive `render` bottom-up, so the paused scene stays visible.
1065
+
1066
+ ```ts
1067
+ import { createSceneManager, pushScene, popScene, updateScenes, renderScenes, type Scene } from 'zx-kit'
1068
+
1069
+ const gameplay: Scene = {
1070
+ name: 'gameplay',
1071
+ update(dt) { /* tick game */ },
1072
+ render(ctx) { /* draw game */ },
1073
+ onPause() { stopAmbientSound() },
1074
+ onResume() { startAmbientSound() },
1075
+ }
1076
+
1077
+ const pauseOverlay: Scene = {
1078
+ name: 'pause',
1079
+ update(_dt) { if (keys.pressed('P')) popScene(mgr) },
1080
+ render(ctx) { drawTextCentered(ctx, '** PAUSED **', ROWS/2 * CELL, C.B_WHITE, C.BLACK) },
1081
+ }
1082
+
1083
+ const mgr = createSceneManager()
1084
+ pushScene(mgr, gameplay) // gameplay.onEnter(null)
1085
+ // later, player presses P:
1086
+ pushScene(mgr, pauseOverlay) // gameplay.onPause() → pauseOverlay.onEnter(gameplay)
1087
+
1088
+ // Game loop:
1089
+ updateScenes(mgr, dt) // only top scene ticks
1090
+ renderScenes(mgr, ctx) // bottom-up: gameplay first, then pause overlay
1091
+ ```
1092
+
1093
+ ### `Scene` interface
1094
+
1095
+ | Field | Description |
1096
+ |-------|-------------|
1097
+ | `name` | Human-readable identifier (for logging / debugging) |
1098
+ | `update(dt)` | Called once per frame on the **top** scene only |
1099
+ | `render(ctx)` | Called once per frame on **all** scenes, bottom-up |
1100
+ | `onEnter?(prev)` | Fired when this scene becomes top (push / replace / initial). `prev` is the previously-top scene or `null`. |
1101
+ | `onExit?(next)` | Fired when this scene is removed (pop / replace). `next` is what becomes top, or `null`. |
1102
+ | `onPause?()` | Fired when another scene is pushed on top of this one. |
1103
+ | `onResume?()` | Fired when the scene above this one is popped. |
1104
+
1105
+ ### `createSceneManager(): SceneManager`
1106
+
1107
+ Creates a manager with an empty stack.
1108
+
1109
+ ### `pushScene(mgr, scene): void`
1110
+
1111
+ Pushes a scene onto the stack. Lifecycle order: `prev.onPause()` → `scene.onEnter(prev)`. Use for modal overlays, dialogs, pause screens.
1112
+
1113
+ ### `popScene(mgr): Scene | null`
1114
+
1115
+ Pops the top scene and returns it (or `null` if the stack was empty). Lifecycle order: `top.onExit(below)` → `below.onResume()`.
1116
+
1117
+ ### `replaceScene(mgr, scene): void`
1118
+
1119
+ Swaps the top scene without affecting scenes beneath. Lifecycle order: `outgoing.onExit(scene)` → `scene.onEnter(outgoing)`. Does **not** fire `onPause` / `onResume` on the scene below — it was never paused by this call. Use for state transitions like `gameplay → gameOver` while keeping `intro` on the bottom.
1120
+
1121
+ On an empty manager `replaceScene` behaves like `pushScene` (outgoing is `null`).
1122
+
1123
+ ### `currentScene(mgr): Scene | null`
1124
+
1125
+ Returns the top scene, or `null` if the stack is empty.
1126
+
1127
+ ### `updateScenes(mgr, dt): void`
1128
+
1129
+ Updates the top scene only. No-op on an empty manager. Scenes beneath the top stay frozen — this is what makes pause overlays work.
1130
+
1131
+ ### `renderScenes(mgr, ctx): void`
1132
+
1133
+ Renders every scene from bottom to top. No-op on an empty manager.
1134
+
1135
+ ---
1136
+
996
1137
  ## `tilemap.ts` — Tile Map Engine
997
1138
 
998
1139
  A scrollable, queryable tile map backed by an O(1) id-index. Tiles use the same 8×8 sprite format as `drawSprite`. Supports solid-tile collision queries, viewport-clipped rendering, and smart seasonal background swapping.
@@ -0,0 +1,117 @@
1
+ /**
2
+ * A 2D camera that maps a window of world space onto the screen viewport.
3
+ *
4
+ * The camera's position (`x`, `y`) is the world-pixel coordinate of the viewport's
5
+ * top-left corner. Use `worldToScreen` to convert sprite positions for rendering,
6
+ * or `tickCamera` each frame to follow a moving target with smoothing.
7
+ *
8
+ * **Coordinate model:**
9
+ * - World extends from `(0,0)` to `(worldW, worldH)`
10
+ * - Viewport is a `viewW × viewH` window into the world
11
+ * - Camera clamps so the viewport never sees outside world bounds
12
+ *
13
+ * **Smoothing:**
14
+ * - `lerp = 1` → snaps to target each tick
15
+ * - `lerp < 1` → eases toward target; lower = slower follow
16
+ * - The lerp factor is **frame-rate independent** — internally adjusted by `dt`
17
+ *
18
+ * **Deadzone:**
19
+ * - Rectangle centred on the camera's centre point
20
+ * - While the target stays inside the deadzone, the camera does not move
21
+ * - Once the target exits, the camera shifts just enough to put the target back on the deadzone edge
22
+ */
23
+ export interface Camera {
24
+ /** Current viewport top-left X in world pixels. */
25
+ x: number;
26
+ /** Current viewport top-left Y in world pixels. */
27
+ y: number;
28
+ /** Viewport width in pixels (typically the game's canvas width). */
29
+ viewW: number;
30
+ /** Viewport height in pixels. */
31
+ viewH: number;
32
+ /** World width in pixels — camera clamps so `x` stays in `[0, worldW - viewW]`. */
33
+ worldW: number;
34
+ /** World height in pixels. */
35
+ worldH: number;
36
+ /**
37
+ * Lerp factor in `(0..1]` — fraction of remaining distance covered per 60 fps frame.
38
+ * `1` = instant snap, `0.1` = covers 10 % of distance per 16.67 ms.
39
+ * Frame-rate corrected: `tickCamera(cam, 33.34)` ≈ two `tickCamera(cam, 16.67)` calls.
40
+ */
41
+ lerp: number;
42
+ /** Deadzone width — target may move ±`deadzoneW/2` from viewport centre without scrolling. */
43
+ deadzoneW: number;
44
+ /** Deadzone height. */
45
+ deadzoneH: number;
46
+ /** Current target X in world pixels (set via `setCameraTarget`). */
47
+ targetX: number;
48
+ /** Current target Y in world pixels. */
49
+ targetY: number;
50
+ }
51
+ /**
52
+ * Options passed to `createCamera`. See {@link Camera} for full semantics.
53
+ */
54
+ export interface CameraOptions {
55
+ viewW: number;
56
+ viewH: number;
57
+ worldW: number;
58
+ worldH: number;
59
+ /** Default `1` (instant snap). */
60
+ lerp?: number;
61
+ /** Default `0`. */
62
+ deadzoneW?: number;
63
+ /** Default `0`. */
64
+ deadzoneH?: number;
65
+ }
66
+ /**
67
+ * Creates a Camera at world origin `(0, 0)`.
68
+ *
69
+ * @example
70
+ * const cam = createCamera({
71
+ * viewW: 256, viewH: 192, // game canvas size
72
+ * worldW: 2048, worldH: 192, // long horizontal level
73
+ * lerp: 0.15, deadzoneW: 64, // smooth follow with a centre deadzone
74
+ * })
75
+ */
76
+ export declare function createCamera(opts: CameraOptions): Camera;
77
+ /**
78
+ * Sets the world-space point the camera should track.
79
+ * Call each frame with the player (or other follow target) position.
80
+ * Does not move the camera itself — call `tickCamera` for that.
81
+ */
82
+ export declare function setCameraTarget(cam: Camera, x: number, y: number): void;
83
+ /**
84
+ * Advances the camera one frame:
85
+ * 1. Computes the desired viewport position from `targetX/Y`, respecting the deadzone
86
+ * 2. Eases `cam.x/y` toward the desired position by `lerp` (frame-rate-corrected by `dt`)
87
+ * 3. Clamps to world bounds so the viewport never sees outside the world
88
+ *
89
+ * Call once per frame after `setCameraTarget`.
90
+ */
91
+ export declare function tickCamera(cam: Camera, dt: number): void;
92
+ /**
93
+ * Converts a world coordinate to a screen (viewport-relative) coordinate.
94
+ * Useful when drawing sprites: `drawSprite(ctx, bmp, screen.x, screen.y, ink, paper)`.
95
+ *
96
+ * @example
97
+ * const s = worldToScreen(cam, enemy.x, enemy.y)
98
+ * drawSprite(ctx, ENEMY, s.x, s.y, C.B_RED, C.BLACK)
99
+ */
100
+ export declare function worldToScreen(cam: Camera, wx: number, wy: number): {
101
+ x: number;
102
+ y: number;
103
+ };
104
+ /**
105
+ * Returns `true` when a world-space rectangle overlaps the camera viewport.
106
+ * Use to cull off-screen sprites before drawing.
107
+ *
108
+ * `w` and `h` default to `0` (point test). For an 8×8 sprite pass `8, 8`.
109
+ *
110
+ * @example
111
+ * for (const e of enemies) {
112
+ * if (!isInView(cam, e.x, e.y, 8, 8)) continue
113
+ * // draw e
114
+ * }
115
+ */
116
+ export declare function isInView(cam: Camera, wx: number, wy: number, w?: number, h?: number): boolean;
117
+ //# sourceMappingURL=camera.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,MAAM;IACrB,mDAAmD;IACnD,CAAC,EAAE,MAAM,CAAA;IACT,mDAAmD;IACnD,CAAC,EAAE,MAAM,CAAA;IACT,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,8FAA8F;IAC9F,SAAS,EAAE,MAAM,CAAA;IACjB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAA;IACf,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAcxD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGvE;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAwCxD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAE3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,SAAI,EAAE,CAAC,SAAI,GAAG,OAAO,CAKnF"}
package/dist/camera.js ADDED
@@ -0,0 +1,114 @@
1
+ // ── Camera: viewport that follows a target across a larger world ────────────
2
+ /**
3
+ * Creates a Camera at world origin `(0, 0)`.
4
+ *
5
+ * @example
6
+ * const cam = createCamera({
7
+ * viewW: 256, viewH: 192, // game canvas size
8
+ * worldW: 2048, worldH: 192, // long horizontal level
9
+ * lerp: 0.15, deadzoneW: 64, // smooth follow with a centre deadzone
10
+ * })
11
+ */
12
+ export function createCamera(opts) {
13
+ return {
14
+ x: 0,
15
+ y: 0,
16
+ viewW: opts.viewW,
17
+ viewH: opts.viewH,
18
+ worldW: opts.worldW,
19
+ worldH: opts.worldH,
20
+ lerp: opts.lerp ?? 1,
21
+ deadzoneW: opts.deadzoneW ?? 0,
22
+ deadzoneH: opts.deadzoneH ?? 0,
23
+ targetX: 0,
24
+ targetY: 0,
25
+ };
26
+ }
27
+ /**
28
+ * Sets the world-space point the camera should track.
29
+ * Call each frame with the player (or other follow target) position.
30
+ * Does not move the camera itself — call `tickCamera` for that.
31
+ */
32
+ export function setCameraTarget(cam, x, y) {
33
+ cam.targetX = x;
34
+ cam.targetY = y;
35
+ }
36
+ /**
37
+ * Advances the camera one frame:
38
+ * 1. Computes the desired viewport position from `targetX/Y`, respecting the deadzone
39
+ * 2. Eases `cam.x/y` toward the desired position by `lerp` (frame-rate-corrected by `dt`)
40
+ * 3. Clamps to world bounds so the viewport never sees outside the world
41
+ *
42
+ * Call once per frame after `setCameraTarget`.
43
+ */
44
+ export function tickCamera(cam, dt) {
45
+ // Desired viewport position that puts target on the relevant deadzone edge.
46
+ const halfW = cam.viewW / 2;
47
+ const halfH = cam.viewH / 2;
48
+ const dzL = cam.deadzoneW / 2;
49
+ const dzT = cam.deadzoneH / 2;
50
+ const centerX = cam.x + halfW;
51
+ const centerY = cam.y + halfH;
52
+ let desiredX = cam.x;
53
+ let desiredY = cam.y;
54
+ if (cam.targetX > centerX + dzL) {
55
+ desiredX = cam.targetX - halfW - dzL;
56
+ }
57
+ else if (cam.targetX < centerX - dzL) {
58
+ desiredX = cam.targetX - halfW + dzL;
59
+ }
60
+ if (cam.targetY > centerY + dzT) {
61
+ desiredY = cam.targetY - halfH - dzT;
62
+ }
63
+ else if (cam.targetY < centerY - dzT) {
64
+ desiredY = cam.targetY - halfH + dzT;
65
+ }
66
+ // Frame-rate-corrected lerp: t = 1 - (1 - lerp)^(dt/16.67)
67
+ const FRAME_MS = 16.67;
68
+ const t = cam.lerp >= 1
69
+ ? (dt > 0 ? 1 : 0)
70
+ : (dt > 0 ? 1 - Math.pow(1 - cam.lerp, dt / FRAME_MS) : 0);
71
+ cam.x += (desiredX - cam.x) * t;
72
+ cam.y += (desiredY - cam.y) * t;
73
+ // Clamp to world bounds; if world is smaller than viewport, pin to 0.
74
+ const maxX = Math.max(0, cam.worldW - cam.viewW);
75
+ const maxY = Math.max(0, cam.worldH - cam.viewH);
76
+ if (cam.x < 0)
77
+ cam.x = 0;
78
+ else if (cam.x > maxX)
79
+ cam.x = maxX;
80
+ if (cam.y < 0)
81
+ cam.y = 0;
82
+ else if (cam.y > maxY)
83
+ cam.y = maxY;
84
+ }
85
+ /**
86
+ * Converts a world coordinate to a screen (viewport-relative) coordinate.
87
+ * Useful when drawing sprites: `drawSprite(ctx, bmp, screen.x, screen.y, ink, paper)`.
88
+ *
89
+ * @example
90
+ * const s = worldToScreen(cam, enemy.x, enemy.y)
91
+ * drawSprite(ctx, ENEMY, s.x, s.y, C.B_RED, C.BLACK)
92
+ */
93
+ export function worldToScreen(cam, wx, wy) {
94
+ return { x: wx - cam.x, y: wy - cam.y };
95
+ }
96
+ /**
97
+ * Returns `true` when a world-space rectangle overlaps the camera viewport.
98
+ * Use to cull off-screen sprites before drawing.
99
+ *
100
+ * `w` and `h` default to `0` (point test). For an 8×8 sprite pass `8, 8`.
101
+ *
102
+ * @example
103
+ * for (const e of enemies) {
104
+ * if (!isInView(cam, e.x, e.y, 8, 8)) continue
105
+ * // draw e
106
+ * }
107
+ */
108
+ export function isInView(cam, wx, wy, w = 0, h = 0) {
109
+ return wx + w > cam.x
110
+ && wx < cam.x + cam.viewW
111
+ && wy + h > cam.y
112
+ && wy < cam.y + cam.viewH;
113
+ }
114
+ //# sourceMappingURL=camera.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.js","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,4 +9,6 @@ export * from './tilemap.js';
9
9
  export * from './sprite.js';
10
10
  export * from './collision.js';
11
11
  export * from './animation.js';
12
+ export * from './camera.js';
13
+ export * from './scene.js';
12
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -9,4 +9,6 @@ export * from './tilemap.js';
9
9
  export * from './sprite.js';
10
10
  export * from './collision.js';
11
11
  export * from './animation.js';
12
+ export * from './camera.js';
13
+ export * from './scene.js';
12
14
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * A single screen / game state — intro, gameplay, pause overlay, game over, etc.
3
+ *
4
+ * `update` is called on the **top** scene only (so pushing a pause scene freezes
5
+ * everything beneath). `render` is called on **all** scenes bottom-up, so a paused
6
+ * underlying scene remains visible behind a translucent overlay.
7
+ *
8
+ * **Lifecycle hooks (all optional):**
9
+ * - `onEnter(prev)` — the scene becomes top of the stack (push / replace / initial)
10
+ * - `onExit(next)` — the scene is removed (pop / replace); `next` is what becomes top
11
+ * - `onPause()` — another scene is pushed on top of this one
12
+ * - `onResume()` — the scene above this one is popped, this becomes top again
13
+ *
14
+ * Manage scenes via {@link createSceneManager}, {@link pushScene}, {@link popScene}, {@link replaceScene}.
15
+ */
16
+ export interface Scene {
17
+ /** Human-readable identifier, useful for logging and debugging. */
18
+ name: string;
19
+ /** Called once per frame on the top scene only. */
20
+ update(dt: number): void;
21
+ /** Called once per frame on all scenes, bottom-up. */
22
+ render(ctx: CanvasRenderingContext2D): void;
23
+ /** Optional: fired when this scene becomes the top of the stack. `prev` is the previously-top scene, or `null` if the stack was empty. */
24
+ onEnter?(prev: Scene | null): void;
25
+ /** Optional: fired when this scene is removed. `next` is the scene that becomes top (or `null` if the stack becomes empty). */
26
+ onExit?(next: Scene | null): void;
27
+ /** Optional: fired when another scene is pushed on top of this one. */
28
+ onPause?(): void;
29
+ /** Optional: fired when the scene above this one is popped, restoring this scene to top. */
30
+ onResume?(): void;
31
+ }
32
+ /**
33
+ * Holds the scene stack. Mutated by `pushScene`, `popScene`, `replaceScene`.
34
+ * Create via {@link createSceneManager}.
35
+ */
36
+ export interface SceneManager {
37
+ /** Bottom-up scene stack. `stack[stack.length - 1]` is the current top. */
38
+ stack: Scene[];
39
+ }
40
+ /**
41
+ * Creates an empty SceneManager.
42
+ *
43
+ * @example
44
+ * const mgr = createSceneManager()
45
+ * pushScene(mgr, introScene)
46
+ *
47
+ * // In game loop:
48
+ * updateScenes(mgr, dt)
49
+ * renderScenes(mgr, ctx)
50
+ */
51
+ export declare function createSceneManager(): SceneManager;
52
+ /**
53
+ * Returns the top scene, or `null` if the stack is empty.
54
+ */
55
+ export declare function currentScene(mgr: SceneManager): Scene | null;
56
+ /**
57
+ * Pushes a scene onto the stack. Fires `onPause` on the previous top (if any),
58
+ * then `onEnter(prev)` on the new scene.
59
+ *
60
+ * @example
61
+ * pushScene(mgr, pauseOverlayScene) // freezes underlying game, draws on top
62
+ */
63
+ export declare function pushScene(mgr: SceneManager, scene: Scene): void;
64
+ /**
65
+ * Pops the top scene and returns it (or `null` if the stack was empty).
66
+ * Fires `onExit(below)` on the popped scene, then `onResume` on the uncovered scene below (if any).
67
+ */
68
+ export declare function popScene(mgr: SceneManager): Scene | null;
69
+ /**
70
+ * Replaces the top scene with a new one without affecting scenes beneath.
71
+ * Fires `onExit(incoming)` on the outgoing top, then `onEnter(outgoing)` on the incoming scene.
72
+ * **Does not** fire `onPause` / `onResume` on any scene below — they were never paused by this call.
73
+ *
74
+ * On an empty manager, behaves like `pushScene` (outgoing is `null`).
75
+ *
76
+ * @example
77
+ * replaceScene(mgr, gameOverScene) // swap gameplay → game over, leave intro on bottom
78
+ */
79
+ export declare function replaceScene(mgr: SceneManager, scene: Scene): void;
80
+ /**
81
+ * Updates the top scene only. No-op on an empty manager.
82
+ * Scenes beneath the top remain frozen — this is what makes pause overlays work.
83
+ */
84
+ export declare function updateScenes(mgr: SceneManager, dt: number): void;
85
+ /**
86
+ * Renders every scene from bottom to top. No-op on an empty manager.
87
+ * Bottom-up order means a paused scene stays visible behind a translucent overlay.
88
+ */
89
+ export declare function renderScenes(mgr: SceneManager, ctx: CanvasRenderingContext2D): void;
90
+ //# sourceMappingURL=scene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,KAAK;IACpB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,sDAAsD;IACtD,MAAM,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC3C,0IAA0I;IAC1I,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,CAAA;IAClC,+HAA+H;IAC/H,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,CAAA;IACjC,uEAAuE;IACvE,OAAO,CAAC,IAAI,IAAI,CAAA;IAChB,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,IAAI,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,KAAK,EAAE,KAAK,EAAE,CAAA;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,CAEjD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,KAAK,GAAG,IAAI,CAE5D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAK/D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,KAAK,GAAG,IAAI,CAOxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAKlE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAGhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAEnF"}
package/dist/scene.js ADDED
@@ -0,0 +1,83 @@
1
+ // ── Scene: stack-based scene manager with onEnter/onExit/onPause/onResume ───
2
+ /**
3
+ * Creates an empty SceneManager.
4
+ *
5
+ * @example
6
+ * const mgr = createSceneManager()
7
+ * pushScene(mgr, introScene)
8
+ *
9
+ * // In game loop:
10
+ * updateScenes(mgr, dt)
11
+ * renderScenes(mgr, ctx)
12
+ */
13
+ export function createSceneManager() {
14
+ return { stack: [] };
15
+ }
16
+ /**
17
+ * Returns the top scene, or `null` if the stack is empty.
18
+ */
19
+ export function currentScene(mgr) {
20
+ return mgr.stack.length === 0 ? null : mgr.stack[mgr.stack.length - 1];
21
+ }
22
+ /**
23
+ * Pushes a scene onto the stack. Fires `onPause` on the previous top (if any),
24
+ * then `onEnter(prev)` on the new scene.
25
+ *
26
+ * @example
27
+ * pushScene(mgr, pauseOverlayScene) // freezes underlying game, draws on top
28
+ */
29
+ export function pushScene(mgr, scene) {
30
+ const prev = currentScene(mgr);
31
+ if (prev)
32
+ prev.onPause?.();
33
+ mgr.stack.push(scene);
34
+ scene.onEnter?.(prev);
35
+ }
36
+ /**
37
+ * Pops the top scene and returns it (or `null` if the stack was empty).
38
+ * Fires `onExit(below)` on the popped scene, then `onResume` on the uncovered scene below (if any).
39
+ */
40
+ export function popScene(mgr) {
41
+ if (mgr.stack.length === 0)
42
+ return null;
43
+ const top = mgr.stack.pop();
44
+ const below = currentScene(mgr);
45
+ top.onExit?.(below);
46
+ if (below)
47
+ below.onResume?.();
48
+ return top;
49
+ }
50
+ /**
51
+ * Replaces the top scene with a new one without affecting scenes beneath.
52
+ * Fires `onExit(incoming)` on the outgoing top, then `onEnter(outgoing)` on the incoming scene.
53
+ * **Does not** fire `onPause` / `onResume` on any scene below — they were never paused by this call.
54
+ *
55
+ * On an empty manager, behaves like `pushScene` (outgoing is `null`).
56
+ *
57
+ * @example
58
+ * replaceScene(mgr, gameOverScene) // swap gameplay → game over, leave intro on bottom
59
+ */
60
+ export function replaceScene(mgr, scene) {
61
+ const outgoing = mgr.stack.length > 0 ? mgr.stack.pop() : null;
62
+ outgoing?.onExit?.(scene);
63
+ mgr.stack.push(scene);
64
+ scene.onEnter?.(outgoing);
65
+ }
66
+ /**
67
+ * Updates the top scene only. No-op on an empty manager.
68
+ * Scenes beneath the top remain frozen — this is what makes pause overlays work.
69
+ */
70
+ export function updateScenes(mgr, dt) {
71
+ const top = currentScene(mgr);
72
+ if (top)
73
+ top.update(dt);
74
+ }
75
+ /**
76
+ * Renders every scene from bottom to top. No-op on an empty manager.
77
+ * Bottom-up order means a paused scene stays visible behind a translucent overlay.
78
+ */
79
+ export function renderScenes(mgr, ctx) {
80
+ for (const scene of mgr.stack)
81
+ scene.render(ctx);
82
+ }
83
+ //# sourceMappingURL=scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene.js","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAAA,+EAA+E;AA2C/E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAiB,EAAE,KAAY;IACvD,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,IAAI,IAAI;QAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAA;IAC1B,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAA;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAiB;IACxC,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACvC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAG,CAAA;IAC5B,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC/B,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;IACnB,IAAI,KAAK;QAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAA;IAC7B,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,GAAiB,EAAE,KAAY;IAC1D,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAG,CAAC,CAAC,CAAC,IAAI,CAAA;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;IACzB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrB,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAA;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAiB,EAAE,EAAU;IACxD,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC7B,IAAI,GAAG;QAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAiB,EAAE,GAA6B;IAC3E,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,KAAK;QAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;AAClD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-kit",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zrebec/zx-kit.git"