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/Tilemap.js ADDED
@@ -0,0 +1,274 @@
1
+ import GameObject from "./components/GameObject.js";
2
+ import InstancedTexture from "./InstancedTexture.js";
3
+ import Instance from "./Instance.js";
4
+ import RigidBody from "./components/RigidBody.js";
5
+ import BoxCollider from "./components/BoxCollider.js";
6
+ import { Vector2, Vector3 } from "./Physics.js";
7
+
8
+ /**
9
+ * @class Tilemap
10
+ * @description Builds a grid of tiles from a 2D array of frame indices, rendered
11
+ * as a single instanced draw call. Use -1 (or null) for empty cells. The tiles
12
+ * live on `tilemap.gameObject`, which you add to the scene.
13
+ *
14
+ * @example
15
+ * const map = new Tilemap("level", "tiles.png", {
16
+ * tileSize: 32, frameWidth: 16, frameHeight: 16, framesPerRow: 4, totalFrames: 16,
17
+ * });
18
+ * map.setMap([
19
+ * [0, 0, 0, 0],
20
+ * [1, -1, -1, 1],
21
+ * [2, 2, 2, 2],
22
+ * ]);
23
+ * scene.add(map.gameObject);
24
+ */
25
+ class Tilemap {
26
+ /**
27
+ * @param {string} name - Name for the underlying GameObject
28
+ * @param {string} texturePath - Path to the tile sheet
29
+ * @param {Object} [options] - tileSize, frameWidth, frameHeight, framesPerRow, totalFrames, pixelart
30
+ */
31
+ constructor(name, texturePath, options = {}) {
32
+ const {
33
+ tileSize = 32,
34
+ frameWidth = tileSize,
35
+ frameHeight = tileSize,
36
+ framesPerRow = 1,
37
+ totalFrames = 1,
38
+ pixelart = true,
39
+ } = options;
40
+
41
+ this.name = name;
42
+ this.tileSize = tileSize;
43
+ /** @private */
44
+ this._config = {
45
+ texturePath,
46
+ frameWidth,
47
+ frameHeight,
48
+ framesPerRow,
49
+ totalFrames,
50
+ pixelart,
51
+ };
52
+
53
+ this.gameObject = new GameObject(
54
+ name,
55
+ new Vector3(0, 0, 0),
56
+ 0,
57
+ new Vector2(1, 1)
58
+ );
59
+ this.texture = null;
60
+
61
+ /** @private */
62
+ this._map = null;
63
+ /** @private */
64
+ this._layout = null;
65
+ /** @private */
66
+ this._colliders = [];
67
+ }
68
+
69
+ /**
70
+ * @method setMap
71
+ * @description Builds (or rebuilds) the tile instances from a 2D array.
72
+ * @param {number[][]} map - Rows of frame indices (-1/null = empty)
73
+ * @param {Object} [options] - { originX = 0, originY = 0, flipY = true }
74
+ * @returns {Tilemap} - this
75
+ */
76
+ setMap(map, options = {}) {
77
+ const { originX = 0, originY = 0, flipY = true } = options;
78
+ const rows = map.length;
79
+ const cols = map.reduce(
80
+ (max, row) => Math.max(max, row ? row.length : 0),
81
+ 0
82
+ );
83
+ const count = Math.max(1, rows * cols);
84
+
85
+ this._map = map;
86
+ this._layout = { originX, originY, flipY, rows, cols };
87
+
88
+ const existing = this.gameObject.getComponent(InstancedTexture);
89
+ if (existing) this.gameObject.removeComponent(existing);
90
+
91
+ this.texture = new InstancedTexture(
92
+ this._config.texturePath,
93
+ count,
94
+ this._config.frameWidth,
95
+ this._config.frameHeight,
96
+ this._config.framesPerRow,
97
+ this._config.totalFrames,
98
+ 0,
99
+ false,
100
+ this._config.pixelart
101
+ );
102
+ this.texture.clearInstances();
103
+
104
+ const half = this.tileSize / 2;
105
+ for (let r = 0; r < rows; r++) {
106
+ const row = map[r];
107
+ if (!row) continue;
108
+ for (let c = 0; c < row.length; c++) {
109
+ const frame = row[c];
110
+ if (frame == null || frame < 0) continue;
111
+ const rowIndex = flipY ? rows - 1 - r : r;
112
+ const x = originX + c * this.tileSize + half;
113
+ const y = originY + rowIndex * this.tileSize + half;
114
+ this.texture.addInstance(
115
+ new Instance(
116
+ `${this.name}_${r}_${c}`,
117
+ new Vector3(x, y, 0),
118
+ new Vector2(half, half),
119
+ 0,
120
+ frame
121
+ )
122
+ );
123
+ }
124
+ }
125
+
126
+ this.texture.setStatic(true);
127
+ this.gameObject.addComponent(this.texture);
128
+ return this;
129
+ }
130
+
131
+ /**
132
+ * @method buildColliders
133
+ * @description Generates static physics colliders from the current map. Solid
134
+ * cells are merged greedily into horizontal runs, so a row of N tiles becomes
135
+ * one box collider instead of N — far fewer bodies for the physics engine.
136
+ * The bodies are tagged so collision callbacks resolve back to `ownerObject`
137
+ * (defaults to the tilemap's GameObject). Call again after setMap to rebuild.
138
+ *
139
+ * @param {Physics} physics - The physics engine
140
+ * @param {Object} [options] - { isSolid, friction = 0.2, restitution = 0,
141
+ * density = 0, ownerObject }
142
+ * @returns {Tilemap} - this
143
+ */
144
+ buildColliders(physics, options = {}) {
145
+ if (!this._map || !this._layout) return this;
146
+ const {
147
+ isSolid = (frame) => frame != null && frame >= 0,
148
+ friction = 0.2,
149
+ restitution = 0,
150
+ density = 0,
151
+ ownerObject = this.gameObject,
152
+ } = options;
153
+
154
+ this.clearColliders();
155
+
156
+ const { originX, originY, flipY, rows } = this._layout;
157
+ const ts = this.tileSize;
158
+ const half = ts / 2;
159
+ const scale = physics.getScale();
160
+
161
+ for (let r = 0; r < rows; r++) {
162
+ const row = this._map[r];
163
+ if (!row) continue;
164
+ let c = 0;
165
+ while (c < row.length) {
166
+ if (!isSolid(row[c])) {
167
+ c++;
168
+ continue;
169
+ }
170
+ let runStart = c;
171
+ while (c < row.length && isSolid(row[c])) c++;
172
+ const runLen = c - runStart;
173
+
174
+ const rowIndex = flipY ? rows - 1 - r : r;
175
+ const runWidth = runLen * ts;
176
+ const centerX = originX + runStart * ts + runWidth / 2;
177
+ const centerY = originY + rowIndex * ts + half;
178
+
179
+ const body = new RigidBody(
180
+ physics,
181
+ "static",
182
+ new Vector2(centerX, centerY),
183
+ true,
184
+ ownerObject,
185
+ new Vector2(0, 0)
186
+ );
187
+ new BoxCollider(
188
+ body,
189
+ new Vector2(runWidth / 2 / scale, half / scale),
190
+ density,
191
+ friction,
192
+ restitution,
193
+ false,
194
+ ownerObject
195
+ );
196
+ this._colliders.push(body);
197
+ }
198
+ }
199
+ return this;
200
+ }
201
+
202
+ /**
203
+ * @method clearColliders
204
+ * @description Destroys the physics bodies created by buildColliders.
205
+ * @returns {Tilemap} - this
206
+ */
207
+ clearColliders() {
208
+ for (const body of this._colliders) {
209
+ if (body && typeof body.destroy === "function") body.destroy();
210
+ }
211
+ this._colliders = [];
212
+ return this;
213
+ }
214
+
215
+ /**
216
+ * @method setAutoTiledMap
217
+ * @description Convenience: computes frame indices from a solidity grid using
218
+ * bitmask auto-tiling, then builds the map. See Tilemap.computeAutoTile.
219
+ * @param {Array<Array<boolean|number>>} solidGrid - Truthy = solid cell
220
+ * @param {Object} [options] - Auto-tile options + setMap options
221
+ * @returns {Tilemap} - this
222
+ */
223
+ setAutoTiledMap(solidGrid, options = {}) {
224
+ const frames = Tilemap.computeAutoTile(solidGrid, options);
225
+ return this.setMap(frames, options);
226
+ }
227
+
228
+ /**
229
+ * @method computeAutoTile
230
+ * @description Pure helper that converts a 2D solidity grid into a 2D frame
231
+ * index grid using 4-bit edge bitmasking. For each solid cell the neighbor
232
+ * mask is built as up|right|down|left (bits 1,2,4,8); empty cells become -1.
233
+ * Provide `frames` (a length-16 lookup from mask -> frame index) to match your
234
+ * tilesheet layout; otherwise the mask itself is used as the frame index, and
235
+ * `base` is added to every solid frame.
236
+ *
237
+ * @param {Array<Array<boolean|number>>} grid - Truthy = solid
238
+ * @param {Object} [options] - { frames, base = 0, edgesSolid = true }
239
+ * @returns {number[][]} - Frame indices (-1 for empty)
240
+ */
241
+ static computeAutoTile(grid, options = {}) {
242
+ const { frames = null, base = 0, edgesSolid = true } = options;
243
+ const rows = grid.length;
244
+ const out = [];
245
+ const solidAt = (r, c) => {
246
+ if (r < 0 || c < 0 || r >= rows || !grid[r] || c >= grid[r].length) {
247
+ return edgesSolid;
248
+ }
249
+ return !!grid[r][c];
250
+ };
251
+
252
+ for (let r = 0; r < rows; r++) {
253
+ const row = grid[r] || [];
254
+ const outRow = [];
255
+ for (let c = 0; c < row.length; c++) {
256
+ if (!row[c]) {
257
+ outRow.push(-1);
258
+ continue;
259
+ }
260
+ let mask = 0;
261
+ if (solidAt(r - 1, c)) mask |= 1;
262
+ if (solidAt(r, c + 1)) mask |= 2;
263
+ if (solidAt(r + 1, c)) mask |= 4;
264
+ if (solidAt(r, c - 1)) mask |= 8;
265
+ const frame = frames ? frames[mask] : mask;
266
+ outRow.push(base + (frame == null ? 0 : frame));
267
+ }
268
+ out.push(outRow);
269
+ }
270
+ return out;
271
+ }
272
+ }
273
+
274
+ export default Tilemap;
package/src/Time.js CHANGED
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * @class Time
3
- * @description Represents the time
4
- * @param {number} deltaTime - The delta time
3
+ * @description Global time state for the game. `deltaTime` is the (scaled)
4
+ * seconds since the last frame, `elapsedTime` accumulates total scaled time, and
5
+ * `timeScale` lets you slow down / speed up / pause the simulation.
5
6
  */
