emeraldengine 2.2.0 → 3.0.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.
Files changed (162) hide show
  1. package/README.md +2359 -968
  2. package/dist/types/index.d.ts +72 -32
  3. package/dist/types/src/Animator.d.ts +50 -0
  4. package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
  5. package/dist/types/src/Camera.d.ts +122 -0
  6. package/dist/types/src/CameraController.d.ts +107 -0
  7. package/dist/types/src/CanvasText.d.ts +91 -0
  8. package/dist/types/src/CollisionLayers.d.ts +58 -0
  9. package/dist/types/{Color.d.ts → src/Color.d.ts} +5 -6
  10. package/dist/types/src/Coroutine.d.ts +111 -0
  11. package/dist/types/src/DebugOverlay.d.ts +86 -0
  12. package/dist/types/src/Drawable.d.ts +271 -0
  13. package/dist/types/src/Easing.d.ts +22 -0
  14. package/dist/types/src/Emerald.d.ts +420 -0
  15. package/dist/types/src/EmeraldDB.d.ts +159 -0
  16. package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
  17. package/dist/types/src/GLUtils.d.ts +4 -0
  18. package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
  19. package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +60 -23
  20. package/dist/types/src/Interpolator.d.ts +66 -0
  21. package/dist/types/src/Material.d.ts +87 -0
  22. package/dist/types/src/MathUtils.d.ts +80 -0
  23. package/dist/types/src/ParticleEmitter.d.ts +131 -0
  24. package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
  25. package/dist/types/src/Pool.d.ts +53 -0
  26. package/dist/types/src/PostEffects.d.ts +68 -0
  27. package/dist/types/src/PostProcessor.d.ts +124 -0
  28. package/dist/types/src/RenderTarget.d.ts +56 -0
  29. package/dist/types/src/Scene.d.ts +62 -0
  30. package/dist/types/src/ScreenEffects.d.ts +111 -0
  31. package/dist/types/src/Serializer.d.ts +86 -0
  32. package/dist/types/src/Shaders.d.ts +2 -0
  33. package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
  34. package/dist/types/src/SpatialGrid.d.ts +50 -0
  35. package/dist/types/src/SpriteBatch.d.ts +89 -0
  36. package/dist/types/src/StateMachine.d.ts +59 -0
  37. package/dist/types/src/Storage.d.ts +89 -0
  38. package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
  39. package/dist/types/src/TextureAtlas.d.ts +55 -0
  40. package/dist/types/src/Tilemap.d.ts +91 -0
  41. package/dist/types/src/Time.d.ts +53 -0
  42. package/dist/types/src/Timer.d.ts +54 -0
  43. package/dist/types/src/Transform.d.ts +94 -0
  44. package/dist/types/src/Tween.d.ts +81 -0
  45. package/dist/types/src/UI.d.ts +121 -0
  46. package/dist/types/src/components/Behaviour.d.ts +71 -0
  47. package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
  48. package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
  49. package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
  50. package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
  51. package/dist/types/src/components/Collider.d.ts +96 -0
  52. package/dist/types/src/components/GameObject.d.ts +135 -0
  53. package/dist/types/src/components/RigidBody.d.ts +183 -0
  54. package/dist/types/src/importers/Aseprite.d.ts +79 -0
  55. package/dist/types/src/importers/TiledMap.d.ts +62 -0
  56. package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
  57. package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
  58. package/dist/types/src/managers/AssetManager.d.ts +116 -0
  59. package/dist/types/src/managers/AudioManager.d.ts +259 -0
  60. package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
  61. package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
  62. package/dist/types/src/managers/GLManager.d.ts +84 -0
  63. package/dist/types/src/managers/GLState.d.ts +37 -0
  64. package/dist/types/src/managers/IDManager.d.ts +31 -0
  65. package/dist/types/src/managers/InputManager.d.ts +290 -0
  66. package/dist/types/src/managers/NetworkManager.d.ts +93 -0
  67. package/dist/types/src/managers/RenderStats.d.ts +34 -0
  68. package/dist/types/src/managers/SceneManager.d.ts +44 -0
  69. package/dist/types/src/managers/ShaderManager.d.ts +55 -0
  70. package/dist/types/src/managers/TextureManager.d.ts +102 -0
  71. package/dist/types/src/particlesystem/Particle.d.ts +64 -0
  72. package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
  73. package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
  74. package/index.js +72 -0
  75. package/package.json +74 -60
  76. package/src/Animator.js +95 -0
  77. package/src/BitmapText.js +6 -5
  78. package/src/Camera.js +183 -0
  79. package/src/CameraController.js +192 -0
  80. package/src/CanvasText.js +281 -0
  81. package/src/CollisionLayers.js +86 -0
  82. package/src/Color.js +18 -18
  83. package/src/Coroutine.js +259 -0
  84. package/src/DebugOverlay.js +246 -0
  85. package/src/Drawable.js +842 -555
  86. package/src/Easing.js +57 -0
  87. package/src/Emerald.js +1150 -459
  88. package/src/EmeraldDB.js +328 -0
  89. package/src/FPSCounter.js +43 -43
  90. package/src/GLUtils.js +60 -67
  91. package/src/Instance.js +41 -5
  92. package/src/InstancedTexture.js +251 -118
  93. package/src/Interpolator.js +124 -0
  94. package/src/Material.js +202 -0
  95. package/src/MathUtils.js +133 -0
  96. package/src/ParticleEmitter.js +284 -0
  97. package/src/Physics.js +186 -5
  98. package/src/Pool.js +85 -0
  99. package/src/PostEffects.js +296 -0
  100. package/src/PostProcessor.js +304 -0
  101. package/src/RenderTarget.js +134 -0
  102. package/src/Scene.js +115 -83
  103. package/src/ScreenEffects.js +266 -0
  104. package/src/Serializer.js +131 -0
  105. package/src/Shaders.js +150 -165
  106. package/src/Shapes.js +118 -129
  107. package/src/SpatialGrid.js +111 -0
  108. package/src/SpriteBatch.js +299 -0
  109. package/src/StateMachine.js +82 -0
  110. package/src/Storage.js +175 -47
  111. package/src/Texture.js +58 -67
  112. package/src/TextureAtlas.js +96 -0
  113. package/src/Tilemap.js +274 -0
  114. package/src/Time.js +51 -6
  115. package/src/Timer.js +99 -0
  116. package/src/Transform.js +100 -7
  117. package/src/Tween.js +160 -0
  118. package/src/UI.js +394 -0
  119. package/src/components/Behaviour.js +90 -0
  120. package/src/components/BoxCollider.js +22 -3
  121. package/src/components/CircleCollider.js +19 -3
  122. package/src/components/CircleColliderDebug.js +24 -24
  123. package/src/components/Collider.js +140 -34
  124. package/src/components/GameObject.js +130 -21
  125. package/src/components/RigidBody.js +123 -2
  126. package/src/importers/Aseprite.js +142 -0
  127. package/src/importers/TiledMap.js +158 -0
  128. package/src/lights/DirectionalLight.js +6 -15
  129. package/src/lights/PointLight.js +4 -4
  130. package/src/managers/AssetManager.js +239 -0
  131. package/src/managers/AudioManager.js +565 -146
  132. package/src/managers/EventManager.js +488 -477
  133. package/src/managers/GLManager.js +57 -0
  134. package/src/managers/GLState.js +70 -0
  135. package/src/managers/IDManager.js +24 -2
  136. package/src/managers/InputManager.js +779 -0
  137. package/src/managers/NetworkManager.js +178 -0
  138. package/src/managers/RenderStats.js +34 -0
  139. package/src/managers/SceneManager.js +30 -0
  140. package/src/managers/ShaderManager.js +0 -2
  141. package/src/managers/TextureManager.js +218 -0
  142. package/src/particlesystem/Particle.js +82 -7
  143. package/src/particlesystem/ParticleSettings.js +21 -3
  144. package/src/particlesystem/Particles.js +80 -31
  145. package/dist/types/Drawable.d.ts +0 -157
  146. package/dist/types/Emerald.d.ts +0 -73
  147. package/dist/types/GLUtils.d.ts +0 -6
  148. package/dist/types/Scene.d.ts +0 -39
  149. package/dist/types/Shaders.d.ts +0 -4
  150. package/dist/types/Storage.d.ts +0 -46
  151. package/dist/types/Time.d.ts +0 -22
  152. package/dist/types/Transform.d.ts +0 -41
  153. package/dist/types/components/BoxColliderDebug.d.ts +0 -19
  154. package/dist/types/components/CircleColliderDebug.d.ts +0 -19
  155. package/dist/types/components/Collider.d.ts +0 -53
  156. package/dist/types/components/GameObject.d.ts +0 -72
  157. package/dist/types/components/RigidBody.d.ts +0 -104
  158. package/dist/types/managers/AudioManager.d.ts +0 -60
  159. package/dist/types/managers/GLManager.d.ts +0 -47
  160. package/dist/types/managers/IDManager.d.ts +0 -21
  161. package/dist/types/managers/SceneManager.d.ts +0 -22
  162. package/dist/types/particlesystem/Particle.d.ts +0 -42
