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