6
7
  class Time {
7
8
  /**
8
9
  * @method getDeltaTime
9
- * @description Returns the delta time
10
+ * @description Returns the (time-scaled) delta time in seconds
10
11
  * @returns {number} - The delta time
11
12
  */
12
13
  static getDeltaTime() {
@@ -15,14 +16,58 @@ class Time {
15
16
 
16
17
  /**
17
18
  * @method setDeltaTime
18
- * @description Sets the delta time
19
- * @param {number} deltaTime - The delta time
19
+ * @description Sets the delta time. The stored value is multiplied by
20
+ * `timeScale`, and `elapsedTime` is advanced by the scaled amount. Called by
21
+ * `Emerald.drawScene`, but can be called manually for custom loops.
22
+ * @param {number} deltaTime - The unscaled delta time in seconds
20
23
  */
21
24
  static setDeltaTime(deltaTime) {
22
- Time.deltaTime = deltaTime;
25
+ const scaled = deltaTime * Time.timeScale;
26
+ Time.unscaledDeltaTime = deltaTime;
27
+ Time.deltaTime = scaled;
28
+ Time.elapsedTime += scaled;
29
+ }
30
+
31
+ /**
32
+ * @method getUnscaledDeltaTime
33
+ * @description Returns the raw delta time, ignoring timeScale
34
+ * @returns {number} - The unscaled delta time
35
+ */
36
+ static getUnscaledDeltaTime() {
37
+ return Time.unscaledDeltaTime;
38
+ }
39
+
40
+ /**
41
+ * @method getElapsedTime
42
+ * @description Returns the total accumulated (scaled) time in seconds
43
+ * @returns {number} - The elapsed time
44
+ */
45
+ static getElapsedTime() {
46
+ return Time.elapsedTime;
47
+ }
48
+
49
+ /**
50
+ * @method setTimeScale
51
+ * @description Sets the time scale (1 = normal, 0 = paused, 2 = double speed)
52
+ * @param {number} scale - The time scale (clamped to >= 0)
53
+ */
54
+ static setTimeScale(scale) {
55
+ Time.timeScale = Math.max(0, scale);
56
+ }
57
+
58
+ /**
59
+ * @method getTimeScale
60
+ * @description Returns the current time scale
61
+ * @returns {number} - The time scale
62
+ */
63
+ static getTimeScale() {
64
+ return Time.timeScale;
23
65
  }
24
66
  }
25
67
 
26
68
  Time.deltaTime = 0;
69
+ Time.unscaledDeltaTime = 0;
70
+ Time.elapsedTime = 0;
71
+ Time.timeScale = 1;
27
72
 
28
73
  export default Time;
package/src/Timer.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @class Timer
3
+ * @description Schedules delayed and repeating callbacks measured in seconds.
4
+ * Driven by `Emerald.drawScene` with time-scaled delta, so timers honor
5
+ * pause/slow-mo. Each scheduler call returns a handle you can pass to `clear`.
6
+ *
7
+ * @example
8
+ * Timer.after(2, () => spawnEnemy()); // once, after 2s
9
+ * Timer.every(0.5, () => tick(), 10); // 10 times, every 0.5s
10
+ * const h = Timer.every(1, () => poll()); // forever until cleared
11
+ * Timer.clear(h);
12
+ */
13
+ class Timer {
14
+ /**
15
+ * @method after
16
+ * @description Runs a callback once after a delay.
17
+ * @param {number} seconds - Delay in seconds
18
+ * @param {Function} callback
19
+ * @returns {Object} - A handle for clear()
20
+ */
21
+ static after(seconds, callback) {
22
+ return Timer._add(seconds, callback, 1);
23
+ }
24
+
25
+ /**
26
+ * @method every
27
+ * @description Runs a callback repeatedly on an interval.
28
+ * @param {number} seconds - Interval in seconds
29
+ * @param {Function} callback
30
+ * @param {number} [repeats] - Number of repeats (default Infinity)
31
+ * @returns {Object} - A handle for clear()
32
+ */
33
+ static every(seconds, callback, repeats = Infinity) {
34
+ return Timer._add(seconds, callback, repeats);
35
+ }
36
+
37
+ /** @private */
38
+ static _add(interval, callback, repeats) {
39
+ const entry = {
40
+ interval: Math.max(0, interval),
41
+ callback,
42
+ repeats,
43
+ elapsed: 0,
44
+ cancelled: false,
45
+ };
46
+ Timer.entries.push(entry);
47
+ return entry;
48
+ }
49
+
50
+ /**
51
+ * @method clear
52
+ * @description Cancels a scheduled timer by its handle.
53
+ * @param {Object} handle - The handle returned by after/every
54
+ */
55
+ static clear(handle) {
56
+ if (handle) handle.cancelled = true;
57
+ }
58
+
59
+ /**
60
+ * @method clearAll
61
+ * @description Cancels all timers.
62
+ */
63
+ static clearAll() {
64
+ Timer.entries.length = 0;
65
+ }
66
+
67
+ /**
68
+ * @method update
69
+ * @description Advances all timers. Driven by Emerald.drawScene.
70
+ * @param {number} dt - Seconds since the last frame
71
+ */
72
+ static update(dt) {
73
+ const entries = Timer.entries;
74
+ for (let i = entries.length - 1; i >= 0; i--) {
75
+ const entry = entries[i];
76
+ if (entry.cancelled) {
77
+ entries.splice(i, 1);
78
+ continue;
79
+ }
80
+
81
+ entry.elapsed += dt;
82
+ while (!entry.cancelled && entry.elapsed >= entry.interval) {
83
+ entry.elapsed -= entry.interval;
84
+ entry.repeats -= 1;
85
+ entry.callback();
86
+ if (entry.repeats <= 0) {
87
+ entry.cancelled = true;
88
+ }
89
+ if (entry.interval === 0) break;
90
+ }
91
+
92
+ if (entry.cancelled) entries.splice(i, 1);
93
+ }
94
+ }
95
+ }
96
+
97
+ Timer.entries = [];
98
+
99
+ export default Timer;
package/src/Transform.js CHANGED
@@ -1,15 +1,108 @@
1
1
  /**
2
2
  * @class Transform
3
- * @description Represents a transform
4
- * @param {Vector3} position - The position of the transform
5
- * @param {number} rotation - The rotation of the transform
6
- * @param {Vector2} scale - The scale of the transform
3
+ * @description Represents a transform with optional parent/child hierarchy.
4
+ * Local position/rotation/scale are composed up the parent chain to produce a
5
+ * world transform. Objects with no parent behave exactly as before (the world
6
+ * transform is the local transform), so existing code is unaffected.
7
+ * @param {Vector3} position - The local position
8
+ * @param {number} rotation - The local rotation (radians)
9
+ * @param {Vector2} scale - The local scale
7
10
  */
8
11
  class Transform {
9
12
  constructor(position, rotation, scale) {
10
13
  this.position = position;
11
14
  this.rotation = rotation;
12
15
  this.scale = scale;
16
+
17
+ this.parent = null;
18
+ this.children = [];
19
+
20
+ /** @private */
21
+ this._world = {
22
+ position: { x: 0, y: 0, z: 0 },
23
+ rotation: 0,
24
+ scale: { x: 1, y: 1 },
25
+ };
26
+ }
27
+
28
+ /**
29
+ * @method setParent
30
+ * @description Sets (or clears) the parent transform, maintaining the
31
+ * children list on both sides.
32
+ * @param {Transform|null} parent - The parent transform, or null to detach
33
+ */
34
+ setParent(parent) {
35
+ if (this.parent === parent) return;
36
+ if (this.parent) {
37
+ const idx = this.parent.children.indexOf(this);
38
+ if (idx !== -1) this.parent.children.splice(idx, 1);
39
+ }
40
+ this.parent = parent || null;
41
+ if (this.parent && this.parent.children.indexOf(this) === -1) {
42
+ this.parent.children.push(this);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * @method getWorldParts
48
+ * @description Returns the composed world TRS as primitives. Composition is
49
+ * standard 2D: child local space is scaled and rotated by the parent.
50
+ * @returns {{px:number, py:number, pz:number, rot:number, sx:number, sy:number}}
51
+ */
52
+ getWorldParts() {
53
+ if (!this.parent) {
54
+ return {
55
+ px: this.position.x,
56
+ py: this.position.y,
57
+ pz: this.position.z,
58
+ rot: this.rotation,
59
+ sx: this.scale.x,
60
+ sy: this.scale.y,
61
+ };
62
+ }
63
+
64
+ const p = this.parent.getWorldParts();
65
+ const cos = Math.cos(p.rot);
66
+ const sin = Math.sin(p.rot);
67
+ const lx = this.position.x * p.sx;
68
+ const ly = this.position.y * p.sy;
69
+
70
+ return {
71
+ px: p.px + (lx * cos - ly * sin),
72
+ py: p.py + (lx * sin + ly * cos),
73
+ pz: p.pz + this.position.z,
74
+ rot: p.rot + this.rotation,
75
+ sx: p.sx * this.scale.x,
76
+ sy: p.sy * this.scale.y,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * @method getWorldTransform
82
+ * @description Returns a transform-shaped object ({position, rotation, scale})
83
+ * in world space. The returned object is reused between calls.
84
+ * @returns {{position:{x,y,z}, rotation:number, scale:{x,y}}}
85
+ */
86
+ getWorldTransform() {
87
+ if (!this.parent) return this;
88
+ const w = this.getWorldParts();
89
+ const out = this._world;
90
+ out.position.x = w.px;
91
+ out.position.y = w.py;
92
+ out.position.z = w.pz;
93
+ out.rotation = w.rot;
94
+ out.scale.x = w.sx;
95
+ out.scale.y = w.sy;
96
+ return out;
97
+ }
98
+
99
+ /**
100
+ * @method getWorldPosition
101
+ * @description Returns the world-space position as a plain { x, y, z }.
102
+ */
103
+ getWorldPosition() {
104
+ const w = this.getWorldParts();
105
+ return { x: w.px, y: w.py, z: w.pz };
13
106
  }
14
107
 
15
108
  /**
@@ -30,7 +123,7 @@ class Transform {
30
123
 
31
124
  /**
32
125
  * @method getPosition
33
- * @description Returns the position of the transform
126
+ * @description Returns the local position of the transform
34
127
  * @returns {Vector3} - The position of the transform
35
128
  */
36
129
  getPosition() {
@@ -39,7 +132,7 @@ class Transform {
39
132
 
40
133
  /**
41
134
  * @method getRotation
42
- * @description Returns the rotation of the transform
135
+ * @description Returns the local rotation of the transform
43
136
  * @returns {number} - The rotation of the transform
44
137
  */
45
138
  getRotation() {
@@ -48,7 +141,7 @@ class Transform {
48
141
 
49
142
  /**
50
143
  * @method getScale
51
- * @description Returns the scale of the transform
144
+ * @description Returns the local scale of the transform
52
145
  * @returns {Vector2} - The scale of the transform
53
146
  */
54
147
  getScale() {