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
package/README.md CHANGED
@@ -1,968 +1,2359 @@
1
- # Emerald
2
-
3
- Emerald is a comprehensive 2D graphics engine that can help you create games easier than ever.
4
-
5
- ## Table of Contents
6
-
7
- - [Emerald](#emerald)
8
- - [Table of Contents](#table-of-contents)
9
- - [Getting Started](#getting-started)
10
- - [Usage](#usage)
11
- - [Basic Setup](#basic-setup)
12
- - [Set the background color for the engine](#set-the-background-color-for-the-engine)
13
- - [Drawing the scene](#drawing-the-scene)
14
- - [Scene](#scene)
15
- - [Adding and removing items from the scene](#adding-and-removing-items-from-the-scene)
16
- - [Set Active Scene](#set-active-scene)
17
- - [Game Objects](#game-objects)
18
- - [Creating a new GameObject](#creating-a-new-gameobject)
19
- - [Components](#components)
20
- - [Texture](#texture)
21
- - [InstancedTexture](#instancedtexture)
22
- - [Square2D](#square2d)
23
- - [Triangle2D](#triangle2d)
24
- - [Circle2D](#circle2d)
25
- - [RigidBody](#rigidbody)
26
- - [BoxCollider](#boxcollider)
27
- - [CircleCollider](#circlecollider)
28
- - [Object methods](#object-methods)
29
- - [Position](#position)
30
- - [Rotation](#rotation)
31
- - [Scale](#scale)
32
- - [Change color](#change-color)
33
- - [Set texture frame](#set-texture-frame)
34
- - [Animations](#animations)
35
- - [Instance System](#instance-system)
36
- - [Creating Instances](#creating-instances)
37
- - [Instance Management](#instance-management)
38
- - [Instance Events](#instance-events)
39
- - [Physics Engine](#physics-engine)
40
- - [Setting up Physics](#setting-up-physics)
41
- - [Physics Bodies](#physics-bodies)
42
- - [Collision Detection](#collision-detection)
43
- - [Collision Events](#collision-events)
44
- - [Particle System](#particle-system)
45
- - [Particle Settings](#particle-settings)
46
- - [Creating Particle Systems](#creating-particle-systems)
47
- - [Particle System Methods](#particle-system-methods)
48
- - [Lighting System](#lighting-system)
49
- - [Ambient Light](#ambient-light)
50
- - [Point Light](#point-light)
51
- - [Directional Light](#directional-light)
52
- - [Text Rendering](#text-rendering)
53
- - [BitmapText](#bitmaptext)
54
- - [EventManager](#eventmanager)
55
- - [Keyboard Events](#keyboard-events)
56
- - [Mouse Events](#mouse-events)
57
- - [Object Events](#object-events)
58
- - [Event Cleanup](#event-cleanup)
59
- - [AudioManager](#audiomanager)
60
- - [Adding Audio](#adding-audio)
61
- - [Playing Audio](#playing-audio)
62
- - [Audio Control](#audio-control)
63
- - [Camera](#camera)
64
- - [FPSCounter](#fpscounter)
65
- - [Scene Management](#scene-management)
66
- - [Time Management](#time-management)
67
- - [Advanced Features](#advanced-features)
68
- - [Resize Handling](#resize-handling)
69
-
70
- ## Getting Started
71
-
72
- To get started with Emerald, you need to have a canvas element in your HTML and import the necessary classes.
73
-
74
- ## Usage
75
-
76
- ### Basic Setup
77
-
78
- ```javascript
79
- import { Emerald } from "./emerald/Emerald";
80
- import { Scene } from "./emerald/Scene";
81
- import { Color } from "./emerald/Color";
82
- import SceneManager from "./emerald/managers/SceneManager";
83
-
84
- const emerald = new Emerald(canvas); // You should pass your own canvas element here
85
- const scene = new Scene();
86
- SceneManager.setScene(scene);
87
- ```
88
-
89
- ### Set the background color for the engine
90
-
91
- ```javascript
92
- emerald.setBackgroundColor(color); // color = new Color(r, g, b, a = 255);
93
- ```
94
-
95
- ### Drawing the scene
96
-
97
- To draw items in the screen you need some sort of animation loop. I use `window.requestAnimationFrame` for this. Here is a basic example:
98
-
99
- ```javascript
100
- let lastTime = 0;
101
- const animate = (currentTime) => {
102
- const deltaTime = (currentTime - lastTime) / 1000;
103
- lastTime = currentTime;
104
- emerald.drawScene(scene, deltaTime); // You need this line to tell the engine what to draw
105
- window.requestAnimationFrame(animate);
106
- };
107
- animate(0);
108
- ```
109
-
110
- ## Scene
111
-
112
- Emerald has multiple scenes support. In order to render any object it has to be added to the scene using the `add` method.
113
-
114
- ### Adding and removing items from the scene
115
-
116
- ```javascript
117
- // Adding an object to the scene
118
- scene.add(gameObject);
119
-
120
- // Removing an object from the scene
121
- scene.remove(gameObject);
122
- ```
123
-
124
- ### Set Active Scene
125
-
126
- ```javascript
127
- // When changing a scene you should deactivate current scene to not mess up the event manager.
128
-
129
- // Activate Scene
130
- scene.setIsActive(true);
131
-
132
- // Deactivate Scene
133
- scene.setIsActive(false);
134
- ```
135
-
136
- ## Game Objects
137
-
138
- ### Creating a new GameObject
139
-
140
- ```javascript
141
- import GameObject from "./emerald/components/GameObject";
142
- import { Vector3, Vector2 } from "./emerald/Physics";
143
-
144
- /*
145
- ARGUMENTS:
146
- 1. name: string = Name of the new GameObject
147
- 2. position: Vector3 = Position of the new GameObject
148
- 3. rotation: number = Rotation of the new GameObject
149
- 4. scale: Vector2 = Scale of the new GameObject
150
- */
151
- const gameObject = new GameObject(name, position, rotation, scale);
152
- ```
153
-
154
- This will create a new empty GameObject. At this stage you will not see anything on the screen until you add some components.
155
-
156
- ### Components
157
-
158
- There are currently 8 components: Texture, InstancedTexture, Square2D, Circle2D, Triangle2D, RigidBody, BoxCollider, CircleCollider
159
-
160
- #### Texture
161
-
162
- ```javascript
163
- import { Texture } from "./emerald/Texture";
164
-
165
- /*
166
- ARGUMENTS:
167
- 1. texturePath = Specify the path for the texture that you want to use.
168
- 2. frameWidth: number = The width of each frame.
169
- 3. frameHeight: number = The height of each frame.
170
- 4. framesPerRow: number = How many frames are in one row in your spritesheet.
171
- 5. totalFrames: number = How many total frames does your spritesheet have.
172
- 6. animationSpeed: number = Speed of change of every frame.
173
- 7. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
174
- 8. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
175
- 9. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
176
- */
177
- const texture = new Texture(
178
- texturePath,
179
- frameWidth,
180
- frameHeight,
181
- framesPerRow,
182
- totalFrames,
183
- animationSpeed,
184
- autoPlay,
185
- (pixelart = true),
186
- (useLighting = true)
187
- );
188
-
189
- // Add the texture to a game object
190
- gameObject.addComponent(texture);
191
- ```
192
-
193
- ![Texture](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/texture.png?raw=true)
194
-
195
- #### InstancedTexture
196
-
197
- InstancedTexture is perfect for rendering many objects with the same texture efficiently, such as tiles, particles, or repeating elements.
198
-
199
- ```javascript
200
- import { InstancedTexture } from "./emerald/InstancedTexture";
201
-
202
- /*
203
- ARGUMENTS:
204
- 1. texturePath = Specify the path for the texture that you want to use.
205
- 2. instanceCount: number = How many instances of the texture you want to create.
206
- 3. frameWidth: number = The width of each frame.
207
- 4. frameHeight: number = The height of each frame.
208
- 5. framesPerRow: number = How many frames are in one row in your spritesheet.
209
- 6. totalFrames: number = How many total frames does your spritesheet have.
210
- 7. animationSpeed: number = Speed of change of every frame.
211
- 8. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
212
- 9. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
213
- 10. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
214
- */
215
- const instancedTexture = new InstancedTexture(
216
- texturePath,
217
- instanceCount,
218
- frameWidth,
219
- frameHeight,
220
- framesPerRow,
221
- totalFrames,
222
- animationSpeed,
223
- autoPlay,
224
- (pixelart = true),
225
- (useLighting = true)
226
- );
227
-
228
- // Add the instanced texture to a game object
229
- gameObject.addComponent(instancedTexture);
230
- ```
231
-
232
- ![InstancedTexture](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/instancedtexture.png?raw=true)
233
-
234
- #### Square2D
235
-
236
- ```javascript
237
- import { Square2D } from "./emerald/Shapes";
238
-
239
- let square = new Square2D();
240
- gameObject.addComponent(square);
241
- ```
242
-
243
- ![Square2D](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/square2d.png?raw=true)
244
-
245
- #### Triangle2D
246
-
247
- ```javascript
248
- import { Triangle2D } from "./emerald/Shapes";
249
-
250
- let triangle = new Triangle2D();
251
- gameObject.addComponent(triangle);
252
- ```
253
-
254
- ![Triangle2D](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/triangle2d.png?raw=true)
255
-
256
- #### Circle2D
257
-
258
- ```javascript
259
- import { Circle2D } from "./emerald/Shapes";
260
-
261
- /*
262
- ARGUMENTS:
263
- 1. segments = number of segments that the circle will have. Default is 32.
264
- */
265
- let circle = new Circle2D(segments);
266
- gameObject.addComponent(circle);
267
- ```
268
-
269
- ![Circle2D](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/circle2d.png?raw=true)
270
-
271
- #### RigidBody
272
-
273
- RigidBody is a component that allows you to add physics to your game objects. However, it won't work until you create a Physics instance at the top of your code.
274
-
275
- ```javascript
276
- import RigidBody from "./emerald/components/RigidBody";
277
- import { Physics, Vector2 } from "./emerald/Physics";
278
-
279
- // Create physics engine first
280
- const physics = new Physics(-70, 32, 2); // gravity, scale, velocityThreshold
281
-
282
- /*
283
- ARGUMENTS:
284
- 1. physics: Physics = Instance of the Physics class that you created at the top of your code.
285
- 2. type: string = Type of the rigid body. It can be "dynamic", "kinematic", or "static".
286
- 3. position: Vector2 = Position of the rigid body is Vector2 because it doesn't need any Z index.
287
- 4. fixedRotation: boolean = Specify whether the rigid body should have a fixed rotation or not. Default is false.
288
- 5. parentObject: GameObject = (OPTIONAL) If you want to attach the rigid body to a GameObject you can pass it here. If you don't want to attach it to any GameObject then pass null.
289
- 6. offset: Vector2 = (OPTIONAL) Offset from the GameObject's position.
290
- */
291
- const rigidBody = new RigidBody(
292
- physics,
293
- "dynamic",
294
- new Vector2(0, 0),
295
- false,
296
- gameObject,
297
- new Vector2(0, 0)
298
- );
299
-
300
- gameObject.addComponent(rigidBody);
301
- ```
302
-
303
- #### BoxCollider
304
-
305
- ```javascript
306
- import BoxCollider from "./emerald/components/BoxCollider";
307
-
308
- /*
309
- ARGUMENTS:
310
- 1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
311
- 2. size: Vector2 = Size of the box collider.
312
- 3. density: number = Density of the collider.
313
- 4. friction: number = Friction of the collider.
314
- 5. restitution: number = Restitution (bounciness) of the collider.
315
- 6. isSensor: boolean = Whether this collider is a sensor (triggers events but doesn't collide physically).
316
- 7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
317
- */
318
- const boxCollider = new BoxCollider(
319
- rigidBody,
320
- new Vector2(1, 1),
321
- 1,
322
- 0.3,
323
- 0.1,
324
- false,
325
- gameObject
326
- );
327
-
328
- gameObject.addComponent(boxCollider);
329
- ```
330
-
331
- ![BoxCollider](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/boxcollider.png?raw=true)
332
-
333
- The `BoxCollider` is specifically made bigger than the `Square2D` component in this image to demonstrate how it works. You can adjust the size of the collider to fit your needs.
334
-
335
- #### CircleCollider
336
-
337
- ```javascript
338
- import CircleCollider from "./emerald/components/CircleCollider";
339
-
340
- /*
341
- ARGUMENTS:
342
- 1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
343
- 2. radius: number = Radius of the circle collider.
344
- 3. density: number = Density of the collider.
345
- 4. friction: number = Friction of the collider.
346
- 5. restitution: number = Restitution (bounciness) of the collider.
347
- 6. isSensor: boolean = Whether this collider is a sensor.
348
- 7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
349
- */
350
- const circleCollider = new CircleCollider(
351
- rigidBody,
352
- 1.5,
353
- 1,
354
- 0.3,
355
- 0.8,
356
- false,
357
- gameObject
358
- );
359
-
360
- gameObject.addComponent(circleCollider);
361
- ```
362
-
363
- ![CircleCollider](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/circlecollider.png?raw=true)
364
-
365
- The `CircleCollider` is specifically made bigger than the `Circle2D` component in this image to demonstrate how it works. You can adjust the radius of the collider to fit your needs.
366
-
367
- ## Object methods
368
-
369
- ### Position
370
-
371
- ```javascript
372
- // Set position
373
- gameObject.transform.position.x = 100;
374
- gameObject.transform.position.y = 200;
375
- gameObject.transform.position.z = 0;
376
-
377
- // Or set all at once
378
- gameObject.transform.position = new Vector3(100, 200, 0);
379
- ```
380
-
381
- ![Position](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/position.gif?raw=true)
382
-
383
- ### Rotation
384
-
385
- ```javascript
386
- // Set rotation (in radians)
387
- gameObject.transform.rotation = Math.PI / 4; // 45 degrees
388
- ```
389
-
390
- ![Rotation](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/rotation.gif?raw=true)
391
-
392
- ### Scale
393
-
394
- ```javascript
395
- // Set scale
396
- gameObject.transform.scale.x = 2;
397
- gameObject.transform.scale.y = 2;
398
-
399
- // Or set both at once
400
- gameObject.transform.scale = new Vector2(2, 2);
401
- ```
402
-
403
- ![Scale](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/scale.gif?raw=true)
404
-
405
- ### Change color
406
-
407
- ```javascript
408
- // For textures
409
- const texture = gameObject.getComponent(Texture);
410
- texture.setColor(new Color(255, 0, 0)); // Red
411
- ```
412
-
413
- ![Change Color](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/changecolor.gif?raw=true)
414
-
415
- ### Set texture frame
416
-
417
- ```javascript
418
- // For animated textures
419
- const texture = gameObject.getComponent(Texture);
420
- texture.setFrame(2); // Set to frame 2
421
- ```
422
-
423
- ## Animations
424
-
425
- ```javascript
426
- // Play animation
427
- texture.playAnimation([0, 1, 2, 3], 200); // frames array, speed in ms
428
-
429
- // Stop animation
430
- texture.stopAnimation();
431
-
432
- // Check if playing
433
- if (texture.isPlaying) {
434
- // Animation is currently playing
435
- }
436
- ```
437
-
438
- ![Animations](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/animations.gif?raw=true)
439
-
440
- ## Instance System
441
-
442
- The Instance system allows you to efficiently manage multiple copies of the same object.
443
-
444
- ### Creating Instances
445
-
446
- ```javascript
447
- import Instance from "./emerald/Instance";
448
-
449
- // Create an instance
450
- const instance = new Instance(
451
- "InstanceName",
452
- new Vector3(x, y, z),
453
- new Vector2(width, height),
454
- rotation,
455
- frame
456
- );
457
-
458
- // Add to InstancedTexture
459
- const instancedTexture = gameObject.getComponent(InstancedTexture);
460
- instancedTexture.addInstance(instance);
461
- ```
462
-
463
- ### Instance Management
464
-
465
- ```javascript
466
- // Remove instance
467
- instancedTexture.removeInstance(instanceId);
468
-
469
- // Get instance by ID
470
- const instance = instancedTexture.getInstanceWithId(instanceId);
471
-
472
- // Get instance at position
473
- const instance = instancedTexture.getInstanceAtPosition(position, tolerance);
474
-
475
- // Clear all instances
476
- instancedTexture.clearInstances();
477
- ```
478
-
479
- ### Instance Events
480
-
481
- ```javascript
482
- // Add click event to specific instance
483
- instancedTexture.addInstanceClickEvent(instanceId, (event) => {
484
- console.log("Instance clicked!");
485
- });
486
-
487
- // Add hover events to specific instance
488
- instancedTexture.addInstanceHoverEvent(
489
- instanceId,
490
- (event) => console.log("Mouse entered"),
491
- (event) => console.log("Mouse left")
492
- );
493
- ```
494
-
495
- ## Physics Engine
496
-
497
- Emerald includes a comprehensive physics engine built on top of Planck.js.
498
-
499
- ### Setting up Physics
500
-
501
- ```javascript
502
- import { Physics } from "./emerald/Physics";
503
-
504
- /*
505
- ARGUMENTS:
506
- 1. gravity: number = Gravity force (negative for downward)
507
- 2. scale: number = Scale factor for physics units to pixels
508
- 3. velocityThreshold: number = Minimum velocity threshold
509
- */
510
- const physics = new Physics(-70, 32, 2);
511
- ```
512
-
513
- ### Physics Bodies
514
-
515
- ```javascript
516
- // Get the physics body from a RigidBody component
517
- const body = rigidBody.getBody();
518
-
519
- // Set velocity
520
- body.setLinearVelocity(new Vector2(10, 0));
521
-
522
- // Get velocity
523
- const velocity = body.getLinearVelocity();
524
-
525
- // Get position
526
- const position = body.getPosition();
527
- ```
528
-
529
- ### Collision Detection
530
-
531
- ```javascript
532
- // Handle collision enter
533
- physics.onCollisionEnter((bodyA, bodyB, contact) => {
534
- console.log("Collision started!");
535
-
536
- // Get collision normal
537
- const normal = contact.getWorldManifold().normal;
538
- //normal.y = -1 when player is on the ground
539
- //normal.y = 1 when player hits the ceiling
540
- //normal.x = -1 when player hits the left wall
541
- //normal.x = 1 when player hits the right wall
542
-
543
- // Check if bodies are sensors
544
- const fixtureA = contact.getFixtureA();
545
- const fixtureB = contact.getFixtureB();
546
- if (fixtureA.isSensor() || fixtureB.isSensor()) {
547
- // Handle sensor collision
548
- }
549
- });
550
-
551
- // Handle collision exit
552
- physics.onCollisionExit((bodyA, bodyB, contact) => {
553
- console.log("Collision ended!");
554
- });
555
- ```
556
-
557
- ### Collision Events
558
-
559
- ```javascript
560
- // Process physics in your update loop
561
- const animate = (currentTime) => {
562
- physics.process(deltaTime);
563
- };
564
- ```
565
-
566
- ## Particle System
567
-
568
- Emerald includes a powerful particle system for creating visual effects.
569
-
570
- ![Particles](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/particles.gif?raw=true)
571
-
572
- ### Particle Settings
573
-
574
- ```javascript
575
- import ParticleSettings from "./emerald/particlesystem/ParticleSettings";
576
-
577
- const particleSettings = new ParticleSettings({
578
- lifetime: 1.2,
579
- velocity: new Vector2(200, 300),
580
- gravity: new Vector2(0, -400),
581
- amount: 16,
582
- direction: new Vector2(0, 1), // upward
583
- spread: Math.PI * 2,
584
- emissionRate: Infinity, // one-shot emission
585
- frame: 0,
586
- offset: 5,
587
- rotation: 0,
588
- scale: new Vector2(5, 5),
589
- animation: { frames: [0, 1, 2], speed: 200 },
590
- });
591
- ```
592
-
593
- ### Creating Particle Systems
594
-
595
- ```javascript
596
- import Particles from "./emerald/particlesystem/Particles";
597
-
598
- /*
599
- ARGUMENTS:
600
- 1. name: string = Name of the particle system
601
- 2. texturePath: string = Path to the texture
602
- 3. frameWidth: number = Width of each frame
603
- 4. frameHeight: number = Height of each frame
604
- 5. framesPerRow: number = Frames per row in spritesheet
605
- 6. totalFrames: number = Total frames in spritesheet
606
- 7. duration: number = Duration of the effect
607
- 8. settings: ParticleSettings = Particle settings object
608
- */
609
- const particles = new Particles(
610
- "explosion",
611
- texturePath,
612
- 16,
613
- 16,
614
- 9,
615
- 27,
616
- 1.2,
617
- particleSettings
618
- );
619
-
620
- // Add to scene
621
- scene.add(particles.gameObject);
622
- ```
623
-
624
- ### Particle System Methods
625
-
626
- ```javascript
627
- // Play particle effect at position
628
- particles.play(new Vector3(x, y, z));
629
-
630
- // Stop particle system
631
- particles.stop();
632
-
633
- // Reset particle system
634
- particles.reset();
635
-
636
- // Update particles (call in your animation loop)
637
- particles.update(deltaTime);
638
-
639
- // Check if active
640
- if (particles.active) {
641
- // Particles are currently active
642
- }
643
- ```
644
-
645
- ## Lighting System
646
-
647
- Emerald supports ambient, point, and directional lighting.
648
-
649
- ### Ambient Light
650
-
651
- ```javascript
652
- // Set ambient light
653
- emerald.setAmbientLight(new Vector3(0.3, 0.3, 0.3)); // RGB values 0-1
654
- ```
655
-
656
- ### Point Light
657
-
658
- ```javascript
659
- import PointLight from "./emerald/lights/PointLight";
660
-
661
- /*
662
- ARGUMENTS:
663
- 1. position: Vector2 = Position of the light
664
- 2. color: Color = Color of the light
665
- 3. intensity: number = Light intensity
666
- 4. radius: number = Light radius
667
- */
668
- const pointLight = new PointLight(
669
- new Vector2(100, 0),
670
- new Color(255, 204, 153),
671
- 1.5,
672
- 400
673
- );
674
-
675
- // Add to engine
676
- emerald.addPointLight(pointLight);
677
-
678
- // Update position
679
- pointLight.position.x = newX;
680
- pointLight.position.y = newY;
681
- ```
682
-
683
- ### Directional Light
684
-
685
- ```javascript
686
- import DirectionalLight from "./emerald/lights/DirectionalLight";
687
-
688
- /*
689
- ARGUMENTS:
690
- 1. position: Vector2 = Position of the light
691
- 2. direction: Vector2 = Direction vector
692
- 3. color: Color = Color of the light
693
- 4. intensity: number = Light intensity
694
- 5. width: number = Width of the light beam
695
- */
696
- const directionalLight = new DirectionalLight(
697
- new Vector2(0, 300),
698
- new Vector2(0, -1), // pointing down
699
- new Color(255, 255, 255),
700
- 3.0,
701
- 200
702
- );
703
-
704
- // Add to engine
705
- emerald.addDirectionalLight(directionalLight);
706
-
707
- // Rotate direction
708
- const angle = 0.1;
709
- const newX =
710
- directionalLight.direction.x * Math.cos(angle) -
711
- directionalLight.direction.y * Math.sin(angle);
712
- const newY =
713
- directionalLight.direction.x * Math.sin(angle) +
714
- directionalLight.direction.y * Math.cos(angle);
715
- directionalLight.direction.x = newX;
716
- directionalLight.direction.y = newY;
717
- ```
718
-
719
- ## Text Rendering
720
-
721
- ### BitmapText
722
-
723
- Emerald supports bitmap font rendering using the BitmapText component. This allows you to display text with custom fonts and styles.
724
-
725
- ![BitmapText](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/bitmaptext.png?raw=true)
726
-
727
- ```javascript
728
- import BitmapText from "./emerald/BitmapText";
729
-
730
- /*
731
- ARGUMENTS:
732
- 1. text: string = Text to display
733
- 2. texturePath: string = Path to bitmap font texture
734
- 3. letters: string = String containing all available characters
735
- 4. letterSpacing: number = Spacing between letters
736
- 5. frameWidth: number = Width of each character frame
737
- 6. frameHeight: number = Height of each character frame
738
- 7. framesPerRow: number = Characters per row in font texture
739
- 8. totalFrames: number = Total character frames
740
- 9. pixelArt: boolean = Whether to use pixel art rendering
741
- 10. fontSize: number = Font size
742
- 11. color: Color = Text color
743
- 12. position: Vector3 = Text position
744
- 13. rotation: number = Text rotation
745
- 14. useLighting: boolean = Whether text should react to lighting
746
- */
747
- const bitmapText = new BitmapText(
748
- "Hello World!",
749
- fontTexturePath,
750
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?.",
751
- 16,
752
- 32,
753
- 32,
754
- 10,
755
- 95,
756
- true,
757
- 24,
758
- new Color(255, 255, 255),
759
- new Vector3(0, 200, 0),
760
- 0,
761
- false
762
- );
763
-
764
- // Add to scene
765
- scene.add(bitmapText.gameObject);
766
-
767
- // Update text
768
- bitmapText.setText("New Text!");
769
- bitmapText.setColor(new Color(255, 0, 0));
770
- bitmapText.setFontSize(32);
771
- bitmapText.setLetterSpacing(20);
772
- ```
773
-
774
- ## EventManager
775
-
776
- Emerald supports keyboard, mouse, click, and hover events. All events are handled using the built-in EventManager class.
777
-
778
- ![EventManager](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/eventmanager.gif?raw=true)
779
-
780
- ```javascript
781
- import EventManager from "./emerald/managers/EventManager";
782
-
783
- let eventManager = new EventManager(canvas, scene, emerald.camera);
784
- ```
785
-
786
- ### Keyboard Events
787
-
788
- ```javascript
789
- // Key down events
790
- eventManager.addKeyDown("w", () => {
791
- console.log("W key pressed");
792
- });
793
-
794
- // Key up events
795
- eventManager.addKeyUp("w", () => {
796
- console.log("W key released");
797
- });
798
-
799
- // Check if key is currently pressed
800
- if (eventManager.isKeyPressed("w")) {
801
- // W key is currently held down
802
- }
803
-
804
- // Remove key events
805
- eventManager.removeKeyDown("w", callbackFunction);
806
- eventManager.removeKeyUp("w", callbackFunction);
807
- ```
808
-
809
- ### Mouse Events
810
-
811
- ```javascript
812
- // Get mouse position
813
- const mousePos = eventManager.getMousePosition();
814
- console.log(mousePos.x, mousePos.y);
815
-
816
- // Check if camera was moved
817
- if (eventManager.wasCameraMoved()) {
818
- // Camera was moved by dragging
819
- eventManager.resetCameraMoved();
820
- }
821
- ```
822
-
823
- ### Object Events
824
-
825
- ```javascript
826
- // Click events
827
- eventManager.addClickEvent(gameObject, (event, object) => {
828
- console.log("Object clicked!");
829
- });
830
-
831
- // Hover events
832
- eventManager.addHoverEvent(
833
- gameObject,
834
- (event) => {
835
- console.log("Mouse entered object");
836
- },
837
- (event) => {
838
- console.log("Mouse left object");
839
- }
840
- );
841
-
842
- // Remove events
843
- eventManager.removeClickEvent(gameObject, callbackFunction);
844
- eventManager.removeHoverEvent(gameObject, enterCallback, leaveCallback);
845
- ```
846
-
847
- ### Event Cleanup
848
-
849
- ```javascript
850
- // Clean up all events when done
851
- eventManager.clean();
852
-
853
- // Change scene
854
- eventManager.changeScene(newScene);
855
- ```
856
-
857
- ## AudioManager
858
-
859
- Emerald includes a comprehensive audio management system.
860
-
861
- ### Adding Audio
862
-
863
- ```javascript
864
- import AudioManager from "./emerald/managers/AudioManager";
865
-
866
- const audioManager = new AudioManager();
867
-
868
- // Add audio files
869
- audioManager.add("path/to/sound.wav", "soundName");
870
- audioManager.add("path/to/music.mp3", "backgroundMusic");
871
- ```
872
-
873
- ### Playing Audio
874
-
875
- ```javascript
876
- // Play audio
877
- audioManager.play("soundName");
878
-
879
- // Play exclusively (stops all other audio first)
880
- audioManager.playExclusive("soundName");
881
- ```
882
-
883
- ### Audio Control
884
-
885
- ```javascript
886
- // Stop specific audio
887
- audioManager.stop("soundName");
888
-
889
- // Stop all audio
890
- audioManager.stopAll();
891
-
892
- // Remove audio
893
- audioManager.remove("soundName");
894
-
895
- // Get audio object
896
- const sound = audioManager.getSound("soundName");
897
- ```
898
-
899
- ## Camera
900
-
901
- The engine has simple controls for the camera. The camera is stored in the emerald variable.
902
-
903
- ```javascript
904
- // Set camera position
905
- emerald.camera.setPosition(x, y, z);
906
-
907
- // Access camera transform directly
908
- emerald.camera.transform.position.x = 100;
909
- emerald.camera.transform.position.y = 200;
910
- emerald.camera.transform.scale.x = 1.5;
911
- emerald.camera.transform.scale.y = 1.5;
912
- ```
913
-
914
- ## FPSCounter
915
-
916
- Emerald has a built-in FPS counter.
917
-
918
- ```javascript
919
- import { FPSCounter } from "./emerald/FPSCounter";
920
-
921
- let fpsCounter = new FPSCounter();
922
-
923
- const animate = (currentTime) => {
924
- emerald.drawScene(scene, deltaTime);
925
- fpsCounter.update(); // Call this in your animation loop
926
- window.requestAnimationFrame(animate);
927
- };
928
- animate();
929
- ```
930
-
931
- ## Scene Management
932
-
933
- ```javascript
934
- import SceneManager from "./emerald/managers/SceneManager";
935
-
936
- // Set active scene
937
- SceneManager.setScene(scene);
938
-
939
- // Get current scene
940
- const currentScene = SceneManager.getScene();
941
- ```
942
-
943
- ## Time Management
944
-
945
- ```javascript
946
- import Time from "./emerald/Time";
947
-
948
- // Get delta time
949
- const deltaTime = Time.deltaTime;
950
-
951
- // Time is automatically updated when you call emerald.drawScene()
952
- // You can also manually set it
953
- Time.setDeltaTime(deltaTime);
954
- ```
955
-
956
- ## Advanced Features
957
-
958
- ### Resize Handling
959
-
960
- ```javascript
961
- // Handle window resize
962
- const handleResize = () => {
963
- const { width, height } = getCanvasDimensions();
964
- emerald.resize(width, height);
965
- };
966
-
967
- window.addEventListener("resize", handleResize);
968
- ```
1
+ # Emerald
2
+
3
+ Emerald is a comprehensive 2D graphics engine that can help you create games easier than ever.
4
+
5
+ **New to the engine?** Start with the [Getting Started guide](docs/getting-started.md) —
6
+ it walks from an empty page to a playable sprite with input, tiles, audio,
7
+ saves, and a debug overlay.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Emerald](#emerald)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [Getting Started](#getting-started)
14
+ - [Core Systems](#core-systems)
15
+ - [Sprites: flipping, pivot & anchor](#sprites-flipping-pivot--anchor)
16
+ - [Rendering Pipeline (post-processing, materials, batching)](#rendering-pipeline-post-processing-materials-batching)
17
+ - [Post-processing](#post-processing)
18
+ - [PostEffects (built-in)](#posteffects-built-in)
19
+ - [RenderTarget](#rendertarget)
20
+ - [Material (custom shaders)](#material-custom-shaders)
21
+ - [SpriteBatch](#spritebatch)
22
+ - [In-Engine UI](#in-engine-ui)
23
+ - [Game Loop & Scene Transitions](#game-loop--scene-transitions)
24
+ - [ScreenEffects (transitions)](#screeneffects-transitions)
25
+ - [Coroutines](#coroutines)
26
+ - [Gamepads & Controllers](#gamepads--controllers)
27
+ - [Per-instance color & tinting](#per-instance-color--tinting)
28
+ - [Physics: collision layers & continuous detection](#physics-collision-layers--continuous-detection)
29
+ - [RigidBody velocity helpers](#rigidbody-velocity-helpers)
30
+ - [Tilemap colliders & auto-tiling](#tilemap-colliders--auto-tiling)
31
+ - [Camera follow deadzone](#camera-follow-deadzone)
32
+ - [ParticleEmitter (pooled sprites)](#particleemitter-pooled-sprites)
33
+ - [Advanced Particles](#advanced-particles)
34
+ - [Positional (spatial) Audio](#positional-spatial-audio)
35
+ - [Audio buses & fades](#audio-buses--fades)
36
+ - [AssetManager](#assetmanager-loading)
37
+ - [Asset importers (Tiled & Aseprite)](#asset-importers-tiled--aseprite)
38
+ - [Networking (NetworkManager + Interpolator)](#networking-networkmanager--interpolator)
39
+ - [DebugOverlay (upgraded)](#debugoverlay-upgraded)
40
+ - [Storage (versioned saves)](#storage-versioned-saves)
41
+ - [EmeraldDB (IndexedDB saves)](#emeralddb-indexeddb-saves)
42
+ - [Resolution independence](#resolution-independence-design-resolution)
43
+ - [Auto-pause & lifecycle](#auto-pause--lifecycle)
44
+ - [Production hardening](#production-hardening-resource-lifecycle-context-loss-render-stats)
45
+ - [Usage](#usage)
46
+ - [Basic Setup](#basic-setup)
47
+ - [Set the background color for the engine](#set-the-background-color-for-the-engine)
48
+ - [Drawing the scene](#drawing-the-scene)
49
+ - [Scene](#scene)
50
+ - [Adding and removing items from the scene](#adding-and-removing-items-from-the-scene)
51
+ - [Set Active Scene](#set-active-scene)
52
+ - [Game Objects](#game-objects)
53
+ - [Creating a new GameObject](#creating-a-new-gameobject)
54
+ - [Components](#components)
55
+ - [Texture](#texture)
56
+ - [InstancedTexture](#instancedtexture)
57
+ - [Square2D](#square2d)
58
+ - [Triangle2D](#triangle2d)
59
+ - [Circle2D](#circle2d)
60
+ - [RigidBody](#rigidbody)
61
+ - [BoxCollider](#boxcollider)
62
+ - [CircleCollider](#circlecollider)
63
+ - [Object methods](#object-methods)
64
+ - [Position](#position)
65
+ - [Rotation](#rotation)
66
+ - [Scale](#scale)
67
+ - [Change color](#change-color)
68
+ - [Set texture frame](#set-texture-frame)
69
+ - [Animations](#animations)
70
+ - [Instance System](#instance-system)
71
+ - [Creating Instances](#creating-instances)
72
+ - [Instance Management](#instance-management)
73
+ - [Instance Events](#instance-events)
74
+ - [Physics Engine](#physics-engine)
75
+ - [Setting up Physics](#setting-up-physics)
76
+ - [Physics Bodies](#physics-bodies)
77
+ - [Collision Detection](#collision-detection)
78
+ - [Collision Events](#collision-events)
79
+ - [Particle System](#particle-system)
80
+ - [Particle Settings](#particle-settings)
81
+ - [Creating Particle Systems](#creating-particle-systems)
82
+ - [Particle System Methods](#particle-system-methods)
83
+ - [Lighting System](#lighting-system)
84
+ - [Ambient Light](#ambient-light)
85
+ - [Point Light](#point-light)
86
+ - [Directional Light](#directional-light)
87
+ - [Text Rendering](#text-rendering)
88
+ - [BitmapText](#bitmaptext)
89
+ - [EventManager](#eventmanager)
90
+ - [Keyboard Events](#keyboard-events)
91
+ - [Mouse Events](#mouse-events)
92
+ - [Object Events](#object-events)
93
+ - [Event Cleanup](#event-cleanup)
94
+ - [AudioManager](#audiomanager)
95
+ - [Adding Audio](#adding-audio)
96
+ - [Playing Audio](#playing-audio)
97
+ - [Audio Control](#audio-control)
98
+ - [Camera](#camera)
99
+ - [FPSCounter](#fpscounter)
100
+ - [Scene Management](#scene-management)
101
+ - [Time Management](#time-management)
102
+ - [Advanced Features](#advanced-features)
103
+ - [Resize Handling](#resize-handling)
104
+
105
+ ## Getting Started
106
+
107
+ To get started with Emerald, you need to have a canvas element in your HTML and import the necessary classes.
108
+
109
+ ## Core Systems
110
+
111
+ Everything below ships from the package root (`import { ... } from "emeraldengine"`).
112
+
113
+ ### Game loop & Time
114
+
115
+ `Emerald.drawScene(scene, dt)` renders the scene and also advances global `Tween`/`Timer` updates using the time-scaled delta. `Time` is updated for you each call.
116
+
117
+ ```javascript
118
+ import { Time } from "emeraldengine";
119
+ Time.getDeltaTime(); // time-scaled seconds since last frame
120
+ Time.getUnscaledDeltaTime(); // raw delta, ignores timeScale
121
+ Time.getElapsedTime(); // total accumulated time
122
+ Time.setTimeScale(0.5); // 0 = paused, 1 = normal, 2 = double speed
123
+ ```
124
+
125
+ ### Behaviour (component lifecycle)
126
+
127
+ ```javascript
128
+ import { Behaviour } from "emeraldengine";
129
+
130
+ class Spinner extends Behaviour {
131
+ start() {
132
+ this.speed = 2;
133
+ } // once, before first update
134
+ update(dt) {
135
+ this.gameObject.transform.rotation += this.speed * dt;
136
+ }
137
+ onCollisionEnter(other, contact) {} // requires physics ticking
138
+ onCollisionExit(other, contact) {}
139
+ onDestroy() {}
140
+ }
141
+ obj.addComponent(new Spinner());
142
+ scene.update(dt); // ticks all Behaviours on active objects
143
+ ```
144
+
145
+ ### Transform hierarchy
146
+
147
+ ```javascript
148
+ parent.addChild(child); // or child.setParent(parent)
149
+ child.setParent(null); // detach
150
+ // child.transform composes on top of the parent's position/rotation/scale
151
+ ```
152
+
153
+ ### Cameras & split-screen
154
+
155
+ ```javascript
156
+ import { Camera, CameraController } from "emeraldengine";
157
+
158
+ // Multiple cameras, each with a normalized viewport (origin bottom-left)
159
+ const top = new Camera({ viewport: { x: 0, y: 0.5, width: 1, height: 0.5 } });
160
+ const bottom = new Camera({ viewport: { x: 0, y: 0, width: 1, height: 0.5 } });
161
+ emerald.setCameras([top, bottom]); // or emerald.addCamera(cam) / removeCamera(cam)
162
+
163
+ camera.setZoom(1.5);
164
+ camera.setPosition(x, y);
165
+ camera.clearColor = new Color(10, 14, 20); // optional per-viewport clear
166
+
167
+ // Per-camera layer visibility (e.g. translucent opponent in 2-player)
168
+ top.setIgnoreLayers([11, 20]); // skip these object layers in this camera
169
+
170
+ // Smooth follow / bounds / shake
171
+ const cam = new CameraController(emerald.camera);
172
+ cam.follow(player, 0.12).setBounds(-1000, -1000, 1000, 1000);
173
+ cam.shake(10, 0.3);
174
+ // each frame: cam.update(dt);
175
+ ```
176
+
177
+ ### Input
178
+
179
+ ```javascript
180
+ import { InputManager } from "emeraldengine";
181
+ const input = new InputManager();
182
+ input.mapAction("jump", ["Space", " ", "pad:0:south"]); // keyboard + gamepad
183
+ input.mapAction("left", ["a", "ArrowLeft", "pad:0:dpadLeft"]);
184
+ // in the loop:
185
+ if (input.justPressed("jump")) player.jump();
186
+ const move = input.getAxis("left", "right"); // -1 / 0 / +1
187
+ input.update(); // call once per frame (edge detection + gamepad polling)
188
+ ```
189
+
190
+ Gamepad tokens (`pad:0:south`, sticks, triggers, rumble, mapping for non-standard
191
+ pads) are documented in [Gamepads & Controllers](#gamepads--controllers).
192
+
193
+ `EventManager` handles per-object click/hover; it now hits only the **topmost** object and `screenToWorld(clientX, clientY)` accounts for camera zoom, DPR, and viewport.
194
+
195
+ ### Tween & Easing
196
+
197
+ ```javascript
198
+ import { Tween, Easing } from "emeraldengine";
199
+ Tween.to(sprite.transform.position, { x: 200, y: -50 }, 0.6, {
200
+ easing: Easing.outBack, // linear, inOutQuad, outCubic, outBounce, outElastic, ...
201
+ delay: 0,
202
+ loop: false,
203
+ yoyo: false,
204
+ onUpdate: (t) => {},
205
+ onComplete: () => {},
206
+ }).then(() => console.log("done"));
207
+ Tween.killOf(target);
208
+ Tween.killAll();
209
+ // driven automatically by drawScene
210
+ ```
211
+
212
+ ### Timer
213
+
214
+ ```javascript
215
+ import { Timer } from "emeraldengine";
216
+ Timer.after(2, () => spawnEnemy()); // once
217
+ const h = Timer.every(0.5, () => tick(), 10); // 10 times (omit count = forever)
218
+ Timer.clear(h);
219
+ Timer.clearAll();
220
+ ```
221
+
222
+ ### StateMachine
223
+
224
+ ```javascript
225
+ import { StateMachine } from "emeraldengine";
226
+ const fsm = new StateMachine();
227
+ fsm.add("idle", {
228
+ update: (dt, sm) => {
229
+ if (seen) sm.set("chase");
230
+ },
231
+ });
232
+ fsm.add("chase", { enter: () => roar(), update: (dt) => move(dt) });
233
+ fsm.set("idle");
234
+ // in update(dt): fsm.update(dt); -> fsm.is("chase")
235
+ ```
236
+
237
+ ### SpatialGrid
238
+
239
+ ```javascript
240
+ import { SpatialGrid } from "emeraldengine";
241
+ const grid = new SpatialGrid(64);
242
+ grid.clear();
243
+ for (const e of enemies)
244
+ grid.insert(e, e.transform.position.x, e.transform.position.y);
245
+ const near = grid.queryRadius(px, py, 100); // or grid.queryRect(...)
246
+ ```
247
+
248
+ ### Pool
249
+
250
+ ```javascript
251
+ import { Pool } from "emeraldengine";
252
+ const bullets = new Pool(
253
+ () => new Bullet(),
254
+ (b, x, y) => b.spawn(x, y),
255
+ 50
256
+ );
257
+ const b = bullets.acquire(px, py);
258
+ bullets.release(b); // bullets.releaseAll()
259
+ ```
260
+
261
+ ### MathUtils
262
+
263
+ ```javascript
264
+ import { MathUtils } from "emeraldengine";
265
+ MathUtils.clamp(v, 0, 1);
266
+ MathUtils.lerp(a, b, t);
267
+ MathUtils.map(v, 0, 10, 0, 100);
268
+ MathUtils.degToRad(90);
269
+ MathUtils.randomRange(0, 5);
270
+ MathUtils.randomInt(1, 6);
271
+ MathUtils.distance(a, b);
272
+ MathUtils.normalize(v);
273
+ MathUtils.angleBetween(a, b);
274
+ ```
275
+
276
+ ### Physics: fixed timestep, per-object collisions, raycast
277
+
278
+ ```javascript
279
+ physics.setFixedTimeStep(1 / 60); // stable, frame-rate independent
280
+ physics.process(dt); // accumulator-based stepping
281
+
282
+ // Per-object events fire on Behaviour components automatically (see Behaviour).
283
+ const hit = physics.raycast({ x, y }, { x: 1, y: 0 }, 500);
284
+ // -> { object, rigidBody, point, normal, fraction } | null
285
+ const objects = physics.queryPoint({ x, y }); // owners whose collider contains the point
286
+ ```
287
+
288
+ ### Rendering extras
289
+
290
+ ```javascript
291
+ // Layers (sorted before z) and screen-space HUD
292
+ obj.setLayer(10);
293
+ hudObj.setScreenSpace(true); // ignores camera; position is pixels from viewport center
294
+
295
+ // Blend modes (per drawable)
296
+ drawable.setBlendMode("additive"); // "normal" | "additive" | "multiply"
297
+
298
+ // Off-screen culling (on by default): objects outside the view are skipped.
299
+ emerald.setCullingEnabled(true);
300
+ particles.alwaysVisible = true; // opt an object out (e.g. emitters with spread)
301
+
302
+ // Texture atlas
303
+ import { TextureAtlas } from "emeraldengine";
304
+ const atlas = await TextureAtlas.load("sheet.png", sheetJson, true);
305
+ atlas.applyTo(sprite, "player_idle_0"); // sets a UV sub-rect on the Drawable
306
+
307
+ // Dynamic system-font text
308
+ import { CanvasText } from "emeraldengine";
309
+ const label = CanvasText.create("Score: 0", {
310
+ font: "bold 28px monospace",
311
+ color: "#6ee7b7",
312
+ screenSpace: true,
313
+ });
314
+ scene.add(label);
315
+ label.getComponent(CanvasText).setText("Score: 120");
316
+
317
+ // Shared/cached GL textures
318
+ import { TextureManager } from "emeraldengine";
319
+ await TextureManager.preload(["a.png", "b.png"], true);
320
+ ```
321
+
322
+ ### Animator, Tilemap, Particles
323
+
324
+ ```javascript
325
+ import { Animator, Tilemap } from "emeraldengine";
326
+
327
+ const anim = new Animator();
328
+ anim
329
+ .addClip("run", [0, 1, 2, 3], { speed: 100 })
330
+ .addClip("jump", [8, 9], { loop: false });
331
+ obj.addComponent(texture);
332
+ obj.addComponent(anim);
333
+ anim.play("run");
334
+
335
+ const map = new Tilemap("level", "tiles.png", {
336
+ tileSize: 32,
337
+ frameWidth: 16,
338
+ frameHeight: 16,
339
+ framesPerRow: 4,
340
+ totalFrames: 16,
341
+ });
342
+ map.setMap([
343
+ [0, 0, 0],
344
+ [1, -1, 1],
345
+ ]); // -1 = empty; one instanced draw call
346
+ scene.add(map.gameObject);
347
+ ```
348
+
349
+ ### AudioManager
350
+
351
+ ```javascript
352
+ audio.add("jump.wav", "jump", { volume: 0.8, loop: false });
353
+ audio.play("jump"); // restart from 0
354
+ audio.playOverlap("jump"); // overlapping copies for rapid SFX
355
+ audio.setMasterVolume(0.5);
356
+ audio.setBusVolume("music", 0.6); // mix buses: master * bus * sound
357
+ audio.crossfade("theme", "boss", 2.0); // timed fades: fadeIn/fadeOut/fadeTo too
358
+ ```
359
+
360
+ Full mixing details: [Audio buses & fades](#audio-buses--fades).
361
+
362
+ ### Serializer (save/load scenes)
363
+
364
+ ```javascript
365
+ import { Serializer } from "emeraldengine";
366
+ Serializer.register("coin", (data) => makeCoin(data.value));
367
+ coin.prefabType = "coin";
368
+ coin.serialize = () => ({ value: 5 });
369
+ const json = Serializer.toJSON(scene); // save
370
+ Serializer.fromJSON(json, new Scene()); // load
371
+ ```
372
+
373
+ ### DebugOverlay
374
+
375
+ ```javascript
376
+ import { DebugOverlay } from "emeraldengine";
377
+ const debug = new DebugOverlay();
378
+ // after drawScene: debug.update(emerald, scene); // FPS / objects / cameras
379
+ ```
380
+
381
+ ### NPM scripts
382
+
383
+ | Script | Purpose |
384
+ | ----------------- | -------------------------------------------- |
385
+ | `npm test` | Node test suite (`node --test test/`) |
386
+ | `npm run types` | Regenerate `dist/types` from JSDoc via `tsc` |
387
+ | `npm run format` | Prettier |
388
+
389
+ ## Sprites: flipping, pivot & anchor
390
+
391
+ Any `Texture` (or other `Drawable`) can be mirrored and re-pivoted without
392
+ touching the GameObject's scale — handy for characters that face left/right and
393
+ for putting a sprite's origin at its feet.
394
+
395
+ ```javascript
396
+ const tex = gameObject.getComponent(Texture);
397
+
398
+ tex.setFlipX(facing < 0); // mirror horizontally (e.g. face left)
399
+ tex.setFlipY(true); // mirror vertically
400
+
401
+ // Pivot: which local point sits on the GameObject's position and acts as the
402
+ // rotation/scale center. (0,0) = center (default); x in [-1,1] left..right,
403
+ // y in [-1,1] bottom..top.
404
+ tex.setPivot(0, -1); // bottom-center — feet on the ground
405
+
406
+ // Anchor: the same thing in 0..1 with a top-left origin (CSS-style).
407
+ tex.setAnchor(0.5, 1); // bottom-center
408
+ tex.setAnchor(0.5, 0.5); // back to center
409
+ ```
410
+
411
+ ## Rendering Pipeline (post-processing, materials, batching)
412
+
413
+ Everything in this section ships from the package root: `import { ... } from "emeraldengine"`.
414
+
415
+ ### Post-processing
416
+
417
+ When post-processing is enabled, Emerald renders the whole scene into an offscreen
418
+ texture and then runs a chain of full-screen shader passes (ping-ponging between two
419
+ render targets) before drawing the final image to the canvas. You manage it entirely
420
+ through the `Emerald` instance:
421
+
422
+ ```javascript
423
+ emerald.enablePostProcessing(); // allocate the scene render target + processor
424
+ emerald.disablePostProcessing(); // turn it back off
425
+
426
+ const bloom = emerald.addPostEffect(PostEffects.bloom()); // returns the effect
427
+ emerald.removePostEffect(bloom);
428
+
429
+ // Effects run in the order they were added. Toggle one without removing it:
430
+ bloom.enabled = false;
431
+ ```
432
+
433
+ `drawScene` automatically routes through the processor while any **enabled** effect
434
+ exists; if none do, it draws straight to the screen with zero overhead.
435
+
436
+ Write a custom pass by constructing a `PostEffect`. Your fragment shader gets
437
+ `vUV` (0–1 screen UV), `uScene` (the previous pass), `uResolution`, and `uTime`
438
+ for free — declare any extra uniforms and set them in `setUniforms`:
439
+
440
+ ```javascript
441
+ import { PostEffect } from "emeraldengine";
442
+
443
+ const tint = new PostEffect(
444
+ "tint",
445
+ `
446
+ uniform vec3 uTint;
447
+ void main() {
448
+ gl_FragColor = texture2D(uScene, vUV) * vec4(uTint, 1.0);
449
+ }`,
450
+ {
451
+ setUniforms: (gl, loc) => gl.uniform3f(loc("uTint"), 1.0, 0.85, 0.7),
452
+ enabled: true,
453
+ }
454
+ );
455
+ emerald.addPostEffect(tint);
456
+ ```
457
+
458
+ Keep UI crisp by rendering it on a camera **excluded from post-processing** —
459
+ it draws straight to the screen after the effect chain, so bloom never blows
460
+ out your buttons and text:
461
+
462
+ ```javascript
463
+ const uiCam = new Camera({ excludeFromPost: true }); // or uiCam.setExcludeFromPost(true)
464
+ emerald.addCamera(uiCam);
465
+ hudObject.setLayer(100); // and restrict cameras via setOnlyLayers/ignoreLayers
466
+ ```
467
+
468
+ ### PostEffects (built-in)
469
+
470
+ Factory functions on the `PostEffects` namespace return a ready `PostEffect`:
471
+
472
+ ```javascript
473
+ import { PostEffects } from "emeraldengine";
474
+
475
+ emerald.enablePostProcessing();
476
+ emerald.addPostEffect(
477
+ PostEffects.bloom({ threshold: 0.6, intensity: 1.2, spread: 1.1 })
478
+ );
479
+ emerald.addPostEffect(
480
+ PostEffects.vignette({ intensity: 0.5, radius: 0.75, softness: 0.45 })
481
+ );
482
+ emerald.addPostEffect(
483
+ PostEffects.colorGrade({ brightness: 0.02, contrast: 1.08, saturation: 1.15 })
484
+ );
485
+ emerald.addPostEffect(PostEffects.chromaticAberration({ amount: 0.003 }));
486
+ emerald.addPostEffect(PostEffects.scanlines({ intensity: 0.15, count: 480 }));
487
+ emerald.addPostEffect(
488
+ PostEffects.crt({ curvature: 4.0, scanlineIntensity: 0.2, vignette: 0.3 })
489
+ );
490
+ emerald.addPostEffect(PostEffects.grayscale());
491
+ ```
492
+
493
+ | Effect | Options (defaults) |
494
+ | --------------------- | ----------------------------------------------------------- |
495
+ | `bloom` | `threshold 0.7`, `intensity 1.0`, `spread 1.0` (multi-pass) |
496
+ | `vignette` | `intensity 0.5`, `radius 0.75`, `softness 0.45` |
497
+ | `colorGrade` | `brightness 0`, `contrast 1`, `saturation 1` |
498
+ | `chromaticAberration` | `amount 0.003` |
499
+ | `scanlines` | `intensity 0.15`, `count 480` |
500
+ | `crt` | `curvature 4.0`, `scanlineIntensity 0.2`, `vignette 0.3` |
501
+ | `grayscale` | — |
502
+
503
+ `bloom` is exported as a class too (`BloomEffect`) if you want to subclass it.
504
+
505
+ ### RenderTarget
506
+
507
+ An offscreen framebuffer backed by a color texture (and an optional depth buffer).
508
+ Used internally by the post-processor, but useful on its own for minimaps, mirrors,
509
+ or picture-in-picture.
510
+
511
+ ```javascript
512
+ import { RenderTarget } from "emeraldengine";
513
+
514
+ const rt = new RenderTarget(512, 512, { depth: false, pixelart: false });
515
+ rt.bind(); // binds the FBO and sets the viewport to its size
516
+ // ...draw...
517
+ rt.unbind(); // restore the canvas framebuffer
518
+ // rt.texture now holds the rendered image (a WebGLTexture)
519
+ rt.resize(1024, 1024); // reallocates only if the size changed
520
+ rt.dispose(); // free GL resources
521
+ ```
522
+
523
+ ### Material (custom shaders)
524
+
525
+ A `Material` replaces a Drawable's fragment shader while reusing the engine's
526
+ standard vertex shader, so transforms, the camera, and instancing keep working.
527
+ Your fragment program automatically has `vTexCoord`, `vFragPos`, `vInstanceColor`,
528
+ `uSampler`, `uColor`, `uOpacity`, and `uTime` — **don't redeclare them**; declare
529
+ any extra uniforms and push values with `set(name, value)`.
530
+
531
+ ```javascript
532
+ import { Material, Square2D } from "emeraldengine";
533
+
534
+ const dissolve = new Material(
535
+ `
536
+ uniform float uAmount;
537
+ void main() {
538
+ vec4 c = texture2D(uSampler, vTexCoord);
539
+ if (c.a < uAmount) discard;
540
+ gl_FragColor = c * uColor * uOpacity;
541
+ }
542
+ `,
543
+ { uniforms: { uAmount: 0.0 } }
544
+ );
545
+
546
+ const shape = new Square2D();
547
+ shape.setMaterial(dissolve); // any Drawable: Texture, Square2D, Circle2D…
548
+ gameObject.addComponent(shape);
549
+
550
+ // Animate a uniform (numbers, vec2/3/4 arrays, or functions are accepted):
551
+ dissolve.set("uAmount", 0.5);
552
+ dissolve.set("uPulse", () => 0.5 + 0.5 * Math.sin(performance.now() / 300));
553
+ ```
554
+
555
+ A pulsing glow material (as used by the race example's finish line):
556
+
557
+ ```javascript
558
+ const glow = new Material(`
559
+ void main() {
560
+ float pulse = 0.5 + 0.5 * sin(uTime * 4.0 + vTexCoord.y * 6.2831);
561
+ vec3 col = mix(vec3(0.40, 1.0, 0.70), vec3(1.0, 0.9, 0.4), pulse);
562
+ gl_FragColor = vec4(col, 0.5 * pulse);
563
+ }
564
+ `);
565
+ square.setMaterial(glow);
566
+ square.setBlendMode("additive");
567
+ ```
568
+
569
+ ### SpriteBatch
570
+
571
+ A dynamic batched renderer with its own minimal shader. Instead of one draw call
572
+ per sprite, it accumulates sprites that share a texture into a single interleaved
573
+ buffer and submits them in one `drawElements` call — ideal for many same-atlas
574
+ quads (bullets, tiles, text glyphs).
575
+
576
+ ```javascript
577
+ import { SpriteBatch } from "emeraldengine";
578
+
579
+ const batch = new SpriteBatch({ maxQuads: 2000 });
580
+ batch.begin(projectionMatrix, viewMatrix); // gl-matrix mat4 / Float32Array(16)
581
+ for (const e of entities) {
582
+ batch.draw({
583
+ texture: atlasTexture, // a WebGLTexture; changing it flushes the batch
584
+ x: e.x,
585
+ y: e.y,
586
+ w: 32,
587
+ h: 32,
588
+ rotation: e.angle,
589
+ originX: 0.5,
590
+ originY: 0.5,
591
+ u0: e.u0,
592
+ v0: e.v0,
593
+ u1: e.u1,
594
+ v1: e.v1, // UV sub-rect (defaults 0..1)
595
+ r: 1,
596
+ g: 1,
597
+ b: 1,
598
+ a: 1, // per-vertex tint
599
+ });
600
+ }
601
+ batch.end(); // flushes remaining sprites
602
+ console.log(batch.drawCalls); // GL draw calls emitted this frame
603
+ ```
604
+
605
+ ## In-Engine UI
606
+
607
+ `UI` is a retained-mode toolkit drawn entirely by the engine — no DOM/HTML overlay.
608
+ Elements are screen-space objects on a dedicated high layer with their own pointer
609
+ and keyboard hit-testing. Positions are **pixels from the viewport center (y up)**,
610
+ either a literal `{x, y}` or a responsive `(viewW, viewH) => ({x, y})` function;
611
+ call `relayout()` after a resize.
612
+
613
+ Pair it with a UI camera so the UI draws over the game and the game cameras skip the
614
+ UI layer:
615
+
616
+ ```javascript
617
+ import { UI } from "emeraldengine";
618
+
619
+ const uiCam = UI.createCamera(); // a full-screen camera that renders ONLY UI.LAYER
620
+ gameCamera.ignoreLayer(UI.LAYER); // keep the UI out of the game viewport(s)
621
+ emerald.setCameras([gameCamera, uiCam]); // add the UI camera last
622
+
623
+ const ui = new UI(scene, canvas, { accent: [120, 200, 255] });
624
+
625
+ // Labels (return a handle with setText)
626
+ const score = ui.label(() => ({ x: 0, y: 200 }), "Score: 0", {
627
+ font: "700 30px system-ui, sans-serif",
628
+ color: "#eaf2ff",
629
+ });
630
+ score.setText("Score: 120");
631
+
632
+ // Buttons (panel + centered label, hover highlight, click handler)
633
+ ui.button(() => ({ x: 0, y: 0 }), "START", 240, 56, {
634
+ accent: [120, 220, 160],
635
+ onClick: () => startGame(),
636
+ });
637
+
638
+ // Panels and a modal dimmer behind a dialog
639
+ ui.dim(0.6); // full-screen backdrop
640
+ ui.panel(() => ({ x: 0, y: 0 }), 480, 320, { opacity: 0.94 });
641
+
642
+ // Editable single-line text field
643
+ const name = ui.textField(() => ({ x: 0, y: -80 }), 280, 44, {
644
+ placeholder: "Your name",
645
+ maxLength: 16,
646
+ onChange: (v) => console.log(v),
647
+ });
648
+ name.getValue();
649
+ name.setValue("P1");
650
+
651
+ ui.relayout(); // after creating/anchoring or on window resize
652
+ ui.isOver(clientX, clientY); // true if an interactive element is under the pointer
653
+ ui.destroy(); // remove all UI objects + detach listeners
654
+
655
+ UI.LAYER; // 100000 — the default UI render layer
656
+ ```
657
+
658
+ ## Game Loop & Scene Transitions
659
+
660
+ `emerald.run(update, options)` owns the `requestAnimationFrame` loop for you: it
661
+ computes a clamped delta time, optionally advances a fixed-timestep simulation,
662
+ and calls your `update(dt, alpha)` each frame. `alpha` is the 0..1 interpolation
663
+ factor between fixed steps (1 when no fixed step is configured), so you can render
664
+ smoothly between simulation ticks.
665
+
666
+ ```javascript
667
+ const stop = emerald.run(
668
+ (dt, alpha) => {
669
+ world.update(dt); // variable-step game logic
670
+ emerald.drawScene(scene, dt);
671
+ },
672
+ {
673
+ maxDelta: 0.25, // clamp dt after a tab-switch stall
674
+ fixedStep: 1 / 60, // optional fixed simulation step (0 = off)
675
+ fixedUpdate: (step) => physics.process(step),
676
+ maxSubSteps: 5,
677
+ }
678
+ );
679
+ // later: stop(); // or emerald.stop();
680
+ ```
681
+
682
+ Switch scenes behind a fade with `SceneManager.transitionTo` (wired to
683
+ `ScreenEffects`), or drive the fade directly with `ScreenEffects.transition`:
684
+
685
+ ```javascript
686
+ import { SceneManager, ScreenEffects, Color } from "emeraldengine";
687
+
688
+ const fx = new ScreenEffects(overlayScene); // update()'d each frame by your loop
689
+
690
+ await SceneManager.transitionTo(nextScene, {
691
+ screenEffects: fx,
692
+ duration: 0.4,
693
+ color: new Color(0, 0, 0, 255),
694
+ onSwap: (scene) => buildLevel(scene), // runs while the screen is covered
695
+ });
696
+
697
+ // Or lower-level: fade out -> swap -> fade in
698
+ await fx.transition(() => swapScenes(), { duration: 0.4 });
699
+ ```
700
+
701
+ ## ScreenEffects (transitions)
702
+
703
+ Full-screen camera transitions drawn with the engine's own screen-space quads
704
+ (no CSS overlay), so they survive resolution changes, post-processing and
705
+ split-screen. `fadeOut`/`fadeIn`/`flash` return promises. Call `update(dt)` each
706
+ frame before `drawScene`.
707
+
708
+ ```javascript
709
+ import { ScreenEffects, Color } from "emeraldengine";
710
+
711
+ const fx = new ScreenEffects(scene, { layer: 100000, size: 5000 });
712
+
713
+ await fx.fadeOut(0.4, new Color(0, 0, 0, 255)); // fade to black
714
+ loadNextLevel();
715
+ await fx.fadeIn(0.4); // fade back in
716
+
717
+ fx.flash(new Color(255, 255, 255, 255), 0.25); // quick screen flash
718
+ fx.setLetterbox(80); // animate cinematic bars to 80px; pass 0 to retract
719
+
720
+ // in the loop:
721
+ fx.update(dt);
722
+ // when leaving the scene:
723
+ fx.destroy();
724
+ ```
725
+
726
+ ## Coroutines
727
+
728
+ Generator-based sequencing layered on the same per-frame delta the rest of the
729
+ engine uses. It's driven automatically from `Emerald.drawScene` (so coroutines
730
+ honor pause/slow-mo via `Time.timeScale`).
731
+
732
+ ```javascript
733
+ import { Coroutine } from "emeraldengine";
734
+
735
+ const handle = Coroutine.start(function* () {
736
+ big.setText("3");
737
+ audio.beep();
738
+ yield 0.7; // wait 0.7 seconds
739
+ big.setText("2");
740
+ audio.beep();
741
+ yield 0.7;
742
+ yield Coroutine.waitFrames(3); // wait 3 frames
743
+ yield Coroutine.waitUntil(() => player.ready); // block until predicate is truthy
744
+ yield Coroutine.waitWhile(() => paused); // block while predicate is truthy
745
+ yield fetch("/level.json"); // await any promise
746
+ yield Coroutine.tween(0, 1, 0.5, (v) => (door.openAmount = v)); // drive a value
747
+ yield otherCoroutineHandle; // wait for a nested coroutine
748
+ start();
749
+ });
750
+
751
+ handle.cancel(); // stop it early
752
+ handle.isRunning(); // boolean
753
+ await handle.promise; // resolves when the coroutine finishes or is cancelled
754
+
755
+ Coroutine.count(); // number of running coroutines
756
+ Coroutine.clearAll(); // cancel + remove every coroutine (e.g. on scene exit)
757
+ ```
758
+
759
+ ## Gamepads & Controllers
760
+
761
+ Full controller support is built into `InputManager`: analog sticks/triggers,
762
+ **semantic button names** that resolve through each pad's mapping, rumble,
763
+ connect/disconnect events, and a registry for non-standard controllers. Gamepad
764
+ input flows through the same `isDown` / `justPressed` / `getAxis` machinery as the
765
+ keyboard, so a token like `"pad:0:south"` works anywhere a key token does
766
+ (including `justPressed` edge detection).
767
+
768
+ ### Button tokens & names
769
+
770
+ `pad:<i>:<name>` targets pad index `<i>`. Names resolve through the pad's mapping,
771
+ so `south` is always the bottom face button whether the pad reports Xbox or
772
+ PlayStation ordering:
773
+
774
+ | Tokens | Buttons |
775
+ | ------------------------------------------------------------------------------------- | -------------------------------------- |
776
+ | `south`/`a`/`cross`, `east`/`b`/`circle`, `west`/`x`/`square`, `north`/`y`/`triangle` | face buttons |
777
+ | `l1`/`lb`, `r1`/`rb`, `l2`/`lt`, `r2`/`rt` | shoulders / triggers |
778
+ | `select`/`back`/`view`/`share`, `start`/`menu`/`options`, `guide`/`home` | center |
779
+ | `l3`/`leftStick`, `r3`/`rightStick` | stick clicks |
780
+ | `dpadUp`/`up`, `dpadDown`/`down`, `dpadLeft`/`left`, `dpadRight`/`right` | d-pad |
781
+ | `pad:<i>:<n>` | raw button index (mapping-independent) |
782
+ | `pad:<i>:axis<n>+` / `axis<n>-` | analog axis past the deadzone |
783
+ | `pad:<i>:leftStickUp/Down/Left/Right`, `rightStick...` | analog stick as a d-pad |
784
+
785
+ ### Analog feel: radial deadzone, rescaling, response curve
786
+
787
+ `getGamepadStick` applies a **radial** deadzone (on the stick's distance from
788
+ center, not per axis), so diagonals aren't clipped square and direction is
789
+ preserved exactly. Values are **rescaled** — 0 at the deadzone edge, 1 at full
790
+ deflection so there's no jump at the threshold and the full range stays
791
+ reachable. An optional response curve shapes the middle:
792
+
793
+ ```javascript
794
+ input.setGamepadDeadzone(0.25);
795
+ input.setGamepadCurve(2); // finer control near center (great for camera sticks)
796
+ const { x, y, magnitude, angle } = input.getGamepadStick("left", 0, { invertY: true });
797
+ ```
798
+
799
+ Semantic stick-direction tokens (`pad:0:leftStickUp`...) fire once the rescaled
800
+ value passes `stickPressThreshold` (default 0.5, configurable), so drift never
801
+ triggers menus. Common **DirectInput** pads Logitech Dual Action, generic
802
+ Twin-USB PS2 adapters, 8BitDo in D-input mode — are recognized out of the box;
803
+ `InputManager.registerGamepadMapping()` overrides always win.
804
+
805
+ ```javascript
806
+ input.mapAction("jump", ["Space", "pad:0:south"]);
807
+ input.mapAction("dash", ["Shift", "pad:0:west", "pad:0:r2"]); // X or right trigger
808
+ if (input.justPressed("jump")) player.jump();
809
+ ```
810
+
811
+ ### Analog sticks, triggers & rumble
812
+
813
+ ```javascript
814
+ const { x, y } = input.getGamepadStick("left"); // deadzoned -1..1
815
+ const aim = input.getGamepadStick("right");
816
+ const t = input.getGamepadTrigger("right"); // 0..1
817
+ input.getGamepadButton("south").pressed; // also .value, .index
818
+ input.setGamepadDeadzone(0.25);
819
+
820
+ input.rumble(0, { duration: 120, strong: 0.6, weak: 0.4 }); // where supported
821
+ ```
822
+
823
+ ### Connection events & diagnostics
824
+
825
+ ```javascript
826
+ input.onGamepadConnected((info) =>
827
+ console.log(info.id, info.mapping, info.buttonCount, info.axesCount)
828
+ );
829
+ input.onGamepadDisconnected((info) => pauseFor(info.index));
830
+
831
+ input.isGamepadConnected(0);
832
+ input.getGamepadInfo(0); // { id, mapping, buttonCount, axesCount, standard }
833
+ input.getPressedButtons(0); // raw indices currently pressed (layout discovery)
834
+ ```
835
+
836
+ ### Non-standard controllers
837
+
838
+ Most pads (and anything via XInput / Steam Input) report `mapping === "standard"`
839
+ and work out of the box. For a controller that reports a non-standard mapping (its
840
+ raw button indices differ), register a mapping once — it only applies to pads
841
+ whose id matches **and** that aren't already standard, so a correctly-reporting
842
+ pad is never remapped:
843
+
844
+ ```javascript
845
+ InputManager.registerGamepadMapping("my-controller-id", {
846
+ buttons: { south: 1, east: 2, west: 0, north: 3, start: 9 },
847
+ });
848
+ ```
849
+
850
+ D-pads reported as a hat axis (instead of buttons 12–15) are decoded into the
851
+ `dpad*` tokens automatically.
852
+
853
+ ## Per-instance color & tinting
854
+
855
+ `InstancedTexture` now supports an independent RGBA tint per instance (white =
856
+ unchanged, so existing scenes render identically). The tint multiplies the
857
+ texture in the shader.
858
+
859
+ ```javascript
860
+ import { Instance, Color } from "emeraldengine";
861
+
862
+ const inst = new Instance("coin", position, scale, rotation, frame);
863
+ instancedTexture.addInstance(inst);
864
+
865
+ // On the Instance directly (Color uses 0..255 channels; raw form is 0..1):
866
+ inst.setColor(new Color(255, 120, 60)); // warm tint
867
+ inst.setColor(1.0, 0.4, 0.2, 1.0); // same, as raw 0..1 RGBA
868
+
869
+ // Or drive it through the InstancedTexture by index:
870
+ instancedTexture.updateInstanceColor(0); // re-read instance 0's tint
871
+ instancedTexture.updateAllInstanceColors(); // re-read every instance's tint
872
+ ```
873
+
874
+ Tip: for additive sparkle/coin glows, set the instanced texture's blend mode:
875
+ `instancedTexture.setBlendMode("additive")`.
876
+
877
+ ### Per-instance atlas regions (setTexCoords)
878
+
879
+ Normally every instance samples the shared frame grid (`instance.frame`). With
880
+ `setTexCoords` an instance carries its **own UV quad**, so a single
881
+ InstancedTexture — one draw call — can batch tiles from an atlas with margins
882
+ and spacing, apply per-instance flips, or mix arbitrary sprite regions:
883
+
884
+ ```javascript
885
+ const inst = new InstancedTexture("atlas.png", count, 0, 0, 1, 1, 0, false, true, false);
886
+ const tile = new Instance("tile", new Vector3(x, y, 0), new Vector2(32, 32), rotation);
887
+ // 8 floats, one vec2 per corner in getFrameTexCoords order: (R,B) (L,B) (R,T) (L,T)
888
+ tile.setTexCoords([right, bottom, left, bottom, right, top, left, top]);
889
+ inst.addInstance(tile);
890
+ inst.setStatic(true); // non-moving batch: matrices upload once
891
+ ```
892
+
893
+ This is exactly how the Tile Forge level loader renders a whole layer of
894
+ sliced, rotated, flipped tiles as one draw call. Pass `null` to return an
895
+ instance to the frame grid.
896
+
897
+ ## Physics: collision layers & continuous detection
898
+
899
+ ### Collision filtering (layers)
900
+
901
+ `CollisionLayers` maps human-readable layer names to the category bits planck uses
902
+ for filtering, so you can express "players collide with ground and enemies, but
903
+ not each other" without juggling bitmasks. Two fixtures collide only when each
904
+ one's category is in the other's mask (symmetric by construction; up to 16
905
+ layers).
906
+
907
+ ```javascript
908
+ import { CollisionLayers } from "emeraldengine";
909
+
910
+ CollisionLayers.define("ground", "player", "enemy", "pickup");
911
+
912
+ playerCollider
913
+ .setCategory("player")
914
+ .setCollidesWith(["ground", "enemy", "pickup"]);
915
+ enemyCollider.setCategory("enemy").setCollidesWith(["ground", "player"]); // ignore each other
916
+
917
+ // Or up front, in the collider constructor (8th arg):
918
+ new BoxCollider(body, size, 1, 0.2, 0, false, gameObject, {
919
+ category: "pickup",
920
+ collidesWith: ["player"],
921
+ });
922
+
923
+ // Raw control if you prefer bits:
924
+ collider.setFilter({ category: 0x0004, mask: 0xffff, group: 0 });
925
+ ```
926
+
927
+ ### Continuous collision detection (CCD)
928
+
929
+ Fast bodies (a dash, a projectile, a hard fall) can move far enough in one physics
930
+ step to tunnel through thin walls. Mark them continuous so planck solves the swept
931
+ path against static geometry instead:
932
+
933
+ ```javascript
934
+ projectile.setContinuous(true); // bullet-mode CCD
935
+ projectile.isContinuous(); // boolean
936
+ ```
937
+
938
+ Reserve it for the handful of bodies that actually move fast — it costs more per
939
+ step.
940
+
941
+ ## RigidBody velocity helpers
942
+
943
+ `RigidBody` gained direct velocity/impulse control in **world (pixel) units** —
944
+ the engine converts to/from physics units internally, so you never touch the
945
+ scale factor.
946
+
947
+ ```javascript
948
+ rigidBody.setLinearVelocity(380, 0); // world units per second
949
+ const v = rigidBody.getLinearVelocity(); // { x, y } in world units/sec
950
+ rigidBody.applyImpulse(0, 900); // impulse at the body's center
951
+ rigidBody.setAwake(true); // wake (or sleep) the body
952
+
953
+ // Live world-space position/rotation (kept in sync with the simulation):
954
+ rigidBody.getPosition(); // Vector2 — current position (not the spawn point)
955
+ rigidBody.getInitialPosition(); // Vector2 — the position it was created at
956
+ ```
957
+
958
+ These make velocity-driven movement (player controllers, knockback, dashes)
959
+ straightforward without reaching into the underlying planck body.
960
+
961
+ ## Tilemap colliders & auto-tiling
962
+
963
+ A `Tilemap` can now generate physics colliders from its map and auto-pick tile
964
+ frames from a solidity grid.
965
+
966
+ ```javascript
967
+ // 1) Build solid colliders from the current map.
968
+ // Solid cells are merged greedily into horizontal runs, so a row of N tiles
969
+ // becomes ONE static box collider instead of N.
970
+ map.setMap(grid, { originX: 0, originY: 0, flipY: true });
971
+ map.buildColliders(physics, {
972
+ isSolid: (frame) => frame != null && frame >= 0, // default
973
+ friction: 0.2,
974
+ restitution: 0,
975
+ density: 0,
976
+ ownerObject: map.gameObject, // collision callbacks resolve back to this
977
+ });
978
+ map.clearColliders(); // destroy the generated bodies (e.g. before a rebuild)
979
+
980
+ // 2) Auto-tiling: turn a boolean solidity grid into frame indices using a
981
+ // 4-bit edge bitmask (up|right|down|left = bits 1,2,4,8). Empty cells -> -1.
982
+ const frames = Tilemap.computeAutoTile(solidGrid, {
983
+ frames: lookup16, // optional length-16 mask -> frame map matching your sheet
984
+ base: 0, // added to every solid frame when no lookup is given
985
+ edgesSolid: true, // treat out-of-bounds as solid
986
+ });
987
+ map.setAutoTiledMap(solidGrid, { base: 0, originX: 0, originY: 0 }); // compute + setMap
988
+ ```
989
+
990
+ ## Camera follow deadzone
991
+
992
+ `CameraController` now supports a centered follow deadzone: the camera only scrolls
993
+ once the target leaves a box around the current focus, so small movements don't jitter
994
+ the view. Chainable with the existing follow/bounds/shake API.
995
+
996
+ ```javascript
997
+ const ctrl = new CameraController(camera);
998
+ ctrl
999
+ .follow(player.gameObject, 0.12)
1000
+ .setDeadzone(90, 60) // half-extents in world units; 0/0 or null disables
1001
+ .setBounds(280, cy, finishX + 200, cy);
1002
+ // each frame: ctrl.update(dt);
1003
+ ```
1004
+
1005
+ ## ParticleEmitter (pooled sprites)
1006
+
1007
+ `ParticleEmitter` is a reliable, allocation-free particle system built from a
1008
+ fixed pool of ordinary textured GameObjects. Every live particle's transform, tint
1009
+ and opacity are driven by hand each frame — there's no instanced-draw buffer
1010
+ lifecycle to desync, so it keeps rendering for the whole session. Spawn with
1011
+ `burst(n, cfg)` / `emit(cfg)` and advance with `update(dt)`; every `cfg` field is
1012
+ optional.
1013
+
1014
+ ```javascript
1015
+ import { ParticleEmitter } from "emeraldengine";
1016
+
1017
+ const fx = new ParticleEmitter(scene, {
1018
+ texture: "spark.png", // sprite source (data URL / path / image)
1019
+ capacity: 256, // pool size (max live particles)
1020
+ layer: 50,
1021
+ });
1022
+
1023
+ fx.burst(12, {
1024
+ x: 200,
1025
+ y: 120,
1026
+ dir: -Math.PI / 2,
1027
+ spread: Math.PI,
1028
+ speed: 180, // emission cone
1029
+ gx: 0,
1030
+ gy: -300,
1031
+ drag: 2, // forces
1032
+ life: 0.4,
1033
+ size: 8,
1034
+ sFrom: 1,
1035
+ sTo: 0.1, // scale over life
1036
+ aFrom: 0.7,
1037
+ aTo: 0, // alpha over life
1038
+ cr: 255,
1039
+ cg: 220,
1040
+ cb: 120,
1041
+ additive: true, // tint + blend
1042
+ rotSpeed: 6,
1043
+ shape: "ring",
1044
+ radius: 12, // "point" | "ring" | "circle" | "box"
1045
+ });
1046
+
1047
+ // in the loop:
1048
+ fx.update(dt);
1049
+ fx.activeCount; // live particles
1050
+ fx.reset(); // kill all immediately
1051
+ fx.destroy(); // remove pooled objects from the scene
1052
+ ```
1053
+
1054
+ This is the go-to for one-off bursts (dust, sparkles, confetti, hit effects). For
1055
+ the curve-driven, instanced system see [Advanced Particles](#advanced-particles)
1056
+ below.
1057
+
1058
+ ## Advanced Particles
1059
+
1060
+ `ParticleSettings` gained emitter shapes and over-lifetime curves, plus per-particle
1061
+ `rotationSpeed` and `drag`. These layer on top of the existing particle fields.
1062
+
1063
+ ```javascript
1064
+ import { ParticleSettings, Vector2, Color } from "emeraldengine";
1065
+
1066
+ const settings = new ParticleSettings({
1067
+ lifetime: 1.0,
1068
+ amount: 24,
1069
+ velocity: new Vector2(0, 260),
1070
+ gravity: new Vector2(0, -300),
1071
+
1072
+ // Emitter shape — where new particles spawn relative to the emit point:
1073
+ // "point" | "circle" | "ring" | "box" | "cone" (default)
1074
+ shape: "ring",
1075
+ shapeRadius: 24, // used by circle/ring
1076
+ shapeSize: new Vector2(40, 10), // used by box
1077
+
1078
+ // Over-lifetime curves ({ from, to } interpolated by normalized age):
1079
+ scaleOverLife: { from: 1.4, to: 0.0 }, // size multiplier
1080
+ alphaOverLife: { from: 1.0, to: 0.0 }, // opacity
1081
+ colorOverLife: {
1082
+ // 0..255 channels
1083
+ from: new Color(255, 240, 180),
1084
+ to: new Color(255, 90, 60),
1085
+ },
1086
+
1087
+ rotationSpeed: Math.PI, // radians/sec per particle
1088
+ drag: 1.2, // velocity damping per second (0 = none)
1089
+ });
1090
+ ```
1091
+
1092
+ ## Positional (spatial) Audio
1093
+
1094
+ `AudioManager` can attenuate and pan sounds based on a listener position. It uses
1095
+ the Web Audio `StereoPanner` when available and falls back to volume-only panning
1096
+ otherwise.
1097
+
1098
+ ```javascript
1099
+ audio.setListener(player.x, player.y); // usually the camera/player each frame
1100
+ audio.setSpatialRange(100, 800); // full volume <100px, silent >800px
1101
+
1102
+ audio.playSpatial("explosion", { x: 1200, y: 50 }); // one-shot, positioned
1103
+
1104
+ // Pure helper (also used internally) — handy for custom routing/tests:
1105
+ const { volume, pan, distance } = audio.computeSpatial({ x, y });
1106
+ ```
1107
+
1108
+ ## Audio buses & fades
1109
+
1110
+ Every sound belongs to a named **mix bus** — `"music"` and `"sfx"` exist by
1111
+ default (new sounds land on `"sfx"`), and any name you use creates a bus on the
1112
+ fly. Effective volume is `master × bus × sound × fade`, so one slider mutes all
1113
+ music without touching the SFX:
1114
+
1115
+ ```javascript
1116
+ audio.add("theme.mp3", "theme", { bus: "music", loop: true });
1117
+ audio.add("jump.wav", "jump"); // default bus: "sfx"
1118
+
1119
+ audio.setBusVolume("music", 0.5); // the settings-menu "music volume" slider
1120
+ audio.setBusVolume("sfx", 0.8);
1121
+ audio.setSoundBus("thunder", "ambience"); // move a sound, creating the bus
1122
+ audio.getBusVolume("music"); // 0.5
1123
+ ```
1124
+
1125
+ Fades run on the manager's clock (self-driven via rAF by default; pass
1126
+ `{ autoTick: false }` and call `audio.update(dt)` yourself to tie them to the
1127
+ game loop):
1128
+
1129
+ ```javascript
1130
+ audio.fadeIn("theme", 1.5); // play from silence to full over 1.5s
1131
+ audio.fadeOut("theme", 2.0); // fade to silence, then stop
1132
+ audio.fadeTo("theme", 0.2, 0.5); // duck under dialogue
1133
+ audio.crossfade("theme", "boss", 2.0); // level -> boss music, one call
1134
+ ```
1135
+
1136
+ ## AssetManager (loading)
1137
+
1138
+ One async loader for everything a game needs at startup — images/textures, audio,
1139
+ JSON, text, and web fonts — with deduplication and aggregate progress for a
1140
+ loading bar. Images are routed through `TextureManager`, so the GL upload cache is
1141
+ shared with the rest of the engine.
1142
+
1143
+ ```javascript
1144
+ import { AssetManager } from "emeraldengine";
1145
+
1146
+ const assets = new AssetManager();
1147
+ assets
1148
+ .image("player", "player.png", { pixelart: true })
1149
+ .audio("jump", "jump.wav")
1150
+ .json("level1", "levels/1.json")
1151
+ .text("credits", "credits.txt")
1152
+ .font("Press Start 2P", "fonts/press-start.woff2");
1153
+
1154
+ assets.onProgress((loaded, total) => bar.set(loaded / total));
1155
+ await assets.load({ continueOnError: false }); // rejects on a failed asset unless true
1156
+
1157
+ assets.get("player"); // HTMLImageElement
1158
+ assets.get("level1"); // parsed JSON
1159
+ assets.has("jump"); // boolean
1160
+ assets.progress(); // 0..1
1161
+ await assets.getTexture("player"); // { texture, width, height } from the GL cache
1162
+ assets.clear();
1163
+ ```
1164
+
1165
+ ## Asset importers (Tiled & Aseprite)
1166
+
1167
+ Import maps from [Tiled](https://www.mapeditor.org) and sprite-sheet animations
1168
+ from [Aseprite](https://www.aseprite.org). Both are pure parsers — hand them the
1169
+ already-parsed JSON (load it with `AssetManager.json` or `fetch`).
1170
+
1171
+ ### Tiled maps
1172
+
1173
+ ```javascript
1174
+ import { TiledMap } from "emeraldengine";
1175
+
1176
+ // Build a ready-to-render Tilemap from a Tiled JSON map + its tile sheet.
1177
+ const map = TiledMap.toTilemap(mapJson, "tiles.png", { layer: "ground" });
1178
+ scene.add(map.gameObject);
1179
+ map.buildColliders(physics);
1180
+
1181
+ // Or just the frame grid (for your own Tilemap.setMap call):
1182
+ const grid = TiledMap.toFrameGrid(mapJson, { layer: "ground" });
1183
+
1184
+ // Object layers (spawn points, triggers) as plain data; Tiled `properties` are
1185
+ // flattened into `props`, and flipY converts to a y-up world.
1186
+ const spawns = TiledMap.objects(mapJson, { layer: "spawns", flipY: true });
1187
+ // -> [{ name, type, x, y, width, height, gid, props, ... }]
1188
+ ```
1189
+
1190
+ ### Aseprite sheets
1191
+
1192
+ ```javascript
1193
+ import { Aseprite, Texture, Animator } from "emeraldengine";
1194
+
1195
+ const cfg = Aseprite.spriteConfig(sheetJson); // { frameWidth, frameHeight, framesPerRow, totalFrames }
1196
+ const tex = new Texture(
1197
+ "hero.png",
1198
+ cfg.frameWidth,
1199
+ cfg.frameHeight,
1200
+ cfg.framesPerRow,
1201
+ cfg.totalFrames,
1202
+ 0,
1203
+ false
1204
+ );
1205
+ obj.addComponent(tex);
1206
+
1207
+ const anim = new Animator();
1208
+ obj.addComponent(anim);
1209
+ Aseprite.applyTo(anim, sheetJson); // registers a clip per frame-tag
1210
+ anim.play("run");
1211
+
1212
+ // Or inspect the clips yourself (handles forward/reverse/pingpong):
1213
+ Aseprite.toClips(sheetJson); // -> [{ name, frames:[...], speed }]
1214
+ ```
1215
+
1216
+ ## Networking (NetworkManager + Interpolator)
1217
+
1218
+ A thin, **optional** multiplayer layer over [Colyseus](https://colyseus.io).
1219
+ `colyseus.js` is a peer dependency imported dynamically, so games that don't use
1220
+ networking never load it. It bundles an `Interpolator` for smooth remote entities.
1221
+
1222
+ ```javascript
1223
+ import { NetworkManager } from "emeraldengine";
1224
+
1225
+ const net = new NetworkManager({ interpolation: { delay: 0.1 } });
1226
+ await net.connect("wss://my-server:2567"); // dynamically imports colyseus.js
1227
+ const room = await net.join("arena", { name: "P1" });
1228
+
1229
+ net.onMessage("hit", (msg) => applyHit(msg));
1230
+ net.onStateChange((state) => {
1231
+ for (const [id, p] of state.players) net.interpolator.push(id, p, net.now());
1232
+ });
1233
+ net.onLeave((code) => showDisconnected(code));
1234
+
1235
+ net.send("move", { dir: 1 });
1236
+ net.sessionId; // this client's id
1237
+ await net.leave();
1238
+
1239
+ // each frame, render remote entities "in the past" for smoothness:
1240
+ const pos = net.interpolator.sample(remoteId, net.now()); // { x, y } | null
1241
+ ```
1242
+
1243
+ `Interpolator` is also exported standalone and is pure (no network/DOM), so you can
1244
+ use it with any transport or in tests:
1245
+
1246
+ ```javascript
1247
+ import { Interpolator } from "emeraldengine";
1248
+
1249
+ const interp = new Interpolator({ delay: 0.1, maxBuffer: 60 });
1250
+ interp.push(entityId, { x, y }, serverTimeSeconds); // on each authoritative update
1251
+ const smoothed = interp.sample(entityId, nowSeconds); // each frame
1252
+ interp.prune(nowSeconds); // bound memory for long-lived entities
1253
+ interp.remove(entityId); // when an entity leaves
1254
+ interp.clear();
1255
+ ```
1256
+
1257
+ ## DebugOverlay (upgraded)
1258
+
1259
+ The overlay now shows a frame-time sparkline with min/avg/max milliseconds and heap
1260
+ usage, accepts custom metric rows, and can visualize physics colliders.
1261
+
1262
+ ```javascript
1263
+ import { DebugOverlay } from "emeraldengine";
1264
+
1265
+ const debug = new DebugOverlay();
1266
+ debug.setVisible(true); // toggle (e.g. bind to F3)
1267
+ debug.setMetric("enemies", enemies.length); // add/refresh a custom row
1268
+ debug.showColliders(scene, true); // overlay collider shapes for the scene
1269
+ // after drawScene each frame:
1270
+ debug.update(emerald, scene); // FPS / frame-time graph / objects / cameras
1271
+ debug.destroy();
1272
+ ```
1273
+
1274
+ ## Storage (versioned saves)
1275
+
1276
+ `Storage.save`/`Storage.load` wrap your data in a versioned envelope
1277
+ (`{ v, t, data }`) with an automatic `.bak` mirror, so saves survive both
1278
+ corrupted writes (a torn write recovers from backup) and **schema changes**
1279
+ (old saves migrate forward instead of being discarded):
1280
+
1281
+ ```javascript
1282
+ import { Storage } from "emeraldengine";
1283
+
1284
+ // Write: version + timestamp envelope, plus a .bak backup by default.
1285
+ Storage.save("profile", { level: 3, coins: 120 }, { version: 2 });
1286
+
1287
+ // Read: falls back, recovers from backup, and migrates old versions.
1288
+ const profile = Storage.load("profile", {
1289
+ version: 2,
1290
+ fallback: { level: 1, coins: 0 },
1291
+ migrate: (old, fromVersion) => {
1292
+ // v1 saves had no coins field — upgrade them instead of losing progress
1293
+ return { ...old, coins: old.coins ?? 0 };
1294
+ },
1295
+ // rewrite: true (default) re-saves migrated data in the new format
1296
+ });
1297
+
1298
+ Storage.hasSave("profile"); // true
1299
+ Storage.removeSave("profile"); // deletes the save AND its backup
1300
+ ```
1301
+
1302
+ Plain pre-versioning values load as version 0, so adopting the envelope on an
1303
+ existing game is safe. (For whole-scene snapshots, see
1304
+ [Serializer](#serializer-saveload-scenes); for raw key/value access,
1305
+ `Storage.saveToLocalStorage` / `readFromLocalStorage` still exist.)
1306
+
1307
+ ## EmeraldDB (IndexedDB saves)
1308
+
1309
+ `Storage` lives on localStorage, which caps out around **5MB** — plenty for
1310
+ settings and high scores, not for a big persistent world (every tile's state,
1311
+ chests, NPC relationships...). `EmeraldDB` is the async, big-world companion:
1312
+ same versioned envelope, `.bak` backup, and migration semantics, backed by
1313
+ IndexedDB — effectively unlimited, and values are **structured-cloned** (no
1314
+ JSON round-trip), so Maps, Sets, Dates, and typed arrays save as-is and large
1315
+ saves stay fast.
1316
+
1317
+ ```javascript
1318
+ import { EmeraldDB } from "emeraldengine";
1319
+
1320
+ // Versioned world save — mirrors Storage.save/load, but async:
1321
+ await EmeraldDB.save("world", world, { version: 3 });
1322
+ const world = await EmeraldDB.load("world", {
1323
+ version: 3,
1324
+ fallback: makeNewWorld(),
1325
+ migrate: (old, fromVersion) => upgradeWorld(old, fromVersion),
1326
+ });
1327
+ await EmeraldDB.hasSave("world"); // true (checks the .bak too)
1328
+ await EmeraldDB.removeSave("world"); // deletes save + backup
1329
+
1330
+ // Plain async key/value (no envelope):
1331
+ await EmeraldDB.set("settings", { volume: 0.8, keybinds: new Map() });
1332
+ const settings = await EmeraldDB.get("settings", {});
1333
+ await EmeraldDB.keys(); // every key in the store
1334
+
1335
+ // Optional setup:
1336
+ EmeraldDB.configure({ name: "my-game", store: "saves" }); // before first use
1337
+ EmeraldDB.isSupported(); // feature-detect (falls back to Storage if false)
1338
+ await EmeraldDB.importFromStorage("profile"); // one-time upgrade of an old localStorage save
1339
+ ```
1340
+
1341
+ The backup write happens **in the same IndexedDB transaction** as the save, so
1342
+ a crash mid-write can never leave you with both copies torn. Rule of thumb:
1343
+ `Storage` for small synchronous bits (settings, best times), `EmeraldDB` for
1344
+ the world.
1345
+
1346
+ ## Resolution independence (design resolution)
1347
+
1348
+ Author your game at one fixed resolution and let the engine scale it to any
1349
+ screen. The world renders at the design size and is fitted per mode; input
1350
+ helpers convert back, so gameplay code never sees the difference:
1351
+
1352
+ ```javascript
1353
+ // Design at 960x540, letterboxed onto whatever screen the player has:
1354
+ emerald.setDesignResolution(960, 540, "fit");
1355
+
1356
+ // Modes:
1357
+ // "fit" letterbox — whole design visible, bars if aspect differs
1358
+ // "fill" cover — fills the screen, crops the overflow
1359
+ // "stretch" distorts to fill exactly (no bars, no crop)
1360
+ // "pixel" integer scaling — crisp for pixel art
1361
+ emerald.clearDesignResolution(); // back to 1:1 CSS pixels
1362
+
1363
+ // Mouse/touch coordinates -> world space (accounts for the design scale,
1364
+ // letterbox offset, camera zoom/position, and DPR):
1365
+ const world = emerald.screenToWorld(input.mouse.x, input.mouse.y);
1366
+ ```
1367
+
1368
+ ## Auto-pause & lifecycle
1369
+
1370
+ `run()` pauses the loop when the tab is hidden (stops audio-desync, timer
1371
+ pileups, and giant delta-time spikes on return). Hooks let you pause music or
1372
+ show an overlay; you can also pause manually:
1373
+
1374
+ ```javascript
1375
+ emerald.run(update, {
1376
+ pauseOnBlur: true, // default: pause when the tab is hidden
1377
+ pauseOnWindowBlur: false, // stricter: also pause when the window loses focus
1378
+ onPause: () => audio.setMasterVolume(0),
1379
+ onResume: () => audio.setMasterVolume(1),
1380
+ });
1381
+
1382
+ emerald.pause(); // e.g. from your own pause menu
1383
+ emerald.resume();
1384
+ ```
1385
+
1386
+ The first `dt` after resuming is clamped (`maxDelta`, default 0.25s), so
1387
+ physics never explodes after a long background stint.
1388
+
1389
+ ## Production hardening (resource lifecycle, context loss, render stats)
1390
+
1391
+ ### Freeing GPU memory
1392
+
1393
+ Removing an object from a scene keeps its GPU resources alive so it can be
1394
+ re-added. When something is gone for good, dispose it — shared textures are
1395
+ reference-counted and freed when their last user disposes:
1396
+
1397
+ ```javascript
1398
+ scene.remove(enemy, { dispose: true }); // buffers + texture reference freed
1399
+ gameObject.destroy(); // same, plus physics bodies + Behaviour.onDestroy
1400
+ scene.dispose(); // tear down an entire level/screen
1401
+ drawable.dispose(); // lowest level, safe to call twice
1402
+ ```
1403
+
1404
+ ### WebGL context loss
1405
+
1406
+ Lost contexts (mobile tab switches, GPU resets, laptops waking) are survived
1407
+ automatically: rendering pauses on loss, and on restore the engine recompiles
1408
+ shaders, re-uploads every cached texture from the surviving image cache,
1409
+ rebuilds all drawable buffers, custom `Material`s, post effects, and render
1410
+ targets, then resumes. Optional hooks:
1411
+
1412
+ ```javascript
1413
+ emerald.onContextLost(() => overlay.show("Recovering graphics..."));
1414
+ emerald.onContextRestored(() => overlay.hide());
1415
+ ```
1416
+
1417
+ ### Render stats
1418
+
1419
+ Every draw site reports into per-frame counters, shown automatically by
1420
+ `DebugOverlay` (`draws` / `quads` / `binds`) and readable in code:
1421
+
1422
+ ```javascript
1423
+ const { drawCalls, quads, textureBinds } = emerald.getRenderStats();
1424
+ ```
1425
+
1426
+ Draw calls growing with level size means something isn't batched — use
1427
+ `Tilemap`, `SpriteBatch`, or the level loader's instanced tile path.
1428
+
1429
+ ### Pixel-perfect rendering
1430
+
1431
+ Spritesheet frame UVs are inset half a texel everywhere, so frames never bleed
1432
+ into neighboring cells (the classic "white seams between tiles" artifact). For
1433
+ pixel-art games also snap the camera to whole pixels:
1434
+
1435
+ ```javascript
1436
+ emerald.camera.setPixelSnap(true); // rendered position rounds; stored position stays smooth
1437
+ ```
1438
+
1439
+ ## Usage
1440
+
1441
+ ### Basic Setup
1442
+
1443
+ ```javascript
1444
+ import { Emerald } from "emeraldengine";
1445
+ import { Scene } from "emeraldengine";
1446
+ import { Color } from "emeraldengine";
1447
+ import { SceneManager } from "emeraldengine";
1448
+
1449
+ const emerald = new Emerald(canvas); // You should pass your own canvas element here
1450
+ const scene = new Scene();
1451
+ SceneManager.setScene(scene);
1452
+ ```
1453
+
1454
+ ### Set the background color for the engine
1455
+
1456
+ ```javascript
1457
+ emerald.setBackgroundColor(color); // color = new Color(r, g, b, a = 255);
1458
+ ```
1459
+
1460
+ ### Drawing the scene
1461
+
1462
+ To draw items in the screen you need some sort of animation loop. I use `window.requestAnimationFrame` for this. Here is a basic example:
1463
+
1464
+ ```javascript
1465
+ let lastTime = 0;
1466
+ const animate = (currentTime) => {
1467
+ const deltaTime = (currentTime - lastTime) / 1000;
1468
+ lastTime = currentTime;
1469
+ emerald.drawScene(scene, deltaTime); // You need this line to tell the engine what to draw
1470
+ window.requestAnimationFrame(animate);
1471
+ };
1472
+ animate(0);
1473
+ ```
1474
+
1475
+ ## Scene
1476
+
1477
+ Emerald has multiple scenes support. In order to render any object it has to be added to the scene using the `add` method.
1478
+
1479
+ ### Adding and removing items from the scene
1480
+
1481
+ ```javascript
1482
+ // Adding an object to the scene
1483
+ scene.add(gameObject);
1484
+
1485
+ // Removing an object from the scene
1486
+ scene.remove(gameObject);
1487
+ ```
1488
+
1489
+ ### Set Active Scene
1490
+
1491
+ ```javascript
1492
+ // When changing a scene you should deactivate current scene to not mess up the event manager.
1493
+
1494
+ // Activate Scene
1495
+ scene.setIsActive(true);
1496
+
1497
+ // Deactivate Scene
1498
+ scene.setIsActive(false);
1499
+ ```
1500
+
1501
+ ## Game Objects
1502
+
1503
+ ### Creating a new GameObject
1504
+
1505
+ ```javascript
1506
+ import { GameObject } from "emeraldengine";
1507
+ import { Vector3, Vector2 } from "emeraldengine";
1508
+ /*
1509
+ ARGUMENTS:
1510
+ 1. name: string = Name of the new GameObject
1511
+ 2. position: Vector3 = Position of the new GameObject
1512
+ 3. rotation: number = Rotation of the new GameObject
1513
+ 4. scale: Vector2 = Scale of the new GameObject
1514
+ */
1515
+ const gameObject = new GameObject(name, position, rotation, scale);
1516
+ ```
1517
+
1518
+ This will create a new empty GameObject. At this stage you will not see anything on the screen until you add some components.
1519
+
1520
+ ### Components
1521
+
1522
+ There are currently 8 components: Texture, InstancedTexture, Square2D, Circle2D, Triangle2D, RigidBody, BoxCollider, CircleCollider
1523
+
1524
+ #### Texture
1525
+
1526
+ ```javascript
1527
+ import { Texture } from "emeraldengine";
1528
+ /*
1529
+ ARGUMENTS:
1530
+ 1. texturePath = Specify the path for the texture that you want to use.
1531
+ 2. frameWidth: number = The width of each frame.
1532
+ 3. frameHeight: number = The height of each frame.
1533
+ 4. framesPerRow: number = How many frames are in one row in your spritesheet.
1534
+ 5. totalFrames: number = How many total frames does your spritesheet have.
1535
+ 6. animationSpeed: number = Speed of change of every frame.
1536
+ 7. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
1537
+ 8. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
1538
+ 9. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
1539
+ */
1540
+ const texture = new Texture(
1541
+ texturePath,
1542
+ frameWidth,
1543
+ frameHeight,
1544
+ framesPerRow,
1545
+ totalFrames,
1546
+ animationSpeed,
1547
+ autoPlay,
1548
+ (pixelart = true),
1549
+ (useLighting = true)
1550
+ );
1551
+
1552
+ // Add the texture to a game object
1553
+ gameObject.addComponent(texture);
1554
+ ```
1555
+
1556
+ ![Texture](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/texture.png?raw=true)
1557
+
1558
+ #### InstancedTexture
1559
+
1560
+ InstancedTexture is perfect for rendering many objects with the same texture efficiently, such as tiles, particles, or repeating elements.
1561
+
1562
+ ```javascript
1563
+ import { InstancedTexture } from "emeraldengine";
1564
+ /*
1565
+ ARGUMENTS:
1566
+ 1. texturePath = Specify the path for the texture that you want to use.
1567
+ 2. instanceCount: number = How many instances of the texture you want to create.
1568
+ 3. frameWidth: number = The width of each frame.
1569
+ 4. frameHeight: number = The height of each frame.
1570
+ 5. framesPerRow: number = How many frames are in one row in your spritesheet.
1571
+ 6. totalFrames: number = How many total frames does your spritesheet have.
1572
+ 7. animationSpeed: number = Speed of change of every frame.
1573
+ 8. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
1574
+ 9. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
1575
+ 10. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
1576
+ */
1577
+ const instancedTexture = new InstancedTexture(
1578
+ texturePath,
1579
+ instanceCount,
1580
+ frameWidth,
1581
+ frameHeight,
1582
+ framesPerRow,
1583
+ totalFrames,
1584
+ animationSpeed,
1585
+ autoPlay,
1586
+ (pixelart = true),
1587
+ (useLighting = true)
1588
+ );
1589
+
1590
+ // Add the instanced texture to a game object
1591
+ gameObject.addComponent(instancedTexture);
1592
+ ```
1593
+
1594
+ ![InstancedTexture](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/instancedtexture.png?raw=true)
1595
+
1596
+ #### Square2D
1597
+
1598
+ ```javascript
1599
+ import { Square2D } from "emeraldengine";
1600
+ let square = new Square2D();
1601
+ gameObject.addComponent(square);
1602
+ ```
1603
+
1604
+ ![Square2D](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/square2d.png?raw=true)
1605
+
1606
+ #### Triangle2D
1607
+
1608
+ ```javascript
1609
+ import { Triangle2D } from "emeraldengine";
1610
+ let triangle = new Triangle2D();
1611
+ gameObject.addComponent(triangle);
1612
+ ```
1613
+
1614
+ ![Triangle2D](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/triangle2d.png?raw=true)
1615
+
1616
+ #### Circle2D
1617
+
1618
+ ```javascript
1619
+ import { Circle2D } from "emeraldengine";
1620
+ /*
1621
+ ARGUMENTS:
1622
+ 1. segments = number of segments that the circle will have. Default is 32.
1623
+ */
1624
+ let circle = new Circle2D(segments);
1625
+ gameObject.addComponent(circle);
1626
+ ```
1627
+
1628
+ ![Circle2D](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/circle2d.png?raw=true)
1629
+
1630
+ #### RigidBody
1631
+
1632
+ RigidBody is a component that allows you to add physics to your game objects. However, it won't work until you create a Physics instance at the top of your code.
1633
+
1634
+ ```javascript
1635
+ import { RigidBody } from "emeraldengine";
1636
+ import { Physics, Vector2 } from "emeraldengine";
1637
+ // Create physics engine first
1638
+ const physics = new Physics(-70, 32, 2); // gravity, scale, velocityThreshold
1639
+
1640
+ /*
1641
+ ARGUMENTS:
1642
+ 1. physics: Physics = Instance of the Physics class that you created at the top of your code.
1643
+ 2. type: string = Type of the rigid body. It can be "dynamic", "kinematic", or "static".
1644
+ 3. position: Vector2 = Position of the rigid body is Vector2 because it doesn't need any Z index.
1645
+ 4. fixedRotation: boolean = Specify whether the rigid body should have a fixed rotation or not. Default is false.
1646
+ 5. parentObject: GameObject = (OPTIONAL) If you want to attach the rigid body to a GameObject you can pass it here. If you don't want to attach it to any GameObject then pass null.
1647
+ 6. offset: Vector2 = (OPTIONAL) Offset from the GameObject's position.
1648
+ */
1649
+ const rigidBody = new RigidBody(
1650
+ physics,
1651
+ "dynamic",
1652
+ new Vector2(0, 0),
1653
+ false,
1654
+ gameObject,
1655
+ new Vector2(0, 0)
1656
+ );
1657
+
1658
+ gameObject.addComponent(rigidBody);
1659
+ ```
1660
+
1661
+ #### BoxCollider
1662
+
1663
+ ```javascript
1664
+ import { BoxCollider } from "emeraldengine";
1665
+
1666
+ /*
1667
+ ARGUMENTS:
1668
+ 1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
1669
+ 2. size: Vector2 = Size of the box collider.
1670
+ 3. density: number = Density of the collider.
1671
+ 4. friction: number = Friction of the collider.
1672
+ 5. restitution: number = Restitution (bounciness) of the collider.
1673
+ 6. isSensor: boolean = Whether this collider is a sensor (triggers events but doesn't collide physically).
1674
+ 7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
1675
+ */
1676
+ const boxCollider = new BoxCollider(
1677
+ rigidBody,
1678
+ new Vector2(1, 1),
1679
+ 1,
1680
+ 0.3,
1681
+ 0.1,
1682
+ false,
1683
+ gameObject
1684
+ );
1685
+
1686
+ gameObject.addComponent(boxCollider);
1687
+ ```
1688
+
1689
+ ![BoxCollider](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/boxcollider.png?raw=true)
1690
+
1691
+ The `BoxCollider` is specifically made bigger than the `Square2D` component in this image to demonstrate how it works. You can adjust the size of the collider to fit your needs.
1692
+
1693
+ #### CircleCollider
1694
+
1695
+ ```javascript
1696
+ import { CircleCollider } from "emeraldengine";
1697
+
1698
+ /*
1699
+ ARGUMENTS:
1700
+ 1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
1701
+ 2. radius: number = Radius of the circle collider.
1702
+ 3. density: number = Density of the collider.
1703
+ 4. friction: number = Friction of the collider.
1704
+ 5. restitution: number = Restitution (bounciness) of the collider.
1705
+ 6. isSensor: boolean = Whether this collider is a sensor.
1706
+ 7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
1707
+ */
1708
+ const circleCollider = new CircleCollider(
1709
+ rigidBody,
1710
+ 1.5,
1711
+ 1,
1712
+ 0.3,
1713
+ 0.8,
1714
+ false,
1715
+ gameObject
1716
+ );
1717
+
1718
+ gameObject.addComponent(circleCollider);
1719
+ ```
1720
+
1721
+ ![CircleCollider](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/circlecollider.png?raw=true)
1722
+
1723
+ The `CircleCollider` is specifically made bigger than the `Circle2D` component in this image to demonstrate how it works. You can adjust the radius of the collider to fit your needs.
1724
+
1725
+ ## Object methods
1726
+
1727
+ ### Position
1728
+
1729
+ ```javascript
1730
+ // Set position
1731
+ gameObject.transform.position.x = 100;
1732
+ gameObject.transform.position.y = 200;
1733
+ gameObject.transform.position.z = 0;
1734
+
1735
+ // Or set all at once
1736
+ gameObject.transform.position = new Vector3(100, 200, 0);
1737
+ ```
1738
+
1739
+ ![Position](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/position.gif?raw=true)
1740
+
1741
+ ### Rotation
1742
+
1743
+ ```javascript
1744
+ // Set rotation (in radians)
1745
+ gameObject.transform.rotation = Math.PI / 4; // 45 degrees
1746
+ ```
1747
+
1748
+ ![Rotation](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/rotation.gif?raw=true)
1749
+
1750
+ ### Scale
1751
+
1752
+ ```javascript
1753
+ // Set scale
1754
+ gameObject.transform.scale.x = 2;
1755
+ gameObject.transform.scale.y = 2;
1756
+
1757
+ // Or set both at once
1758
+ gameObject.transform.scale = new Vector2(2, 2);
1759
+ ```
1760
+
1761
+ ![Scale](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/scale.gif?raw=true)
1762
+
1763
+ ### Change color
1764
+
1765
+ ```javascript
1766
+ // For textures
1767
+ const texture = gameObject.getComponent(Texture);
1768
+ texture.setColor(new Color(255, 0, 0)); // Red
1769
+ ```
1770
+
1771
+ ![Change Color](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/changecolor.gif?raw=true)
1772
+
1773
+ ### Set texture frame
1774
+
1775
+ ```javascript
1776
+ // For animated textures
1777
+ const texture = gameObject.getComponent(Texture);
1778
+ texture.setFrame(2); // Set to frame 2
1779
+ ```
1780
+
1781
+ ## Animations
1782
+
1783
+ ```javascript
1784
+ // Play animation
1785
+ texture.playAnimation([0, 1, 2, 3], 200); // frames array, speed in ms
1786
+
1787
+ // Stop animation
1788
+ texture.stopAnimation();
1789
+
1790
+ // Check if playing
1791
+ if (texture.isPlaying) {
1792
+ // Animation is currently playing
1793
+ }
1794
+ ```
1795
+
1796
+ ![Animations](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/animations.gif?raw=true)
1797
+
1798
+ ## Instance System
1799
+
1800
+ The Instance system allows you to efficiently manage multiple copies of the same object.
1801
+
1802
+ ### Creating Instances
1803
+
1804
+ ```javascript
1805
+ import { Instance } from "emeraldengine";
1806
+
1807
+ // Create an instance
1808
+ const instance = new Instance(
1809
+ "InstanceName",
1810
+ new Vector3(x, y, z),
1811
+ new Vector2(width, height),
1812
+ rotation,
1813
+ frame
1814
+ );
1815
+
1816
+ // Add to InstancedTexture
1817
+ const instancedTexture = gameObject.getComponent(InstancedTexture);
1818
+ instancedTexture.addInstance(instance);
1819
+ ```
1820
+
1821
+ ### Instance Management
1822
+
1823
+ ```javascript
1824
+ // Remove instance
1825
+ instancedTexture.removeInstance(instanceId);
1826
+
1827
+ // Get instance by ID
1828
+ const instance = instancedTexture.getInstanceWithId(instanceId);
1829
+
1830
+ // Get instance at position
1831
+ const instance = instancedTexture.getInstanceAtPosition(position, tolerance);
1832
+
1833
+ // Clear all instances
1834
+ instancedTexture.clearInstances();
1835
+ ```
1836
+
1837
+ ### Instance Events
1838
+
1839
+ ```javascript
1840
+ // Add click event to specific instance
1841
+ instancedTexture.addInstanceClickEvent(instanceId, (event) => {
1842
+ console.log("Instance clicked!");
1843
+ });
1844
+
1845
+ // Add hover events to specific instance
1846
+ instancedTexture.addInstanceHoverEvent(
1847
+ instanceId,
1848
+ (event) => console.log("Mouse entered"),
1849
+ (event) => console.log("Mouse left")
1850
+ );
1851
+ ```
1852
+
1853
+ ## Physics Engine
1854
+
1855
+ Emerald includes a comprehensive physics engine built on top of Planck.js.
1856
+
1857
+ ### Setting up Physics
1858
+
1859
+ ```javascript
1860
+ import { Physics } from "emeraldengine";
1861
+ /*
1862
+ ARGUMENTS:
1863
+ 1. gravity: number = Gravity force (negative for downward)
1864
+ 2. scale: number = Scale factor for physics units to pixels
1865
+ 3. velocityThreshold: number = Minimum velocity threshold
1866
+ */
1867
+ const physics = new Physics(-70, 32, 2);
1868
+ ```
1869
+
1870
+ ### Physics Bodies
1871
+
1872
+ ```javascript
1873
+ // Get the physics body from a RigidBody component
1874
+ const body = rigidBody.getBody();
1875
+
1876
+ // Set velocity
1877
+ body.setLinearVelocity(new Vector2(10, 0));
1878
+
1879
+ // Get velocity
1880
+ const velocity = body.getLinearVelocity();
1881
+
1882
+ // Get position
1883
+ const position = body.getPosition();
1884
+ ```
1885
+
1886
+ ### Collision Detection
1887
+
1888
+ ```javascript
1889
+ // Handle collision enter
1890
+ physics.onCollisionEnter((bodyA, bodyB, contact) => {
1891
+ console.log("Collision started!");
1892
+
1893
+ // Get collision normal
1894
+ const normal = contact.getWorldManifold().normal;
1895
+ //normal.y = -1 when player is on the ground
1896
+ //normal.y = 1 when player hits the ceiling
1897
+ //normal.x = -1 when player hits the left wall
1898
+ //normal.x = 1 when player hits the right wall
1899
+
1900
+ // Check if bodies are sensors
1901
+ const fixtureA = contact.getFixtureA();
1902
+ const fixtureB = contact.getFixtureB();
1903
+ if (fixtureA.isSensor() || fixtureB.isSensor()) {
1904
+ // Handle sensor collision
1905
+ }
1906
+ });
1907
+
1908
+ // Handle collision exit
1909
+ physics.onCollisionExit((bodyA, bodyB, contact) => {
1910
+ console.log("Collision ended!");
1911
+ });
1912
+ ```
1913
+
1914
+ ### Collision Events
1915
+
1916
+ ```javascript
1917
+ // Process physics in your update loop
1918
+ const animate = (currentTime) => {
1919
+ physics.process(deltaTime);
1920
+ };
1921
+ ```
1922
+
1923
+ ## Particle System
1924
+
1925
+ Emerald includes a powerful particle system for creating visual effects.
1926
+
1927
+ ![Particles](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/particles.gif?raw=true)
1928
+
1929
+ ### Particle Settings
1930
+
1931
+ ```javascript
1932
+ import { ParticleSettings } from "emeraldengine";
1933
+
1934
+ const particleSettings = new ParticleSettings({
1935
+ lifetime: 1.2,
1936
+ velocity: new Vector2(200, 300),
1937
+ gravity: new Vector2(0, -400),
1938
+ amount: 16,
1939
+ direction: new Vector2(0, 1), // upward
1940
+ spread: Math.PI * 2,
1941
+ emissionRate: Infinity, // one-shot emission
1942
+ frame: 0,
1943
+ offset: 5,
1944
+ rotation: 0,
1945
+ scale: new Vector2(5, 5),
1946
+ animation: { frames: [0, 1, 2], speed: 200 },
1947
+ });
1948
+ ```
1949
+
1950
+ ### Creating Particle Systems
1951
+
1952
+ ```javascript
1953
+ import { Particles } from "emeraldengine";
1954
+
1955
+ /*
1956
+ ARGUMENTS:
1957
+ 1. name: string = Name of the particle system
1958
+ 2. texturePath: string = Path to the texture
1959
+ 3. frameWidth: number = Width of each frame
1960
+ 4. frameHeight: number = Height of each frame
1961
+ 5. framesPerRow: number = Frames per row in spritesheet
1962
+ 6. totalFrames: number = Total frames in spritesheet
1963
+ 7. duration: number = Duration of the effect
1964
+ 8. settings: ParticleSettings = Particle settings object
1965
+ */
1966
+ const particles = new Particles(
1967
+ "explosion",
1968
+ texturePath,
1969
+ 16,
1970
+ 16,
1971
+ 9,
1972
+ 27,
1973
+ 1.2,
1974
+ particleSettings
1975
+ );
1976
+
1977
+ // Add to scene
1978
+ scene.add(particles.gameObject);
1979
+ ```
1980
+
1981
+ ### Particle System Methods
1982
+
1983
+ ```javascript
1984
+ // Play particle effect at position
1985
+ particles.play(new Vector3(x, y, z));
1986
+
1987
+ // Stop particle system
1988
+ particles.stop();
1989
+
1990
+ // Reset particle system
1991
+ particles.reset();
1992
+
1993
+ // Update particles (call in your animation loop)
1994
+ particles.update(deltaTime);
1995
+
1996
+ // Check if active
1997
+ if (particles.active) {
1998
+ // Particles are currently active
1999
+ }
2000
+ ```
2001
+
2002
+ ## Lighting System
2003
+
2004
+ Emerald supports ambient, point, and directional lighting.
2005
+
2006
+ ### Ambient Light
2007
+
2008
+ ```javascript
2009
+ // Set ambient light
2010
+ emerald.setAmbientLight(new Vector3(0.3, 0.3, 0.3)); // RGB values 0-1
2011
+ ```
2012
+
2013
+ ### Point Light
2014
+
2015
+ ```javascript
2016
+ import { PointLight } from "emeraldengine";
2017
+
2018
+ /*
2019
+ ARGUMENTS:
2020
+ 1. position: Vector2 = Position of the light
2021
+ 2. color: Color = Color of the light
2022
+ 3. intensity: number = Light intensity
2023
+ 4. radius: number = Light radius
2024
+ */
2025
+ const pointLight = new PointLight(
2026
+ new Vector2(100, 0),
2027
+ new Color(255, 204, 153),
2028
+ 1.5,
2029
+ 400
2030
+ );
2031
+
2032
+ // Add to engine
2033
+ emerald.addPointLight(pointLight);
2034
+
2035
+ // Update position
2036
+ pointLight.position.x = newX;
2037
+ pointLight.position.y = newY;
2038
+ ```
2039
+
2040
+ ### Directional Light
2041
+
2042
+ ```javascript
2043
+ import { DirectionalLight } from "emeraldengine";
2044
+
2045
+ /*
2046
+ ARGUMENTS:
2047
+ 1. position: Vector2 = Position of the light
2048
+ 2. direction: Vector2 = Direction vector
2049
+ 3. color: Color = Color of the light
2050
+ 4. intensity: number = Light intensity
2051
+ 5. width: number = Width of the light beam
2052
+ */
2053
+ const directionalLight = new DirectionalLight(
2054
+ new Vector2(0, 300),
2055
+ new Vector2(0, -1), // pointing down
2056
+ new Color(255, 255, 255),
2057
+ 3.0,
2058
+ 200
2059
+ );
2060
+
2061
+ // Add to engine
2062
+ emerald.addDirectionalLight(directionalLight);
2063
+
2064
+ // Rotate direction
2065
+ const angle = 0.1;
2066
+ const newX =
2067
+ directionalLight.direction.x * Math.cos(angle) -
2068
+ directionalLight.direction.y * Math.sin(angle);
2069
+ const newY =
2070
+ directionalLight.direction.x * Math.sin(angle) +
2071
+ directionalLight.direction.y * Math.cos(angle);
2072
+ directionalLight.direction.x = newX;
2073
+ directionalLight.direction.y = newY;
2074
+ ```
2075
+
2076
+ ## Text Rendering
2077
+
2078
+ ### BitmapText
2079
+
2080
+ Emerald supports bitmap font rendering using the BitmapText component. This allows you to display text with custom fonts and styles.
2081
+
2082
+ ![BitmapText](https://github.com/vahan-gev/emeralddocs/blob/main/github/screenshots/bitmaptext.png?raw=true)
2083
+
2084
+ ```javascript
2085
+ import { BitmapText } from "emeraldengine";
2086
+
2087
+ /*
2088
+ ARGUMENTS:
2089
+ 1. text: string = Text to display
2090
+ 2. texturePath: string = Path to bitmap font texture
2091
+ 3. letters: string = String containing all available characters
2092
+ 4. letterSpacing: number = Spacing between letters
2093
+ 5. frameWidth: number = Width of each character frame
2094
+ 6. frameHeight: number = Height of each character frame
2095
+ 7. framesPerRow: number = Characters per row in font texture
2096
+ 8. totalFrames: number = Total character frames
2097
+ 9. pixelArt: boolean = Whether to use pixel art rendering
2098
+ 10. fontSize: number = Font size
2099
+ 11. color: Color = Text color
2100
+ 12. position: Vector3 = Text position
2101
+ 13. rotation: number = Text rotation
2102
+ 14. useLighting: boolean = Whether text should react to lighting
2103
+ */
2104
+ const bitmapText = new BitmapText(
2105
+ "Hello World!",
2106
+ fontTexturePath,
2107
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?.",
2108
+ 16,
2109
+ 32,
2110
+ 32,
2111
+ 10,
2112
+ 95,
2113
+ true,
2114
+ 24,
2115
+ new Color(255, 255, 255),
2116
+ new Vector3(0, 200, 0),
2117
+ 0,
2118
+ false
2119
+ );
2120
+
2121
+ // Add to scene
2122
+ scene.add(bitmapText.gameObject);
2123
+
2124
+ // Update text
2125
+ bitmapText.setText("New Text!");
2126
+ bitmapText.setColor(new Color(255, 0, 0));
2127
+ bitmapText.setFontSize(32);
2128
+ bitmapText.setLetterSpacing(20);
2129
+ ```
2130
+
2131
+ ### CanvasText (system fonts, word-wrap, Retina-crisp)
2132
+
2133
+ `CanvasText` renders any CSS font (including loaded webfonts) into a texture.
2134
+ It renders at the device pixel ratio, so text is crisp on Retina/HiDPI
2135
+ displays, and it supports multi-line strings with word-wrapping and alignment:
2136
+
2137
+ ```javascript
2138
+ import { CanvasText } from "emeraldengine";
2139
+
2140
+ // Factory: returns a GameObject already sized to the text
2141
+ const label = CanvasText.create("Score: 0", {
2142
+ font: "700 24px 'Pixelify Sans', sans-serif",
2143
+ color: "#8fe0ff",
2144
+ screenSpace: true, // HUD: fixed on screen, position in px from center
2145
+ position: new Vector3(0, 240, 0),
2146
+ });
2147
+ scene.add(label);
2148
+
2149
+ // Multi-line + wrapping
2150
+ const dialog = CanvasText.create(
2151
+ "A long line of dialogue that wraps automatically.\nExplicit breaks work too.",
2152
+ { font: "16px system-ui", maxWidth: 320, align: "left", lineHeight: 22 }
2153
+ );
2154
+
2155
+ // Updating (re-renders the texture; attached GameObject rescales to fit)
2156
+ const text = label.getComponent(CanvasText);
2157
+ text.setText("Score: 120");
2158
+ text.setColor("#ffd166");
2159
+ text.setMaxWidth(400);
2160
+ text.setAlign("center"); // "left" | "center" | "right"
2161
+ ```
2162
+
2163
+ Rule of thumb: `BitmapText` for retro/pixel fonts from a glyph sheet,
2164
+ `CanvasText` for everything else (UI, dialogue, any real font).
2165
+
2166
+ ## EventManager
2167
+
2168
+ Emerald supports keyboard, mouse, click, and hover events. All events are handled using the built-in EventManager class.
2169
+
2170
+ ![EventManager](https://github.com/vahan-gev/emeralddocs/blob/main/github/videos/eventmanager.gif?raw=true)
2171
+
2172
+ ```javascript
2173
+ import { EventManager } from "emeraldengine";
2174
+
2175
+ let eventManager = new EventManager(canvas, scene, emerald.camera);
2176
+ ```
2177
+
2178
+ ### Keyboard Events
2179
+
2180
+ ```javascript
2181
+ // Key down events
2182
+ eventManager.addKeyDown("w", () => {
2183
+ console.log("W key pressed");
2184
+ });
2185
+
2186
+ // Key up events
2187
+ eventManager.addKeyUp("w", () => {
2188
+ console.log("W key released");
2189
+ });
2190
+
2191
+ // Check if key is currently pressed
2192
+ if (eventManager.isKeyPressed("w")) {
2193
+ // W key is currently held down
2194
+ }
2195
+
2196
+ // Remove key events
2197
+ eventManager.removeKeyDown("w", callbackFunction);
2198
+ eventManager.removeKeyUp("w", callbackFunction);
2199
+ ```
2200
+
2201
+ ### Mouse Events
2202
+
2203
+ ```javascript
2204
+ // Get mouse position
2205
+ const mousePos = eventManager.getMousePosition();
2206
+ console.log(mousePos.x, mousePos.y);
2207
+
2208
+ // Check if camera was moved
2209
+ if (eventManager.wasCameraMoved()) {
2210
+ // Camera was moved by dragging
2211
+ eventManager.resetCameraMoved();
2212
+ }
2213
+ ```
2214
+
2215
+ ### Object Events
2216
+
2217
+ ```javascript
2218
+ // Click events
2219
+ eventManager.addClickEvent(gameObject, (event, object) => {
2220
+ console.log("Object clicked!");
2221
+ });
2222
+
2223
+ // Hover events
2224
+ eventManager.addHoverEvent(
2225
+ gameObject,
2226
+ (event) => {
2227
+ console.log("Mouse entered object");
2228
+ },
2229
+ (event) => {
2230
+ console.log("Mouse left object");
2231
+ }
2232
+ );
2233
+
2234
+ // Remove events
2235
+ eventManager.removeClickEvent(gameObject, callbackFunction);
2236
+ eventManager.removeHoverEvent(gameObject, enterCallback, leaveCallback);
2237
+ ```
2238
+
2239
+ ### Event Cleanup
2240
+
2241
+ ```javascript
2242
+ // Clean up all events when done
2243
+ eventManager.clean();
2244
+
2245
+ // Change scene
2246
+ eventManager.changeScene(newScene);
2247
+ ```
2248
+
2249
+ ## AudioManager
2250
+
2251
+ Emerald includes a comprehensive audio management system.
2252
+
2253
+ ### Adding Audio
2254
+
2255
+ ```javascript
2256
+ import { AudioManager } from "emeraldengine";
2257
+
2258
+ const audioManager = new AudioManager();
2259
+
2260
+ // Add audio files
2261
+ audioManager.add("path/to/sound.wav", "soundName");
2262
+ audioManager.add("path/to/music.mp3", "backgroundMusic");
2263
+ ```
2264
+
2265
+ ### Playing Audio
2266
+
2267
+ ```javascript
2268
+ // Play audio
2269
+ audioManager.play("soundName");
2270
+
2271
+ // Play exclusively (stops all other audio first)
2272
+ audioManager.playExclusive("soundName");
2273
+ ```
2274
+
2275
+ ### Audio Control
2276
+
2277
+ ```javascript
2278
+ // Stop specific audio
2279
+ audioManager.stop("soundName");
2280
+
2281
+ // Stop all audio
2282
+ audioManager.stopAll();
2283
+
2284
+ // Remove audio
2285
+ audioManager.remove("soundName");
2286
+
2287
+ // Get audio object
2288
+ const sound = audioManager.getSound("soundName");
2289
+ ```
2290
+
2291
+ ## Camera
2292
+
2293
+ The engine has simple controls for the camera. The camera is stored in the emerald variable.
2294
+
2295
+ ```javascript
2296
+ // Set camera position
2297
+ emerald.camera.setPosition(x, y, z);
2298
+
2299
+ // Access camera transform directly
2300
+ emerald.camera.transform.position.x = 100;
2301
+ emerald.camera.transform.position.y = 200;
2302
+ emerald.camera.transform.scale.x = 1.5;
2303
+ emerald.camera.transform.scale.y = 1.5;
2304
+ ```
2305
+
2306
+ ## FPSCounter
2307
+
2308
+ Emerald has a built-in FPS counter.
2309
+
2310
+ ```javascript
2311
+ import { FPSCounter } from "emeraldengine";
2312
+ let fpsCounter = new FPSCounter();
2313
+
2314
+ const animate = (currentTime) => {
2315
+ emerald.drawScene(scene, deltaTime);
2316
+ fpsCounter.update(); // Call this in your animation loop
2317
+ window.requestAnimationFrame(animate);
2318
+ };
2319
+ animate();
2320
+ ```
2321
+
2322
+ ## Scene Management
2323
+
2324
+ ```javascript
2325
+ import { SceneManager } from "emeraldengine";
2326
+
2327
+ // Set active scene
2328
+ SceneManager.setScene(scene);
2329
+
2330
+ // Get current scene
2331
+ const currentScene = SceneManager.getScene();
2332
+ ```
2333
+
2334
+ ## Time Management
2335
+
2336
+ ```javascript
2337
+ import { Time } from "emeraldengine";
2338
+
2339
+ // Get delta time
2340
+ const deltaTime = Time.deltaTime;
2341
+
2342
+ // Time is automatically updated when you call emerald.drawScene()
2343
+ // You can also manually set it
2344
+ Time.setDeltaTime(deltaTime);
2345
+ ```
2346
+
2347
+ ## Advanced Features
2348
+
2349
+ ### Resize Handling
2350
+
2351
+ ```javascript
2352
+ // Handle window resize
2353
+ const handleResize = () => {
2354
+ const { width, height } = getCanvasDimensions();
2355
+ emerald.resize(width, height);
2356
+ };
2357
+
2358
+ window.addEventListener("resize", handleResize);
2359
+ ```