package/src/Tween.js ADDED
@@ -0,0 +1,160 @@
1
+ import Easing from "./Easing.js";
2
+
3
+ /**
4
+ * @class Tween
5
+ * @description Animates numeric properties of a target object over time with
6
+ * easing. Tweens created via `Tween.to(...)` are auto-registered and advanced
7
+ * by `Emerald.drawScene` (using time-scaled delta), so they respect pause/slow-mo.
8
+ *
9
+ * @example
10
+ * Tween.to(sprite.transform.position, { x: 200, y: -50 }, 0.6, {
11
+ * easing: Easing.outBack,
12
+ * onComplete: () => console.log("done"),
13
+ * });
14
+ */
15
+ class Tween {
16
+ /**
17
+ * @param {Object} target - The object whose numeric keys will be animated
18
+ * @param {Object} props - Map of key -> end value
19
+ * @param {number} duration - Duration in seconds
20
+ * @param {Object} [options] - { easing, delay, loop, yoyo, onUpdate, onComplete }
21
+ */
22
+ constructor(target, props, duration, options = {}) {
23
+ this.target = target;
24
+ this.props = props;
25
+ this.duration = Math.max(1e-4, duration);
26
+ this.easing = options.easing || Easing.linear;
27
+ this.delay = options.delay || 0;
28
+ this.loop = !!options.loop;
29
+ this.yoyo = !!options.yoyo;
30
+ this.onUpdate = options.onUpdate || null;
31
+ this.onComplete = options.onComplete || null;
32
+
33
+ this.elapsed = 0;
34
+ this.started = false;
35
+ this.done = false;
36
+ this.from = {};
37
+ }
38
+
39
+ /** @private */
40
+ _begin() {
41
+ for (const key in this.props) {
42
+ this.from[key] = this.target[key];
43
+ }
44
+ this.started = true;
45
+ }
46
+
47
+ /**
48
+ * @method update
49
+ * @description Advances the tween. Returns false when finished.
50
+ * @param {number} dt - Seconds since the last update
51
+ * @returns {boolean} - Whether the tween is still active
52
+ */
53
+ update(dt) {
54
+ if (this.done) return false;
55
+
56
+ if (this.delay > 0) {
57
+ this.delay -= dt;
58
+ if (this.delay > 0) return true;
59
+ dt = -this.delay;
60
+ this.delay = 0;
61
+ }
62
+
63
+ if (!this.started) this._begin();
64
+
65
+ this.elapsed += dt;
66
+ const t = Math.min(1, this.elapsed / this.duration);
67
+ const e = this.easing(t);
68
+
69
+ for (const key in this.props) {
70
+ this.target[key] =
71
+ this.from[key] + (this.props[key] - this.from[key]) * e;
72
+ }
73
+ if (this.onUpdate) this.onUpdate(this.target, t);
74
+
75
+ if (t >= 1) {
76
+ if (this.loop) {
77
+ this.elapsed = 0;
78
+ if (this.yoyo) {
79
+ const swap = this.from;
80
+ this.from = this.props;
81
+ this.props = swap;
82
+ } else {
83
+ for (const key in this.props) this.target[key] = this.from[key];
84
+ }
85
+ } else {
86
+ this.done = true;
87
+ if (this.onComplete) this.onComplete(this.target);
88
+ return false;
89
+ }
90
+ }
91
+ return true;
92
+ }
93
+
94
+ /**
95
+ * @method then
96
+ * @description Chains a callback to run when the tween completes.
97
+ * @param {Function} callback
98
+ * @returns {Tween} - this
99
+ */
100
+ then(callback) {
101
+ const prev = this.onComplete;
102
+ this.onComplete = (target) => {
103
+ if (prev) prev(target);
104
+ callback(target);
105
+ };
106
+ return this;
107
+ }
108
+
109
+ /**
110
+ * @method stop
111
+ * @description Stops the tween immediately (without firing onComplete).
112
+ */
113
+ stop() {
114
+ this.done = true;
115
+ }
116
+ }
117
+
118
+ Tween.active = [];
119
+
120
+ /**
121
+ * @method Tween.to
122
+ * @description Creates and registers a tween.
123
+ * @returns {Tween}
124
+ */
125
+ Tween.to = function (target, props, duration, options) {
126
+ const tween = new Tween(target, props, duration, options);
127
+ Tween.active.push(tween);
128
+ return tween;
129
+ };
130
+
131
+ /**
132
+ * @method Tween.update
133
+ * @description Advances all active tweens. Driven by Emerald.drawScene.
134
+ * @param {number} dt - Seconds since the last frame
135
+ */
136
+ Tween.update = function (dt) {
137
+ for (let i = Tween.active.length - 1; i >= 0; i--) {
138
+ if (!Tween.active[i].update(dt)) {
139
+ Tween.active.splice(i, 1);
140
+ }
141
+ }
142
+ };
143
+
144
+ /**
145
+ * @method Tween.killOf
146
+ * @description Removes all tweens targeting the given object.
147
+ */
148
+ Tween.killOf = function (target) {
149
+ Tween.active = Tween.active.filter((tween) => tween.target !== target);
150
+ };
151
+
152
+ /**
153
+ * @method Tween.killAll
154
+ * @description Removes every active tween.
155
+ */
156
+ Tween.killAll = function () {
157
+ Tween.active.length = 0;
158
+ };
159
+
160
+ export default Tween;
package/src/UI.js ADDED
@@ -0,0 +1,394 @@
1
+ import GameObject from "./components/GameObject.js";
2
+ import { Square2D } from "./Shapes.js";
3
+ import CanvasText from "./CanvasText.js";
4
+ import Camera from "./Camera.js";
5
+ import Color from "./Color.js";
6
+ import { Vector2, Vector3 } from "./Physics.js";
7
+
8
+ /**
9
+ * @class UI
10
+ * @description A small retained-mode UI toolkit rendered entirely with the
11
+ * engine (no DOM/HTML overlay): panels, labels, buttons and text fields, drawn
12
+ * as screen-space objects on a dedicated layer with their own pointer/keyboard
13
+ * hit-testing. Positions are in pixels from the viewport center (y up), or a
14
+ * `(viewW, viewH) => ({x, y})` function for responsive anchoring; call
15
+ * `relayout()` on resize.
16
+ *
17
+ * Pair it with a UI camera (see {@link UI.createCamera}) so the UI draws over
18
+ * the game and the game cameras ignore the UI layer.
19
+ *
20
+ * @example
21
+ * const uiCam = UI.createCamera(); // onlyLayers = [UI layer]
22
+ * gameCamera.ignoreLayer(UI.LAYER);
23
+ * emerald.setCameras([gameCamera, uiCam]);
24
+ *
25
+ * const ui = new UI(scene, canvas);
26
+ * ui.label(() => ({ x: 0, y: 200 }), "PAUSED", { font: "700 48px sans-serif" });
27
+ * ui.button(() => ({ x: 0, y: 0 }), "Resume", 220, 56, { onClick: resume });
28
+ * // in the loop: ui.relayout() on resize; remove input via ui.destroy().
29
+ */
30
+ class UI {
31
+ /**
32
+ * @param {Scene} scene - Scene to add UI objects to
33
+ * @param {HTMLCanvasElement} canvas - Canvas for pointer coordinates
34
+ * @param {Object} [options] - { layer = UI.LAYER, accent = [110,180,255] }
35
+ */
36
+ constructor(scene, canvas, options = {}) {
37
+ this.scene = scene;
38
+ this.canvas = canvas;
39
+ this.layer = options.layer ?? UI.LAYER;
40
+ this.accent = options.accent || [110, 180, 255];
41
+
42
+ this.elements = [];
43
+ this.focused = null;
44
+ /** @private */
45
+ this._hover = null;
46
+
47
+ /** @private */
48
+ this._onMove = (e) => this._handleMove(e);
49
+ /** @private */
50
+ this._onDown = (e) => this._handleDown(e);
51
+ /** @private */
52
+ this._onKey = (e) => this._handleKey(e);
53
+ canvas.addEventListener("mousemove", this._onMove);
54
+ canvas.addEventListener("mousedown", this._onDown);
55
+ window.addEventListener("keydown", this._onKey);
56
+ }
57
+
58
+ /** @private */
59
+ _resolvePos(pos) {
60
+ const vw = this.canvas.clientWidth || this.canvas.width;
61
+ const vh = this.canvas.clientHeight || this.canvas.height;
62
+ return typeof pos === "function" ? pos(vw, vh) : pos;
63
+ }
64
+
65
+ /**
66
+ * @method relayout
67
+ * @description Recomputes positions of all elements (call after a resize).
68
+ */
69
+ relayout() {
70
+ for (const el of this.elements) {
71
+ const p = this._resolvePos(el.pos);
72
+ el.x = p.x;
73
+ el.y = p.y;
74
+ el.go.transform.position.x = p.x;
75
+ el.go.transform.position.y = p.y;
76
+ if (el.label) {
77
+ el.label.transform.position.x = p.x;
78
+ el.label.transform.position.y = p.y;
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * @method panel
85
+ * @description Adds a rectangular panel.
86
+ * @returns {Object} - Element handle
87
+ */
88
+ panel(pos, width, height, options = {}) {
89
+ const color = options.color || new Color(20, 24, 34, 235);
90
+ const go = this._makeQuad(pos, width, height, color, options.z ?? 0);
91
+ if (options.opacity != null) go.setOpacity(options.opacity);
92
+ const el = { type: "panel", go, pos, w: width, h: height };
93
+ return this._register(el, pos);
94
+ }
95
+
96
+ /**
97
+ * @method dim
98
+ * @description Adds a full-screen dimmer (modal backdrop).
99
+ * @returns {Object} - Element handle
100
+ */
101
+ dim(opacity = 0.6) {
102
+ const go = this._makeQuad(
103
+ { x: 0, y: 0 },
104
+ 20000,
105
+ 20000,
106
+ new Color(0, 0, 0, 255),
107
+ -1
108
+ );
109
+ go.setOpacity(opacity);
110
+ const el = { type: "dim", go, pos: { x: 0, y: 0 }, w: 20000, h: 20000 };
111
+ return this._register(el, { x: 0, y: 0 });
112
+ }
113
+
114
+ /**
115
+ * @method label
116
+ * @description Adds a text label.
117
+ * @returns {Object} - Element handle with setText(text)
118
+ */
119
+ label(pos, text, options = {}) {
120
+ const go = CanvasText.create(text, {
121
+ font: options.font || "500 18px system-ui, sans-serif",
122
+ color: options.color || "#e8eefc",
123
+ screenSpace: true,
124
+ });
125
+ go.setLayer(this.layer);
126
+ go.alwaysVisible = true;
127
+ go.transform.position.z = options.z ?? 1;
128
+ this.scene.add(go);
129
+ const text2d = go.getComponent(CanvasText);
130
+ const el = {
131
+ type: "label",
132
+ go,
133
+ pos,
134
+ get w() {
135
+ return text2d.width;
136
+ },
137
+ get h() {
138
+ return text2d.height;
139
+ },
140
+ setText: (t) => text2d.setText(t),
141
+ };
142
+ return this._register(el, pos);
143
+ }
144
+
145
+ /**
146
+ * @method button
147
+ * @description Adds an interactive button (panel + centered label).
148
+ * @param {Object} [options] - { onClick, accent, font, color }
149
+ * @returns {Object} - Element handle with setText(text), setEnabled(bool)
150
+ */
151
+ button(pos, text, width, height, options = {}) {
152
+ const accent = options.accent || this.accent;
153
+ const base = new Color(28, 34, 48, 245);
154
+ const go = this._makeQuad(pos, width, height, base, options.z ?? 0);
155
+
156
+ const label = CanvasText.create(text, {
157
+ font: options.font || "600 18px system-ui, sans-serif",
158
+ color: "#f2f6ff",
159
+ screenSpace: true,
160
+ });
161
+ label.setLayer(this.layer);
162
+ label.alwaysVisible = true;
163
+ label.transform.position.z = (options.z ?? 0) + 1;
164
+ this.scene.add(label);
165
+
166
+ const shape = go.getComponent(Square2D);
167
+ const el = {
168
+ type: "button",
169
+ go,
170
+ label,
171
+ pos,
172
+ w: width,
173
+ h: height,
174
+ enabled: true,
175
+ onClick: options.onClick,
176
+ _base: base,
177
+ _hover: new Color(accent[0], accent[1], accent[2], 255),
178
+ _shape: shape,
179
+ setText: (t) => label.getComponent(CanvasText).setText(t),
180
+ setEnabled: (v) => {
181
+ el.enabled = v;
182
+ go.setOpacity(v ? 1 : 0.45);
183
+ },
184
+ };
185
+ return this._register(el, pos);
186
+ }
187
+
188
+ /**
189
+ * @method textField
190
+ * @description Adds an editable single-line text field.
191
+ * @param {Object} [options] - { value, placeholder, onChange, maxLength, font }
192
+ * @returns {Object} - Element handle with getValue()/setValue(text)
193
+ */
194
+ textField(pos, width, height, options = {}) {
195
+ const go = this._makeQuad(
196
+ pos,
197
+ width,
198
+ height,
199
+ new Color(12, 16, 24, 250),
200
+ options.z ?? 0
201
+ );
202
+ const label = CanvasText.create(
203
+ options.value || options.placeholder || "",
204
+ {
205
+ font: options.font || "500 18px system-ui, sans-serif",
206
+ color: options.value ? "#e8eefc" : "#7c8aa0",
207
+ screenSpace: true,
208
+ }
209
+ );
210
+ label.setLayer(this.layer);
211
+ label.alwaysVisible = true;
212
+ label.transform.position.z = (options.z ?? 0) + 1;
213
+ this.scene.add(label);
214
+
215
+ const el = {
216
+ type: "textField",
217
+ go,
218
+ label,
219
+ pos,
220
+ w: width,
221
+ h: height,
222
+ value: options.value || "",
223
+ placeholder: options.placeholder || "",
224
+ maxLength: options.maxLength || 64,
225
+ onChange: options.onChange,
226
+ _render: () => {
227
+ const ct = label.getComponent(CanvasText);
228
+ const showing = el.value || el.placeholder;
229
+ ct.setColor(el.value ? "#e8eefc" : "#7c8aa0");
230
+ ct.setText(showing + (this.focused === el ? "|" : ""));
231
+ },
232
+ getValue: () => el.value,
233
+ setValue: (t) => {
234
+ el.value = t;
235
+ el._render();
236
+ },
237
+ };
238
+ return this._register(el, pos);
239
+ }
240
+
241
+ /** @private */
242
+ _makeQuad(pos, width, height, color, z) {
243
+ const go = new GameObject(
244
+ "UIQuad",
245
+ new Vector3(0, 0, z),
246
+ 0,
247
+ new Vector2(width / 2, height / 2)
248
+ );
249
+ const shape = new Square2D();
250
+ shape.setColor(color);
251
+ shape.setUseLighting(false);
252
+ go.addComponent(shape);
253
+ go.setScreenSpace(true);
254
+ go.setLayer(this.layer);
255
+ go.alwaysVisible = true;
256
+ this.scene.add(go);
257
+ const p = this._resolvePos(pos);
258
+ go.transform.position.x = p.x;
259
+ go.transform.position.y = p.y;
260
+ return go;
261
+ }
262
+
263
+ /** @private */
264
+ _register(el, pos) {
265
+ const p = this._resolvePos(pos);
266
+ el.x = p.x;
267
+ el.y = p.y;
268
+ this.elements.push(el);
269
+ return el;
270
+ }
271
+
272
+ /** @private */
273
+ _toScreen(clientX, clientY) {
274
+ const rect = this.canvas.getBoundingClientRect();
275
+ const cssW = rect.width;
276
+ const cssH = rect.height;
277
+ return {
278
+ x: clientX - rect.left - cssW / 2,
279
+ y: -(clientY - rect.top - cssH / 2),
280
+ };
281
+ }
282
+
283
+ /** @private */
284
+ _hitTest(sx, sy) {
285
+ for (let i = this.elements.length - 1; i >= 0; i--) {
286
+ const el = this.elements[i];
287
+ if (el.type !== "button" && el.type !== "textField") continue;
288
+ if (el.enabled === false) continue;
289
+ const x = el.x;
290
+ const y = el.y;
291
+ if (Math.abs(sx - x) <= el.w / 2 && Math.abs(sy - y) <= el.h / 2) {
292
+ return el;
293
+ }
294
+ }
295
+ return null;
296
+ }
297
+
298
+ /**
299
+ * @method isOver
300
+ * @description Whether an interactive element is under the given page point —
301
+ * useful to suppress game clicks behind the UI.
302
+ */
303
+ isOver(clientX, clientY) {
304
+ const s = this._toScreen(clientX, clientY);
305
+ return this._hitTest(s.x, s.y) != null;
306
+ }
307
+
308
+ /** @private */
309
+ _handleMove(e) {
310
+ const s = this._toScreen(e.clientX, e.clientY);
311
+ const hit = this._hitTest(s.x, s.y);
312
+ if (hit === this._hover) return;
313
+ if (this._hover && this._hover.type === "button") {
314
+ this._hover._shape.setColor(this._hover._base);
315
+ }
316
+ this._hover = hit;
317
+ if (hit && hit.type === "button") {
318
+ hit._shape.setColor(hit._hover);
319
+ this.canvas.style.cursor = "pointer";
320
+ } else {
321
+ this.canvas.style.cursor = "default";
322
+ }
323
+ }
324
+
325
+ /** @private */
326
+ _handleDown(e) {
327
+ const s = this._toScreen(e.clientX, e.clientY);
328
+ const hit = this._hitTest(s.x, s.y);
329
+
330
+ if (this.focused && this.focused !== hit) {
331
+ const prev = this.focused;
332
+ this.focused = null;
333
+ if (prev._render) prev._render();
334
+ }
335
+
336
+ if (!hit) return;
337
+ if (hit.type === "button") {
338
+ if (typeof hit.onClick === "function") hit.onClick(e, hit);
339
+ } else if (hit.type === "textField") {
340
+ this.focused = hit;
341
+ hit._render();
342
+ }
343
+ }
344
+
345
+ /** @private */
346
+ _handleKey(e) {
347
+ if (!this.focused || this.focused.type !== "textField") return;
348
+ const el = this.focused;
349
+ if (e.key === "Backspace") {
350
+ el.value = el.value.slice(0, -1);
351
+ e.preventDefault();
352
+ } else if (e.key === "Enter" || e.key === "Escape") {
353
+ this.focused = null;
354
+ } else if (e.key.length === 1 && el.value.length < el.maxLength) {
355
+ el.value += e.key;
356
+ e.preventDefault();
357
+ } else {
358
+ return;
359
+ }
360
+ el._render();
361
+ if (typeof el.onChange === "function") el.onChange(el.value);
362
+ }
363
+
364
+ /**
365
+ * @method destroy
366
+ * @description Removes all UI objects and detaches input listeners.
367
+ */
368
+ destroy() {
369
+ for (const el of this.elements) {
370
+ this.scene.remove(el.go);
371
+ if (el.label) this.scene.remove(el.label);
372
+ }
373
+ this.elements.length = 0;
374
+ this.canvas.removeEventListener("mousemove", this._onMove);
375
+ this.canvas.removeEventListener("mousedown", this._onDown);
376
+ window.removeEventListener("keydown", this._onKey);
377
+ this.canvas.style.cursor = "default";
378
+ }
379
+
380
+ /**
381
+ * @method createCamera
382
+ * @description Returns a full-screen camera that renders only the UI layer,
383
+ * so UI draws over the game. Add it last in setCameras.
384
+ * @param {number} [layer] - The UI layer (defaults to UI.LAYER)
385
+ * @returns {Camera}
386
+ */
387
+ static createCamera(layer = UI.LAYER) {
388
+ return new Camera({ onlyLayers: [layer] });
389
+ }
390
+ }
391
+
392
+ UI.LAYER = 100000;
393
+
394
+ export default UI;
@@ -0,0 +1,90 @@
1
+ import IDManager from "../managers/IDManager.js";
2
+
3
+ /**
4
+ * @class Behaviour
5
+ * @description Base class for custom per-object game logic. Subclass it and
6
+ * override `start()` (called once, the first update after being added) and
7
+ * `update(deltaTime)` (called every frame the owner is active). Add it to a
8
+ * GameObject like any other component; it is ticked by `Scene.update(dt)` via
9
+ * `GameObject.update(dt)`.
10
+ *
11
+ * @example
12
+ * class Spinner extends Behaviour {
13
+ * start() { this.speed = 2; }
14
+ * update(dt) { this.gameObject.transform.rotation += this.speed * dt; }
15
+ * }
16
+ * obj.addComponent(new Spinner());
17
+ */
18
+ class Behaviour {
19
+ constructor() {
20
+ this.id = IDManager.generateUniqueID();
21
+ this.parentObject = null;
22
+ this.enabled = true;
23
+ /** @private */
24
+ this._started = false;
25
+ }
26
+
27
+ /**
28
+ * @member gameObject
29
+ * @description Convenience accessor for the owning GameObject.
30
+ */
31
+ get gameObject() {
32
+ return this.parentObject;
33
+ }
34
+
35
+ /**
36
+ * @method setParent
37
+ * @description Sets the owning object. Called automatically by addComponent.
38
+ * @param {Object} parent - The owning GameObject (or Instance)
39
+ */
40
+ setParent(parent) {
41
+ this.parentObject = parent;
42
+ }
43
+
44
+ /**
45
+ * @method getParent
46
+ * @description Returns the owning object
47
+ * @returns {Object} - The owning object
48
+ */
49
+ getParent() {
50
+ return this.parentObject;
51
+ }
52
+
53
+ /**
54
+ * @method start
55
+ * @description Called once before the first update. Override in subclasses.
56
+ */
57
+ start() {}
58
+
59
+ /**
60
+ * @method update
61
+ * @description Called every active frame. Override in subclasses.
62
+ * @param {number} deltaTime - Seconds since the previous frame (time-scaled)
63
+ */
64
+ update(deltaTime) {}
65
+
66
+ /**
67
+ * @method onDestroy
68
+ * @description Called when the behaviour is removed. Override for cleanup.
69
+ */
70
+ onDestroy() {}
71
+
72
+ /**
73
+ * @method onCollisionEnter
74
+ * @description Called when the owner's body starts touching another. Requires
75
+ * the Physics world to be ticked. Override in subclasses.
76
+ * @param {Object} other - The other GameObject/Instance (or null)
77
+ * @param {Object} contact - The planck contact
78
+ */
79
+ onCollisionEnter(other, contact) {}
80
+
81
+ /**
82
+ * @method onCollisionExit
83
+ * @description Called when the owner's body stops touching another.
84
+ * @param {Object} other - The other GameObject/Instance (or null)
85
+ * @param {Object} contact - The planck contact
86
+ */
87
+ onCollisionExit(other, contact) {}
88
+ }
89
+
90
+ export default Behaviour;
@@ -22,7 +22,8 @@ class BoxCollider extends Collider {
22
22
  friction,
23
23
  restitution,
24
24
  isSensor = false,
25
- parentObject = null
25
+ parentObject = null,
26
+ filter = null
26
27
  ) {
27
28
  super(rigidbody, isSensor, parentObject);
28
29
  this.collider = rigidbody
@@ -36,7 +37,21 @@ class BoxCollider extends Collider {
36
37
  this.fixtureSize = fixtureSize;
37
38
  this.rigidbody.setCollider(this);
38
39
  this.name = "BoxCollider" + this.id;
39
- this.debugShape = new BoxColliderDebug(this.rigidbody);
40
+ this._applyFilterSpec(filter);
41
+ }
42
+
43
+ /**
44
+ * @method debugShape
45
+ * @description Lazily builds the debug visualization on first access, so a
46
+ * collider stays free of any GameObject/GL allocation until it is debugged.
47
+ * @returns {BoxColliderDebug} - The debug shape
48
+ */
49
+ get debugShape() {
50
+ if (!this._debugShape) {
51
+ /** @private */
52
+ this._debugShape = new BoxColliderDebug(this.rigidbody);
53
+ }
54
+ return this._debugShape;
40
55
  }
41
56
 
42
57
  /**
@@ -55,7 +70,11 @@ class BoxCollider extends Collider {
55
70
  * @description Hides the debug shape of the collider
56
71
  */
57
72
  hideDebugShape() {
58
- SceneManager.getScene().remove(this.debugShape.gameObject);
73
+ if (!this._debugShape) return;
74
+ var scene = SceneManager.getScene();
75
+ if (scene) {
76
+ scene.remove(this._debugShape.gameObject);
77
+ }
59
78
  }
60
79
 
61
80
  /**