emeraldengine 2.2.1 → 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 -582
  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
@@ -1,477 +1,488 @@
1
- import Scene from "../Scene.js";
2
- import InstancedTexture from "../InstancedTexture.js";
3
-
4
- /**
5
- * @class EventManager
6
- * @description Manages the events for the game
7
- * @param {HTMLCanvasElement} canvas - The canvas element
8
- * @param {Scene} scene - The scene
9
- * @param {Camera} camera - The camera
10
- */
11
- class EventManager {
12
- constructor(canvas, scene, camera) {
13
- this.scene = scene;
14
- this.camera = camera;
15
- this.canvas = canvas;
16
- this.keyDownListeners = new Map();
17
- this.keyUpListeners = new Map();
18
- this.clickListeners = new Map();
19
- this.hoverListeners = new Map();
20
- this.lastHoveredObject = null;
21
- this.lastHoveredInstance = null;
22
- this.pressedKeys = new Map();
23
- this.mousePosition = { x: 0, y: 0 };
24
- this.boundHandleMouseMove = this.handleMouseMove.bind(this);
25
- this.boundHandleKeyDown = this.handleKeyDown.bind(this);
26
- this.boundHandleKeyUp = this.handleKeyUp.bind(this);
27
- this.boundHandleClick = this.handleClick.bind(this);
28
-
29
- this.boundHandleMouseDown = this.handleMouseDown.bind(this);
30
- this.boundHandleMouseUp = this.handleMouseUp.bind(this);
31
-
32
- // Camera dragging state
33
- this.isDragging = false;
34
- this.dragStartX = 0;
35
- this.dragStartY = 0;
36
- this.initialCameraX = this.camera.transform.position.x;
37
- this.initialCameraY = this.camera.transform.position.y;
38
- this.cameraWasMoved = false;
39
-
40
- window.addEventListener("mousemove", this.boundHandleMouseMove);
41
- window.addEventListener("keydown", this.boundHandleKeyDown);
42
- window.addEventListener("keyup", this.boundHandleKeyUp);
43
- window.addEventListener("click", this.boundHandleClick);
44
- window.addEventListener("mousedown", this.boundHandleMouseDown);
45
- window.addEventListener("mouseup", this.boundHandleMouseUp);
46
- }
47
-
48
- /**
49
- * @method addKeyDown
50
- * @description Adds a key down event listener
51
- * @param {string} key - The key to listen for
52
- * @param {function} func - The function to call when the key is pressed
53
- */
54
- addKeyDown(key, func) {
55
- if (!this.keyDownListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
56
- this.keyDownListeners.set(
57
- key.length <= 1 ? key.toLowerCase() : key,
58
- new Set()
59
- );
60
- }
61
- this.keyDownListeners
62
- .get(key.length <= 1 ? key.toLowerCase() : key)
63
- .add(func);
64
- }
65
-
66
- /**
67
- * @method addKeyUp
68
- * @description Adds a key up event listener
69
- * @param {string} key - The key to listen for
70
- * @param {function} func - The function to call when the key is released
71
- */
72
- addKeyUp(key, func) {
73
- if (!this.keyUpListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
74
- this.keyUpListeners.set(
75
- key.length <= 1 ? key.toLowerCase() : key,
76
- new Set()
77
- );
78
- }
79
- this.keyUpListeners
80
- .get(key.length <= 1 ? key.toLowerCase() : key)
81
- .add(func);
82
- }
83
-
84
- /**
85
- * @method removeKeyDown
86
- * @description Removes a key down event listener
87
- * @param {string} key - The key to remove the listener for
88
- * @param {function} func - The function to remove
89
- */
90
- removeKeyDown(key, func) {
91
- if (this.keyDownListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
92
- this.keyDownListeners
93
- .get(key.length <= 1 ? key.toLowerCase() : key)
94
- .delete(func);
95
- }
96
- }
97
-
98
- /**
99
- * @method removeKeyUp
100
- * @description Removes a key up event listener
101
- * @param {string} key - The key to remove the listener for
102
- * @param {function} func - The function to remove
103
- */
104
- removeKeyUp(key, func) {
105
- if (this.keyUpListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
106
- this.keyUpListeners
107
- .get(key.length <= 1 ? key.toLowerCase() : key)
108
- .delete(func);
109
- }
110
- }
111
-
112
- /**
113
- * @method handleKeyDown
114
- * @description Handles a key down event
115
- * @param {KeyboardEvent} event - The keyboard event
116
- */
117
- handleKeyDown(event) {
118
- this.pressedKeys.set(
119
- event.key.length <= 1 ? event.key.toLowerCase() : event.key,
120
- true
121
- );
122
- const listeners = this.keyDownListeners.get(
123
- event.key.length <= 1 ? event.key.toLowerCase() : event.key
124
- );
125
- if (listeners) {
126
- listeners.forEach((func) => func(event));
127
- }
128
- }
129
-
130
- /**
131
- * @method handleKeyUp
132
- * @description Handles a key up event
133
- * @param {KeyboardEvent} event - The keyboard event
134
- */
135
- handleKeyUp(event) {
136
- this.pressedKeys.set(
137
- event.key.length <= 1 ? event.key.toLowerCase() : event.key,
138
- false
139
- );
140
- const listeners = this.keyUpListeners.get(
141
- event.key.length <= 1 ? event.key.toLowerCase() : event.key
142
- );
143
- if (listeners) {
144
- listeners.forEach((func) => func(event));
145
- }
146
- }
147
-
148
- /**
149
- * @method isKeyPressed
150
- * @description Checks if a key is pressed
151
- * @param {string} key - The key to check
152
- * @returns {boolean} - True if the key is pressed
153
- */
154
- isKeyPressed(key) {
155
- return (
156
- this.pressedKeys.get(key.length <= 1 ? key.toLowerCase() : key) || false
157
- );
158
- }
159
-
160
- /**
161
- * @method addClickEvent
162
- * @description Adds a click event listener
163
- * @param {GameObject} object - The object to listen for
164
- * @param {function} func - The function to call when the object is clicked
165
- */
166
- addClickEvent(object, func) {
167
- if (!this.clickListeners.has(object.id)) {
168
- this.clickListeners.set(object.id, new Set());
169
- }
170
- this.clickListeners.get(object.id).add(func);
171
- }
172
-
173
- /**
174
- * @method handleClick
175
- * @description Handles a click event
176
- * @param {MouseEvent} event - The mouse event
177
- */
178
- handleClick(event) {
179
- const canvas = event.target;
180
- const rect = canvas.getBoundingClientRect();
181
-
182
- // Calculate click position relative to the canvas
183
- const canvasX = event.clientX - rect.left;
184
- const canvasY = event.clientY - rect.top;
185
-
186
- // Convert to world coordinates
187
- const worldX =
188
- canvasX - this.canvas.width / 2 - this.camera.transform.position.x;
189
- const worldY =
190
- this.canvas.height / 2 - canvasY - this.camera.transform.position.y;
191
-
192
- this.scene.objects.forEach((sceneObject) => {
193
- if (!sceneObject.isActive) return; // Skip inactive objects
194
- // Check if the object has an InstancedTexture component
195
- let instanceHandled = false;
196
- if (
197
- sceneObject.getComponent &&
198
- sceneObject.getComponent(InstancedTexture)
199
- ) {
200
- const instancedTexture = sceneObject.getComponent(InstancedTexture);
201
- instanceHandled = instancedTexture.handleInstanceClick(
202
- event,
203
- worldX,
204
- worldY
205
- );
206
- }
207
-
208
- // If no instance handled the click, check the object itself
209
- if (
210
- !instanceHandled &&
211
- this.isPointInObject(worldX, worldY, sceneObject)
212
- ) {
213
- const listeners = this.clickListeners.get(sceneObject.id);
214
- if (listeners) {
215
- listeners.forEach((func) => func(event, sceneObject));
216
- }
217
- }
218
-
219
- });
220
- }
221
-
222
- /**
223
- * @method removeClickEvent
224
- * @description Removes a click event listener
225
- * @param {GameObject} object - The object to remove the listener for
226
- * @param {function} func - The function to remove
227
- */
228
- removeClickEvent(object, func) {
229
- if (this.clickListeners.has(object.id)) {
230
- this.clickListeners.get(object.id).delete(func);
231
- }
232
- }
233
-
234
- /**
235
- * @method isPointInObject
236
- * @description Checks if a point is in an object
237
- * @param {number} x - The x coordinate of the point
238
- * @param {number} y - The y coordinate of the point
239
- * @param {GameObject} object - The object to check
240
- * @returns {boolean} - True if the point is in the object
241
- */
242
- isPointInObject(x, y, object) {
243
- const left = object.transform.position.x - object.transform.scale.x;
244
- const right = object.transform.position.x + object.transform.scale.x;
245
- const top = object.transform.position.y + object.transform.scale.y;
246
- const bottom = object.transform.position.y - object.transform.scale.y;
247
- return x >= left && x <= right && y >= bottom && y <= top;
248
- }
249
-
250
- /**
251
- * @method addHoverEvent
252
- * @description Adds a hover event listener
253
- * @param {GameObject} object - The object to listen for
254
- * @param {function} enterFunc - The function to call when the object is hovered
255
- * @param {function} leaveFunc - The function to call when the object is no longer hovered
256
- */
257
- addHoverEvent(object, enterFunc, leaveFunc) {
258
- if (!this.hoverListeners.has(object.id)) {
259
- this.hoverListeners.set(object.id, {
260
- enter: new Set(),
261
- leave: new Set(),
262
- });
263
- }
264
- this.hoverListeners.get(object.id).enter.add(enterFunc);
265
- this.hoverListeners.get(object.id).leave.add(leaveFunc);
266
- }
267
-
268
- /**
269
- * @method removeHoverEvent
270
- * @description Removes a hover event listener
271
- * @param {GameObject} object - The object to remove the listener for
272
- * @param {function} enterFunc - The function to remove
273
- * @param {function} leaveFunc - The function to remove
274
- */
275
- removeHoverEvent(object, enterFunc, leaveFunc) {
276
- if (this.hoverListeners.has(object.id)) {
277
- const listeners = this.hoverListeners.get(object.id);
278
- listeners.enter.delete(enterFunc);
279
- listeners.leave.delete(leaveFunc);
280
- }
281
- }
282
-
283
- /**
284
- * @method handleMouseDown
285
- * @description Handles a mouse down event
286
- * @param {MouseEvent} event - The mouse event
287
- */
288
- handleMouseDown(event) {
289
- if (event.button === 1) {
290
- event.preventDefault();
291
- this.isDragging = true;
292
- this.dragStartX = event.clientX;
293
- this.dragStartY = event.clientY;
294
- this.initialCameraX = this.camera.transform.position.x;
295
- this.initialCameraY = this.camera.transform.position.y;
296
- this.canvas.style.cursor = "grabbing";
297
- }
298
- }
299
-
300
- /**
301
- * @method handleMouseUp
302
- * @description Handles a mouse up event
303
- * @param {MouseEvent} event - The mouse event
304
- */
305
- handleMouseUp(event) {
306
- if (event.button === 1 && this.isDragging) {
307
- this.isDragging = false;
308
- this.canvas.style.cursor = "default";
309
- this.cameraWasMoved = true;
310
- }
311
- }
312
-
313
- /**
314
- * @method handleMouseMove
315
- * @description Handles a mouse move event
316
- * @param {MouseEvent} event - The mouse event
317
- */
318
- handleMouseMove(event) {
319
- const rect = this.canvas.getBoundingClientRect();
320
-
321
- // Calculate mouse position relative to the canvas
322
- const canvasX = event.clientX - rect.left;
323
- const canvasY = event.clientY - rect.top;
324
-
325
- // Convert to world coordinates
326
- const worldX =
327
- canvasX - this.canvas.width / 2 - this.camera.transform.position.x;
328
- const worldY =
329
- this.canvas.height / 2 - canvasY - this.camera.transform.position.y;
330
- this.mousePosition.x = worldX;
331
- this.mousePosition.y = worldY;
332
- let hoveredObject = null;
333
- let hoveredInstance = null;
334
-
335
- this.scene.objects.forEach((sceneObject) => {
336
- if (!sceneObject.isActive) return; // Skip inactive objects
337
-
338
- // Check if the object has an InstancedTexture component
339
- if (
340
- sceneObject.getComponent &&
341
- sceneObject.getComponent(InstancedTexture)
342
- ) {
343
- const instancedTexture = sceneObject.getComponent(InstancedTexture);
344
- const instanceAtPoint = instancedTexture.getInstanceAtPoint(
345
- worldX,
346
- worldY
347
- );
348
- if (instanceAtPoint) {
349
- hoveredInstance = instanceAtPoint;
350
- hoveredObject = sceneObject; // Also set the parent object as hovered
351
- }
352
- }
353
-
354
- // If no instance was hovered, check the object itself
355
- if (
356
- !hoveredInstance &&
357
- this.isPointInObject(worldX, worldY, sceneObject)
358
- ) {
359
- hoveredObject = sceneObject;
360
- }
361
- });
362
-
363
- // Handle instance hover events
364
- if (hoveredInstance !== this.lastHoveredInstance) {
365
- if (this.lastHoveredInstance) {
366
- const parent = this.lastHoveredInstance.parent;
367
- if (parent && parent instanceof InstancedTexture) {
368
- parent.handleInstanceHover(
369
- event,
370
- worldX,
371
- worldY,
372
- this.lastHoveredInstance
373
- );
374
- }
375
- }
376
-
377
- if (hoveredInstance) {
378
- const parent = hoveredInstance.parent;
379
- if (parent && parent instanceof InstancedTexture) {
380
- parent.handleInstanceHover(
381
- event,
382
- worldX,
383
- worldY,
384
- this.lastHoveredInstance
385
- );
386
- }
387
- }
388
-
389
- this.lastHoveredInstance = hoveredInstance;
390
- }
391
-
392
- // Handle object hover events
393
- if (hoveredObject !== this.lastHoveredObject) {
394
- // Always trigger leave event if we had a previously hovered object
395
- if (this.lastHoveredObject && this.lastHoveredObject.isActive) {
396
- const leaveListeners = this.hoverListeners.get(
397
- this.lastHoveredObject.id
398
- );
399
- if (leaveListeners) {
400
- leaveListeners.leave.forEach((func) =>
401
- func(event, this.lastHoveredObject)
402
- );
403
- }
404
- }
405
-
406
- // Only trigger enter event if we're not hovering over an instance
407
- if (hoveredObject && hoveredObject.isActive && !hoveredInstance) {
408
- const enterListeners = this.hoverListeners.get(hoveredObject.id);
409
- if (enterListeners) {
410
- enterListeners.enter.forEach((func) => func(event, hoveredObject));
411
- }
412
- }
413
-
414
- this.lastHoveredObject = hoveredObject;
415
- }
416
- }
417
-
418
- /**
419
- * @method getMousePosition
420
- * @description Returns the mouse position
421
- * @returns {Object} - The mouse position
422
- */
423
- getMousePosition() {
424
- return this.mousePosition;
425
- }
426
-
427
- /**
428
- * @method wasCameraMoved
429
- * @description Checks if the camera was moved
430
- * @returns {boolean} - True if the camera was moved
431
- */
432
- wasCameraMoved() {
433
- return this.cameraWasMoved;
434
- }
435
-
436
- /**
437
- * @method resetCameraMoved
438
- * @description Resets the camera moved flag
439
- */
440
- resetCameraMoved() {
441
- this.cameraWasMoved = false;
442
- }
443
-
444
- /**
445
- * @method changeScene
446
- * @description Changes the scene
447
- * @param {Scene} scene - The scene to change to
448
- */
449
- changeScene(scene) {
450
- if (scene instanceof Scene) {
451
- this.scene = scene;
452
- } else {
453
- console.error(
454
- "[EventManager] > Specified argument is not an instance of Scene class"
455
- );
456
- }
457
- }
458
-
459
- /**
460
- * @method clean
461
- * @description Cleans up the event manager
462
- */
463
- clean() {
464
- window.removeEventListener("mousemove", this.boundHandleMouseMove);
465
- window.removeEventListener("keydown", this.boundHandleKeyDown);
466
- window.removeEventListener("keyup", this.boundHandleKeyUp);
467
- window.removeEventListener("click", this.boundHandleClick);
468
- window.removeEventListener("mousedown", this.boundHandleMouseDown);
469
- window.removeEventListener("mouseup", this.boundHandleMouseUp);
470
- this.pressedKeys.clear();
471
- this.keyDownListeners.clear();
472
- this.lastHoveredObject = null;
473
- this.lastHoveredInstance = null;
474
- }
475
- }
476
-
477
- export default EventManager;
1
+ import Scene from "../Scene.js";
2
+ import InstancedTexture from "../InstancedTexture.js";
3
+
4
+ /**
5
+ * @class EventManager
6
+ * @description Manages the events for the game
7
+ * @param {HTMLCanvasElement} canvas - The canvas element
8
+ * @param {Scene} scene - The scene
9
+ * @param {Camera} camera - The camera
10
+ */
11
+ class EventManager {
12
+ constructor(canvas, scene, camera) {
13
+ this.scene = scene;
14
+ this.camera = camera;
15
+ this.canvas = canvas;
16
+ this.keyDownListeners = new Map();
17
+ this.keyUpListeners = new Map();
18
+ this.clickListeners = new Map();
19
+ this.hoverListeners = new Map();
20
+ this.lastHoveredObject = null;
21
+ this.lastHoveredInstance = null;
22
+ this.pressedKeys = new Map();
23
+ this.mousePosition = { x: 0, y: 0 };
24
+ this.boundHandleMouseMove = this.handleMouseMove.bind(this);
25
+ this.boundHandleKeyDown = this.handleKeyDown.bind(this);
26
+ this.boundHandleKeyUp = this.handleKeyUp.bind(this);
27
+ this.boundHandleClick = this.handleClick.bind(this);
28
+
29
+ this.boundHandleMouseDown = this.handleMouseDown.bind(this);
30
+ this.boundHandleMouseUp = this.handleMouseUp.bind(this);
31
+
32
+ this.isDragging = false;
33
+ this.dragStartX = 0;
34
+ this.dragStartY = 0;
35
+ this.initialCameraX = this.camera.transform.position.x;
36
+ this.initialCameraY = this.camera.transform.position.y;
37
+ this.cameraWasMoved = false;
38
+
39
+ this.canvas.addEventListener("click", this.boundHandleClick);
40
+ this.canvas.addEventListener("mousedown", this.boundHandleMouseDown);
41
+ window.addEventListener("mousemove", this.boundHandleMouseMove);
42
+ window.addEventListener("mouseup", this.boundHandleMouseUp);
43
+ window.addEventListener("keydown", this.boundHandleKeyDown);
44
+ window.addEventListener("keyup", this.boundHandleKeyUp);
45
+ }
46
+
47
+ /**
48
+ * @method addKeyDown
49
+ * @description Adds a key down event listener
50
+ * @param {string} key - The key to listen for
51
+ * @param {function} func - The function to call when the key is pressed
52
+ */
53
+ addKeyDown(key, func) {
54
+ if (!this.keyDownListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
55
+ this.keyDownListeners.set(
56
+ key.length <= 1 ? key.toLowerCase() : key,
57
+ new Set()
58
+ );
59
+ }
60
+ this.keyDownListeners
61
+ .get(key.length <= 1 ? key.toLowerCase() : key)
62
+ .add(func);
63
+ }
64
+
65
+ /**
66
+ * @method addKeyUp
67
+ * @description Adds a key up event listener
68
+ * @param {string} key - The key to listen for
69
+ * @param {function} func - The function to call when the key is released
70
+ */
71
+ addKeyUp(key, func) {
72
+ if (!this.keyUpListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
73
+ this.keyUpListeners.set(
74
+ key.length <= 1 ? key.toLowerCase() : key,
75
+ new Set()
76
+ );
77
+ }
78
+ this.keyUpListeners
79
+ .get(key.length <= 1 ? key.toLowerCase() : key)
80
+ .add(func);
81
+ }
82
+
83
+ /**
84
+ * @method removeKeyDown
85
+ * @description Removes a key down event listener
86
+ * @param {string} key - The key to remove the listener for
87
+ * @param {function} func - The function to remove
88
+ */
89
+ removeKeyDown(key, func) {
90
+ if (this.keyDownListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
91
+ this.keyDownListeners
92
+ .get(key.length <= 1 ? key.toLowerCase() : key)
93
+ .delete(func);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * @method removeKeyUp
99
+ * @description Removes a key up event listener
100
+ * @param {string} key - The key to remove the listener for
101
+ * @param {function} func - The function to remove
102
+ */
103
+ removeKeyUp(key, func) {
104
+ if (this.keyUpListeners.has(key.length <= 1 ? key.toLowerCase() : key)) {
105
+ this.keyUpListeners
106
+ .get(key.length <= 1 ? key.toLowerCase() : key)
107
+ .delete(func);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * @method handleKeyDown
113
+ * @description Handles a key down event
114
+ * @param {KeyboardEvent} event - The keyboard event
115
+ */
116
+ handleKeyDown(event) {
117
+ this.pressedKeys.set(
118
+ event.key.length <= 1 ? event.key.toLowerCase() : event.key,
119
+ true
120
+ );
121
+ const listeners = this.keyDownListeners.get(
122
+ event.key.length <= 1 ? event.key.toLowerCase() : event.key
123
+ );
124
+ if (listeners) {
125
+ listeners.forEach((func) => func(event));
126
+ }
127
+ }
128
+
129
+ /**
130
+ * @method handleKeyUp
131
+ * @description Handles a key up event
132
+ * @param {KeyboardEvent} event - The keyboard event
133
+ */
134
+ handleKeyUp(event) {
135
+ this.pressedKeys.set(
136
+ event.key.length <= 1 ? event.key.toLowerCase() : event.key,
137
+ false
138
+ );
139
+ const listeners = this.keyUpListeners.get(
140
+ event.key.length <= 1 ? event.key.toLowerCase() : event.key
141
+ );
142
+ if (listeners) {
143
+ listeners.forEach((func) => func(event));
144
+ }
145
+ }
146
+
147
+ /**
148
+ * @method isKeyPressed
149
+ * @description Checks if a key is pressed
150
+ * @param {string} key - The key to check
151
+ * @returns {boolean} - True if the key is pressed
152
+ */
153
+ isKeyPressed(key) {
154
+ return (
155
+ this.pressedKeys.get(key.length <= 1 ? key.toLowerCase() : key) || false
156
+ );
157
+ }
158
+
159
+ /**
160
+ * @method addClickEvent
161
+ * @description Adds a click event listener
162
+ * @param {GameObject} object - The object to listen for
163
+ * @param {function} func - The function to call when the object is clicked
164
+ */
165
+ addClickEvent(object, func) {
166
+ if (!this.clickListeners.has(object.id)) {
167
+ this.clickListeners.set(object.id, new Set());
168
+ }
169
+ this.clickListeners.get(object.id).add(func);
170
+ }
171
+
172
+ /**
173
+ * @method screenToWorld
174
+ * @description Converts screen (client) coordinates to world coordinates,
175
+ * accounting for camera position, zoom, and CSS/backing pixel ratio.
176
+ * @param {number} clientX - The clientX of the pointer
177
+ * @param {number} clientY - The clientY of the pointer
178
+ * @returns {{x: number, y: number}} - The world-space coordinates
179
+ */
180
+ screenToWorld(clientX, clientY) {
181
+ const rect = this.canvas.getBoundingClientRect();
182
+ const scale = this.camera.transform.scale;
183
+ const zoom = scale && scale.x ? scale.x : 1;
184
+
185
+ const vp = this.camera.viewport || { x: 0, y: 0, width: 1, height: 1 };
186
+ const vpLeft = rect.left + vp.x * rect.width;
187
+ const vpTop = rect.top + (1 - vp.y - vp.height) * rect.height;
188
+ const centerX = vpLeft + (vp.width * rect.width) / 2;
189
+ const centerY = vpTop + (vp.height * rect.height) / 2;
190
+
191
+ const cx = clientX - centerX;
192
+ const cy = clientY - centerY;
193
+ return {
194
+ x: cx / zoom - this.camera.transform.position.x,
195
+ y: -cy / zoom - this.camera.transform.position.y,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * @method getTopObjectAt
201
+ * @description Returns the topmost active object under a world-space point
202
+ * along with the instance hit (if the object is an InstancedTexture). Topmost
203
+ * means highest z, with later scene order breaking ties — matching draw order.
204
+ * @param {number} worldX - The world-space x coordinate
205
+ * @param {number} worldY - The world-space y coordinate
206
+ * @returns {{object: GameObject, instance: Instance|null}|null}
207
+ */
208
+ getTopObjectAt(worldX, worldY) {
209
+ const objects = this.scene.objects;
210
+ let best = null;
211
+ let bestInstance = null;
212
+ let bestZ = -Infinity;
213
+ let bestIndex = -1;
214
+
215
+ for (let i = 0; i < objects.length; i++) {
216
+ const obj = objects[i];
217
+ if (!obj.isActive) continue;
218
+
219
+ let instanceHit = null;
220
+ if (obj.getComponent) {
221
+ const instancedTexture = obj.getComponent(InstancedTexture);
222
+ if (instancedTexture) {
223
+ instanceHit = instancedTexture.getInstanceAtPoint(worldX, worldY);
224
+ }
225
+ }
226
+
227
+ if (!instanceHit && !this.isPointInObject(worldX, worldY, obj)) {
228
+ continue;
229
+ }
230
+
231
+ const z = obj.transform.position.z;
232
+ if (z > bestZ || (z === bestZ && i > bestIndex)) {
233
+ best = obj;
234
+ bestInstance = instanceHit;
235
+ bestZ = z;
236
+ bestIndex = i;
237
+ }
238
+ }
239
+
240
+ return best ? { object: best, instance: bestInstance } : null;
241
+ }
242
+
243
+ /**
244
+ * @method handleClick
245
+ * @description Handles a click event
246
+ * @param {MouseEvent} event - The mouse event
247
+ */
248
+ handleClick(event) {
249
+ const { x: worldX, y: worldY } = this.screenToWorld(
250
+ event.clientX,
251
+ event.clientY
252
+ );
253
+
254
+ const hit = this.getTopObjectAt(worldX, worldY);
255
+ if (!hit) return;
256
+
257
+ if (hit.instance) {
258
+ hit.object
259
+ .getComponent(InstancedTexture)
260
+ .handleInstanceClick(event, worldX, worldY);
261
+ return;
262
+ }
263
+
264
+ const listeners = this.clickListeners.get(hit.object.id);
265
+ if (listeners) {
266
+ listeners.forEach((func) => func(event, hit.object));
267
+ }
268
+ }
269
+
270
+ /**
271
+ * @method removeClickEvent
272
+ * @description Removes a click event listener
273
+ * @param {GameObject} object - The object to remove the listener for
274
+ * @param {function} func - The function to remove
275
+ */
276
+ removeClickEvent(object, func) {
277
+ if (this.clickListeners.has(object.id)) {
278
+ this.clickListeners.get(object.id).delete(func);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * @method isPointInObject
284
+ * @description Checks if a point is in an object
285
+ * @param {number} x - The x coordinate of the point
286
+ * @param {number} y - The y coordinate of the point
287
+ * @param {GameObject} object - The object to check
288
+ * @returns {boolean} - True if the point is in the object
289
+ */
290
+ isPointInObject(x, y, object) {
291
+ const left = object.transform.position.x - object.transform.scale.x;
292
+ const right = object.transform.position.x + object.transform.scale.x;
293
+ const top = object.transform.position.y + object.transform.scale.y;
294
+ const bottom = object.transform.position.y - object.transform.scale.y;
295
+ return x >= left && x <= right && y >= bottom && y <= top;
296
+ }
297
+
298
+ /**
299
+ * @method addHoverEvent
300
+ * @description Adds a hover event listener
301
+ * @param {GameObject} object - The object to listen for
302
+ * @param {function} enterFunc - The function to call when the object is hovered
303
+ * @param {function} leaveFunc - The function to call when the object is no longer hovered
304
+ */
305
+ addHoverEvent(object, enterFunc, leaveFunc) {
306
+ if (!this.hoverListeners.has(object.id)) {
307
+ this.hoverListeners.set(object.id, {
308
+ enter: new Set(),
309
+ leave: new Set(),
310
+ });
311
+ }
312
+ this.hoverListeners.get(object.id).enter.add(enterFunc);
313
+ this.hoverListeners.get(object.id).leave.add(leaveFunc);
314
+ }
315
+
316
+ /**
317
+ * @method removeHoverEvent
318
+ * @description Removes a hover event listener
319
+ * @param {GameObject} object - The object to remove the listener for
320
+ * @param {function} enterFunc - The function to remove
321
+ * @param {function} leaveFunc - The function to remove
322
+ */
323
+ removeHoverEvent(object, enterFunc, leaveFunc) {
324
+ if (this.hoverListeners.has(object.id)) {
325
+ const listeners = this.hoverListeners.get(object.id);
326
+ listeners.enter.delete(enterFunc);
327
+ listeners.leave.delete(leaveFunc);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * @method handleMouseDown
333
+ * @description Handles a mouse down event
334
+ * @param {MouseEvent} event - The mouse event
335
+ */
336
+ handleMouseDown(event) {
337
+ if (event.button === 1) {
338
+ event.preventDefault();
339
+ this.isDragging = true;
340
+ this.dragStartX = event.clientX;
341
+ this.dragStartY = event.clientY;
342
+ this.initialCameraX = this.camera.transform.position.x;
343
+ this.initialCameraY = this.camera.transform.position.y;
344
+ this.canvas.style.cursor = "grabbing";
345
+ }
346
+ }
347
+
348
+ /**
349
+ * @method handleMouseUp
350
+ * @description Handles a mouse up event
351
+ * @param {MouseEvent} event - The mouse event
352
+ */
353
+ handleMouseUp(event) {
354
+ if (event.button === 1 && this.isDragging) {
355
+ this.isDragging = false;
356
+ this.canvas.style.cursor = "default";
357
+ this.cameraWasMoved = true;
358
+ }
359
+ }
360
+
361
+ /**
362
+ * @method handleMouseMove
363
+ * @description Handles a mouse move event
364
+ * @param {MouseEvent} event - The mouse event
365
+ */
366
+ handleMouseMove(event) {
367
+ const { x: worldX, y: worldY } = this.screenToWorld(
368
+ event.clientX,
369
+ event.clientY
370
+ );
371
+ this.mousePosition.x = worldX;
372
+ this.mousePosition.y = worldY;
373
+
374
+ const hit = this.getTopObjectAt(worldX, worldY);
375
+ const hoveredObject = hit ? hit.object : null;
376
+ const hoveredInstance = hit ? hit.instance : null;
377
+
378
+ if (hoveredInstance !== this.lastHoveredInstance) {
379
+ if (this.lastHoveredInstance) {
380
+ const parent = this.lastHoveredInstance.parent;
381
+ if (parent && parent instanceof InstancedTexture) {
382
+ parent.handleInstanceHover(
383
+ event,
384
+ worldX,
385
+ worldY,
386
+ this.lastHoveredInstance
387
+ );
388
+ }
389
+ }
390
+
391
+ if (hoveredInstance) {
392
+ const parent = hoveredInstance.parent;
393
+ if (parent && parent instanceof InstancedTexture) {
394
+ parent.handleInstanceHover(
395
+ event,
396
+ worldX,
397
+ worldY,
398
+ this.lastHoveredInstance
399
+ );
400
+ }
401
+ }
402
+
403
+ this.lastHoveredInstance = hoveredInstance;
404
+ }
405
+
406
+ if (hoveredObject !== this.lastHoveredObject) {
407
+ if (this.lastHoveredObject && this.lastHoveredObject.isActive) {
408
+ const leaveListeners = this.hoverListeners.get(
409
+ this.lastHoveredObject.id
410
+ );
411
+ if (leaveListeners) {
412
+ leaveListeners.leave.forEach((func) =>
413
+ func(event, this.lastHoveredObject)
414
+ );
415
+ }
416
+ }
417
+
418
+ if (hoveredObject && hoveredObject.isActive && !hoveredInstance) {
419
+ const enterListeners = this.hoverListeners.get(hoveredObject.id);
420
+ if (enterListeners) {
421
+ enterListeners.enter.forEach((func) => func(event, hoveredObject));
422
+ }
423
+ }
424
+
425
+ this.lastHoveredObject = hoveredObject;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * @method getMousePosition
431
+ * @description Returns the mouse position
432
+ * @returns {Object} - The mouse position
433
+ */
434
+ getMousePosition() {
435
+ return this.mousePosition;
436
+ }
437
+
438
+ /**
439
+ * @method wasCameraMoved
440
+ * @description Checks if the camera was moved
441
+ * @returns {boolean} - True if the camera was moved
442
+ */
443
+ wasCameraMoved() {
444
+ return this.cameraWasMoved;
445
+ }
446
+
447
+ /**
448
+ * @method resetCameraMoved
449
+ * @description Resets the camera moved flag
450
+ */
451
+ resetCameraMoved() {
452
+ this.cameraWasMoved = false;
453
+ }
454
+
455
+ /**
456
+ * @method changeScene
457
+ * @description Changes the scene
458
+ * @param {Scene} scene - The scene to change to
459
+ */
460
+ changeScene(scene) {
461
+ if (scene instanceof Scene) {
462
+ this.scene = scene;
463
+ } else {
464
+ console.error(
465
+ "[EventManager] > Specified argument is not an instance of Scene class"
466
+ );
467
+ }
468
+ }
469
+
470
+ /**
471
+ * @method clean
472
+ * @description Cleans up the event manager
473
+ */
474
+ clean() {
475
+ this.canvas.removeEventListener("click", this.boundHandleClick);
476
+ this.canvas.removeEventListener("mousedown", this.boundHandleMouseDown);
477
+ window.removeEventListener("mousemove", this.boundHandleMouseMove);
478
+ window.removeEventListener("mouseup", this.boundHandleMouseUp);
479
+ window.removeEventListener("keydown", this.boundHandleKeyDown);
480
+ window.removeEventListener("keyup", this.boundHandleKeyUp);
481
+ this.pressedKeys.clear();
482
+ this.keyDownListeners.clear();
483
+ this.lastHoveredObject = null;
484
+ this.lastHoveredInstance = null;
485
+ }
486
+ }
487
+
488
+ export default EventManager;