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
@@ -0,0 +1,192 @@
1
+ import MathUtils from "./MathUtils.js";
2
+
3
+ /**
4
+ * @class CameraController
5
+ * @description Drives an Emerald camera with follow, bounds clamping, zoom and
6
+ * screen shake. Construct it with `emerald.camera` and call `update(dt)` once
7
+ * per frame.
8
+ *
9
+ * @example
10
+ * const cam = new CameraController(emerald.camera);
11
+ * cam.follow(player.gameObject, 0.1).setBounds(-1000, -1000, 1000, 1000);
12
+ * // in the loop: cam.update(deltaTime);
13
+ */
14
+ class CameraController {
15
+ /**
16
+ * @param {Object} camera - The Emerald camera (e.g. emerald.camera)
17
+ */
18
+ constructor(camera) {
19
+ this.camera = camera;
20
+ this.target = null;
21
+ this.smoothing = 1;
22
+ this.bounds = null;
23
+
24
+ const start = camera.getPosition ? camera.getPosition() : { x: 0, y: 0 };
25
+ this.position = { x: start.x, y: start.y };
26
+
27
+ this.shakeIntensity = 0;
28
+ this.shakeDuration = 0;
29
+ this.shakeElapsed = Infinity;
30
+
31
+ this.deadzone = null;
32
+ }
33
+
34
+ /**
35
+ * @method setDeadzone
36
+ * @description Sets a centered follow deadzone. The camera only scrolls when
37
+ * the target moves outside this box, giving the player room to move without
38
+ * the camera reacting to every step. Pass 0/0 or null to disable.
39
+ * @param {number} halfWidth - Horizontal half-extent in world units
40
+ * @param {number} halfHeight - Vertical half-extent in world units
41
+ * @returns {CameraController} - this
42
+ */
43
+ setDeadzone(halfWidth, halfHeight) {
44
+ this.deadzone =
45
+ halfWidth || halfHeight
46
+ ? { x: Math.abs(halfWidth || 0), y: Math.abs(halfHeight || 0) }
47
+ : null;
48
+ return this;
49
+ }
50
+
51
+ /**
52
+ * @method follow
53
+ * @description Follows a target each frame.
54
+ * @param {Object} target - GameObject/Instance (uses transform.position) or a { x, y }
55
+ * @param {number} smoothing - 0..1, higher snaps faster (1 = instant)
56
+ * @returns {CameraController} - this
57
+ */
58
+ follow(target, smoothing = 0.1) {
59
+ this.target = target;
60
+ this.smoothing = smoothing;
61
+ return this;
62
+ }
63
+
64
+ /**
65
+ * @method stopFollow
66
+ * @description Stops following the current target.
67
+ * @returns {CameraController} - this
68
+ */
69
+ stopFollow() {
70
+ this.target = null;
71
+ return this;
72
+ }
73
+
74
+ /**
75
+ * @method setBounds
76
+ * @description Clamps the camera center to a world-space rectangle.
77
+ * @returns {CameraController} - this
78
+ */
79
+ setBounds(minX, minY, maxX, maxY) {
80
+ this.bounds = { minX, minY, maxX, maxY };
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * @method clearBounds
86
+ * @description Removes bounds clamping.
87
+ * @returns {CameraController} - this
88
+ */
89
+ clearBounds() {
90
+ this.bounds = null;
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * @method setZoom
96
+ * @description Sets the camera zoom (1 = default, >1 zooms in).
97
+ * @returns {CameraController} - this
98
+ */
99
+ setZoom(zoom) {
100
+ if (this.camera.setZoom) this.camera.setZoom(zoom);
101
+ return this;
102
+ }
103
+
104
+ /**
105
+ * @method getZoom
106
+ * @returns {number} - The current zoom
107
+ */
108
+ getZoom() {
109
+ return this.camera.getZoom ? this.camera.getZoom() : 1;
110
+ }
111
+
112
+ /**
113
+ * @method setPosition
114
+ * @description Immediately moves the camera center.
115
+ * @returns {CameraController} - this
116
+ */
117
+ setPosition(x, y) {
118
+ this.position.x = x;
119
+ this.position.y = y;
120
+ this.camera.setPosition(x, y, 0);
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * @method shake
126
+ * @description Triggers a screen shake that decays over its duration.
127
+ * @param {number} intensity - Max offset in world units
128
+ * @param {number} duration - Duration in seconds
129
+ * @returns {CameraController} - this
130
+ */
131
+ shake(intensity, duration) {
132
+ this.shakeIntensity = intensity;
133
+ this.shakeDuration = duration;
134
+ this.shakeElapsed = 0;
135
+ return this;
136
+ }
137
+
138
+ /** @private */
139
+ _targetPosition() {
140
+ const t = this.target;
141
+ if (!t) return null;
142
+ if (t.transform && t.transform.position) return t.transform.position;
143
+ return t;
144
+ }
145
+
146
+ /**
147
+ * @method update
148
+ * @description Advances follow/shake and applies the result to the camera.
149
+ * @param {number} dt - Seconds since the last frame
150
+ */
151
+ update(dt) {
152
+ const targetPos = this._targetPosition();
153
+ if (targetPos) {
154
+ let goalX = targetPos.x;
155
+ let goalY = targetPos.y;
156
+ if (this.deadzone) {
157
+ goalX = this.position.x;
158
+ goalY = this.position.y;
159
+ const dx = targetPos.x - this.position.x;
160
+ const dy = targetPos.y - this.position.y;
161
+ if (dx > this.deadzone.x) goalX = targetPos.x - this.deadzone.x;
162
+ else if (dx < -this.deadzone.x) goalX = targetPos.x + this.deadzone.x;
163
+ if (dy > this.deadzone.y) goalY = targetPos.y - this.deadzone.y;
164
+ else if (dy < -this.deadzone.y) goalY = targetPos.y + this.deadzone.y;
165
+ }
166
+
167
+ const s = MathUtils.clamp(this.smoothing, 0, 1);
168
+ const a = s >= 1 ? 1 : 1 - Math.pow(1 - s, dt * 60);
169
+ this.position.x = MathUtils.lerp(this.position.x, goalX, a);
170
+ this.position.y = MathUtils.lerp(this.position.y, goalY, a);
171
+ }
172
+
173
+ let x = this.position.x;
174
+ let y = this.position.y;
175
+
176
+ if (this.bounds) {
177
+ x = MathUtils.clamp(x, this.bounds.minX, this.bounds.maxX);
178
+ y = MathUtils.clamp(y, this.bounds.minY, this.bounds.maxY);
179
+ }
180
+
181
+ if (this.shakeElapsed < this.shakeDuration) {
182
+ this.shakeElapsed += dt;
183
+ const falloff = Math.max(0, 1 - this.shakeElapsed / this.shakeDuration);
184
+ x += (Math.random() * 2 - 1) * this.shakeIntensity * falloff;
185
+ y += (Math.random() * 2 - 1) * this.shakeIntensity * falloff;
186
+ }
187
+
188
+ this.camera.setPosition(x, y, 0);
189
+ }
190
+ }
191
+
192
+ export default CameraController;
@@ -0,0 +1,281 @@
1
+ import Drawable from "./Drawable.js";
2
+ import GameObject from "./components/GameObject.js";
3
+ import GLManager from "./managers/GLManager.js";
4
+ import { initVertexBuffer } from "./GLUtils.js";
5
+ import { Vector2, Vector3 } from "./Physics.js";
6
+
7
+ /**
8
+ * @class CanvasText
9
+ * @description Renders dynamic text using a system font by drawing to an
10
+ * offscreen 2D canvas and uploading it as a texture. Unlike BitmapText it needs
11
+ * no font sheet and supports any installed font. It's a Drawable component;
12
+ * `CanvasText.create()` returns a ready GameObject sized to the text.
13
+ *
14
+ * @example
15
+ * const label = CanvasText.create("Score: 0", {
16
+ * font: "bold 28px monospace", color: "#6ee7b7", screenSpace: true,
17
+ * });
18
+ * label.transform.position = new Vector3(-300, 260, 100);
19
+ * scene.add(label);
20
+ * label.getComponent(CanvasText).setText("Score: 120");
21
+ */
22
+ class CanvasText extends Drawable {
23
+ constructor(text, options = {}) {
24
+ const gl = GLManager.getGL();
25
+ const vertices = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
26
+ const verticesBuffer = initVertexBuffer(gl, vertices);
27
+ const texCoords = [1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0];
28
+ const texCoordBuffer = initVertexBuffer(gl, texCoords);
29
+
30
+ super(
31
+ gl,
32
+ GLManager.getProgramInfo(),
33
+ verticesBuffer,
34
+ texCoordBuffer,
35
+ vertices,
36
+ false,
37
+ "",
38
+ 0,
39
+ 0,
40
+ 1,
41
+ 1,
42
+ 0,
43
+ false,
44
+ false,
45
+ options.useLighting === true
46
+ );
47
+
48
+ this.font = options.font || "24px sans-serif";
49
+ this.fillStyle = options.color || "#ffffff";
50
+ this.padding = options.padding != null ? options.padding : 6;
51
+ this.maxWidth = options.maxWidth || 0;
52
+ this.align = options.align || "center";
53
+ this.lineHeight = options.lineHeight || 0;
54
+ /** @private */
55
+ this._canvas = document.createElement("canvas");
56
+ /** @private */
57
+ this._ctx = this._canvas.getContext("2d");
58
+
59
+ this.useTexture = true;
60
+ this.texture = gl.createTexture();
61
+ this.width = 1;
62
+ this.height = 1;
63
+
64
+ this.setText(text);
65
+ }
66
+
67
+ /**
68
+ * @method setText
69
+ * @description Re-renders the text and re-uploads the texture. If attached to
70
+ * a GameObject, its scale is updated to match the new size.
71
+ * @param {string} text
72
+ */
73
+ setText(text) {
74
+ this.text = text;
75
+ const ctx = this._ctx;
76
+ const dpr = (typeof window !== "undefined" && window.devicePixelRatio) || 1;
77
+
78
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
79
+ ctx.font = this.font;
80
+ const fontSize = parseInt(this.font, 10) || 24;
81
+ const lineHeight = this.lineHeight || Math.ceil(fontSize * 1.4);
82
+
83
+ const lines = CanvasText.wrapText(
84
+ (s) => ctx.measureText(s).width,
85
+ String(text),
86
+ this.maxWidth
87
+ );
88
+
89
+ let maxLineW = 1;
90
+ for (const ln of lines) {
91
+ const m = ctx.measureText(ln);
92
+ let lw = m.width;
93
+ if (m.actualBoundingBoxLeft != null && m.actualBoundingBoxRight != null) {
94
+ lw = Math.max(lw, m.actualBoundingBoxLeft + m.actualBoundingBoxRight);
95
+ }
96
+ maxLineW = Math.max(maxLineW, lw);
97
+ }
98
+ const contentW = this.maxWidth
99
+ ? Math.max(maxLineW, this.maxWidth)
100
+ : maxLineW;
101
+ const w = Math.max(1, Math.ceil(contentW) + this.padding * 2);
102
+ const h = lines.length * lineHeight + this.padding * 2;
103
+
104
+ this._canvas.width = Math.max(1, Math.round(w * dpr));
105
+ this._canvas.height = Math.max(1, Math.round(h * dpr));
106
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
107
+ ctx.clearRect(0, 0, w, h);
108
+ ctx.font = this.font;
109
+ ctx.textBaseline = "middle";
110
+ ctx.textAlign = this.align;
111
+ ctx.fillStyle = this.fillStyle;
112
+
113
+ const x =
114
+ this.align === "left"
115
+ ? this.padding
116
+ : this.align === "right"
117
+ ? w - this.padding
118
+ : w / 2;
119
+ for (let i = 0; i < lines.length; i++) {
120
+ const y = this.padding + lineHeight * (i + 0.5);
121
+ ctx.fillText(lines[i], x, y);
122
+ }
123
+
124
+ const gl = this.gl;
125
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
126
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
127
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
128
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
129
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
130
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
131
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
132
+ gl.texImage2D(
133
+ gl.TEXTURE_2D,
134
+ 0,
135
+ gl.RGBA,
136
+ gl.RGBA,
137
+ gl.UNSIGNED_BYTE,
138
+ this._canvas
139
+ );
140
+
141
+ this.textureWidth = this._canvas.width;
142
+ this.textureHeight = this._canvas.height;
143
+ this.originalTexture = this.texture;
144
+ this.width = w;
145
+ this.height = h;
146
+ /** @private */
147
+ this._lastTextureWidth = -1;
148
+
149
+ if (this.parentObject && this.parentObject.transform) {
150
+ this.parentObject.transform.scale.x = w / 2;
151
+ this.parentObject.transform.scale.y = h / 2;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * @method wrapText
157
+ * @description Pure helper that breaks `text` into rendered lines. Honors
158
+ * explicit `\n` line breaks and, when `maxWidth > 0`, greedily word-wraps each
159
+ * paragraph so no line measures wider than `maxWidth`. A single word longer
160
+ * than `maxWidth` is kept on its own line rather than being split.
161
+ * @param {(s:string)=>number} measure - Returns the pixel width of a string
162
+ * @param {string} text - The text to lay out
163
+ * @param {number} maxWidth - Wrap width in pixels (0 disables wrapping)
164
+ * @returns {string[]} - The lines to render top to bottom
165
+ */
166
+ static wrapText(measure, text, maxWidth) {
167
+ const hardLines = text.split("\n");
168
+ if (!maxWidth || maxWidth <= 0) return hardLines;
169
+ const out = [];
170
+ for (const hard of hardLines) {
171
+ const words = hard.split(" ");
172
+ let line = "";
173
+ for (const word of words) {
174
+ const test = line ? line + " " + word : word;
175
+ if (line && measure(test) > maxWidth) {
176
+ out.push(line);
177
+ line = word;
178
+ } else {
179
+ line = test;
180
+ }
181
+ }
182
+ out.push(line);
183
+ }
184
+ return out;
185
+ }
186
+
187
+ /**
188
+ * @method setMaxWidth
189
+ * @description Sets the wrap width in pixels (0 disables wrapping) and
190
+ * re-renders.
191
+ * @param {number} px
192
+ */
193
+ setMaxWidth(px) {
194
+ this.maxWidth = px || 0;
195
+ this.setText(this.text);
196
+ }
197
+
198
+ /**
199
+ * @method setAlign
200
+ * @description Sets horizontal alignment ("left" | "center" | "right") and
201
+ * re-renders.
202
+ * @param {string} align
203
+ */
204
+ setAlign(align) {
205
+ this.align = align;
206
+ this.setText(this.text);
207
+ }
208
+
209
+ /**
210
+ * @method setColor
211
+ * @description Sets the text color (CSS color string) and re-renders.
212
+ * @param {string} cssColor
213
+ */
214
+ setColor(cssColor) {
215
+ this.fillStyle = cssColor;
216
+ this.setText(this.text);
217
+ }
218
+
219
+ /**
220
+ * @method getSize
221
+ * @returns {{width:number, height:number}} - The canvas pixel size
222
+ */
223
+ getSize() {
224
+ return { width: this.width, height: this.height };
225
+ }
226
+
227
+ /**
228
+ * @method _restoreGL
229
+ * @description After a context loss: rebuild the base buffers, recreate this
230
+ * text's own texture, and re-render the current string into it.
231
+ * @private
232
+ */
233
+ _restoreGL() {
234
+ if (this._disposed) return;
235
+ super._restoreGL();
236
+ if (!this.gl) return;
237
+ this._canvas = this._canvas || document.createElement("canvas");
238
+ this._ctx = this._canvas.getContext("2d");
239
+ this.texture = this.gl.createTexture();
240
+ this.setText(this.text);
241
+ }
242
+
243
+ /**
244
+ * @method dispose
245
+ * @description Frees the GL texture this text owns (CanvasText renders into
246
+ * its own texture, unlike path-loaded Textures which are shared) plus the
247
+ * base Drawable buffers. Safe to call twice.
248
+ */
249
+ dispose() {
250
+ if (this._disposed) return;
251
+ if (this.gl && this.texture) this.gl.deleteTexture(this.texture);
252
+ this.texture = null;
253
+ this.originalTexture = null;
254
+ this._canvas = null;
255
+ this._ctx = null;
256
+ super.dispose();
257
+ }
258
+
259
+ /**
260
+ * @method create
261
+ * @description Convenience factory returning a GameObject sized to the text.
262
+ * @param {string} text
263
+ * @param {Object} [options] - { name, position, screenSpace, font, color,
264
+ * maxWidth, align, lineHeight, padding }
265
+ * @returns {GameObject}
266
+ */
267
+ static create(text, options = {}) {
268
+ const component = new CanvasText(text, options);
269
+ const go = new GameObject(
270
+ options.name || `text:${text}`,
271
+ options.position || new Vector3(0, 0, 0),
272
+ 0,
273
+ new Vector2(component.width / 2, component.height / 2)
274
+ );
275
+ go.addComponent(component);
276
+ if (options.screenSpace) go.setScreenSpace(true);
277
+ return go;
278
+ }
279
+ }
280
+
281
+ export default CanvasText;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @class CollisionLayers
3
+ * @description A small registry that maps human-readable layer names to the
4
+ * category bits the physics engine uses for collision filtering, so games can
5
+ * "players collide with ground and enemies, but not with each other" without
6
+ * juggling raw bitmasks.
7
+ *
8
+ * Two fixtures collide only if each one's category is in the other's mask, so
9
+ * filtering is symmetric by construction. Up to 16 distinct layers are
10
+ * supported (filter bits are 16-bit).
11
+ *
12
+ * @example
13
+ * CollisionLayers.define("ground", "player", "enemy", "pickup");
14
+ * playerCollider.setCategory("player");
15
+ * playerCollider.setCollidesWith(["ground", "enemy", "pickup"]);
16
+ * enemyCollider.setCategory("enemy");
17
+ * enemyCollider.setCollidesWith(["ground", "player"]); // enemies ignore each other
18
+ */
19
+ const ALL_BITS = 0xffff;
20
+
21
+ class CollisionLayers {
22
+ /**
23
+ * @method define
24
+ * @description Registers one or more named layers (in order), assigning each
25
+ * the next free category bit. Re-defining an existing name returns its
26
+ * existing bit. Returns the bit of the last name passed.
27
+ * @param {...string} names
28
+ * @returns {number}
29
+ */
30
+ static define(...names) {
31
+ let last = 0;
32
+ for (const name of names) last = CollisionLayers.bit(name);
33
+ return last;
34
+ }
35
+
36
+ /**
37
+ * @method bit
38
+ * @description Returns the category bit for a layer name, registering it on
39
+ * first use.
40
+ * @param {string} name
41
+ * @returns {number}
42
+ */
43
+ static bit(name) {
44
+ if (CollisionLayers._bits.has(name)) return CollisionLayers._bits.get(name);
45
+ if (CollisionLayers._next > 15) {
46
+ throw new Error(
47
+ "[CollisionLayers] Exceeded 16 collision layers (filter bit limit)."
48
+ );
49
+ }
50
+ const bit = 1 << CollisionLayers._next++;
51
+ CollisionLayers._bits.set(name, bit);
52
+ return bit;
53
+ }
54
+
55
+ /**
56
+ * @method mask
57
+ * @description Computes a combined mask from layer names. Accepts a single
58
+ * name, an array of names, a number (passed through), or "all"/null for
59
+ * "collide with everything".
60
+ * @param {string|string[]|number|null} layers
61
+ * @returns {number}
62
+ */
63
+ static mask(layers) {
64
+ if (layers == null || layers === "all") return ALL_BITS;
65
+ if (typeof layers === "number") return layers;
66
+ const list = Array.isArray(layers) ? layers : [layers];
67
+ let m = 0;
68
+ for (const name of list) m |= CollisionLayers.bit(name);
69
+ return m;
70
+ }
71
+
72
+ /**
73
+ * @method reset
74
+ * @description Clears all registered layers (mainly for tests).
75
+ */
76
+ static reset() {
77
+ CollisionLayers._bits.clear();
78
+ CollisionLayers._next = 0;
79
+ }
80
+ }
81
+
82
+ CollisionLayers._bits = new Map();
83
+ CollisionLayers._next = 0;
84
+ CollisionLayers.ALL = ALL_BITS;
85
+
86
+ export default CollisionLayers;
package/src/Color.js CHANGED
@@ -1,18 +1,26 @@
1
- /**
2
- * @class Color
3
- * @description Represents a color
4
- * @param {number} r - The red value
5
- * @param {number} g - The green value
6
- * @param {number} b - The blue value
7
- * @param {number} a - The alpha value
8
- */
9
- class Color {
10
- constructor(r, g, b, a = 255) {
11
- this.r = r;
12
- this.g = g;
13
- this.b = b;
14
- this.a = a;
15
- }
16
- }
17
-
18
- export default Color;
1
+ /**
2
+ * @class Color
3
+ * @description Represents a color
4
+ * @param {number} r - The red value
5
+ * @param {number} g - The green value
6
+ * @param {number} b - The blue value
7
+ * @param {number} a - The alpha value
8
+ */
9
+ class Color {
10
+ constructor(r, g, b, a = 255) {
11
+ this.r = r;
12
+ this.g = g;
13
+ this.b = b;
14
+ this.a = a;
15
+ }
16
+
17
+ static fromHex(hex) {
18
+ const value = hex.startsWith("#") ? hex.slice(1) : hex;
19
+ const r = parseInt(value.slice(0, 2), 16);
20
+ const g = parseInt(value.slice(2, 4), 16);
21
+ const b = parseInt(value.slice(4, 6), 16);
22
+ return new Color(r, g, b, 255);
23
+ }
24
+ }
25
+
26
+ export default Color;