emeraldengine 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +2359 -968
  2. package/dist/types/index.d.ts +72 -32
  3. package/dist/types/src/Animator.d.ts +50 -0
  4. package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
  5. package/dist/types/src/Camera.d.ts +122 -0
  6. package/dist/types/src/CameraController.d.ts +107 -0
  7. package/dist/types/src/CanvasText.d.ts +91 -0
  8. package/dist/types/src/CollisionLayers.d.ts +58 -0
  9. package/dist/types/{Color.d.ts → src/Color.d.ts} +5 -6
  10. package/dist/types/src/Coroutine.d.ts +111 -0
  11. package/dist/types/src/DebugOverlay.d.ts +86 -0
  12. package/dist/types/src/Drawable.d.ts +271 -0
  13. package/dist/types/src/Easing.d.ts +22 -0
  14. package/dist/types/src/Emerald.d.ts +420 -0
  15. package/dist/types/src/EmeraldDB.d.ts +159 -0
  16. package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
  17. package/dist/types/src/GLUtils.d.ts +4 -0
  18. package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
  19. package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +60 -23
  20. package/dist/types/src/Interpolator.d.ts +66 -0
  21. package/dist/types/src/Material.d.ts +87 -0
  22. package/dist/types/src/MathUtils.d.ts +80 -0
  23. package/dist/types/src/ParticleEmitter.d.ts +131 -0
  24. package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
  25. package/dist/types/src/Pool.d.ts +53 -0
  26. package/dist/types/src/PostEffects.d.ts +68 -0
  27. package/dist/types/src/PostProcessor.d.ts +124 -0
  28. package/dist/types/src/RenderTarget.d.ts +56 -0
  29. package/dist/types/src/Scene.d.ts +62 -0
  30. package/dist/types/src/ScreenEffects.d.ts +111 -0
  31. package/dist/types/src/Serializer.d.ts +86 -0
  32. package/dist/types/src/Shaders.d.ts +2 -0
  33. package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
  34. package/dist/types/src/SpatialGrid.d.ts +50 -0
  35. package/dist/types/src/SpriteBatch.d.ts +89 -0
  36. package/dist/types/src/StateMachine.d.ts +59 -0
  37. package/dist/types/src/Storage.d.ts +89 -0
  38. package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
  39. package/dist/types/src/TextureAtlas.d.ts +55 -0
  40. package/dist/types/src/Tilemap.d.ts +91 -0
  41. package/dist/types/src/Time.d.ts +53 -0
  42. package/dist/types/src/Timer.d.ts +54 -0
  43. package/dist/types/src/Transform.d.ts +94 -0
  44. package/dist/types/src/Tween.d.ts +81 -0
  45. package/dist/types/src/UI.d.ts +121 -0
  46. package/dist/types/src/components/Behaviour.d.ts +71 -0
  47. package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
  48. package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
  49. package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
  50. package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
  51. package/dist/types/src/components/Collider.d.ts +96 -0
  52. package/dist/types/src/components/GameObject.d.ts +135 -0
  53. package/dist/types/src/components/RigidBody.d.ts +183 -0
  54. package/dist/types/src/importers/Aseprite.d.ts +79 -0
  55. package/dist/types/src/importers/TiledMap.d.ts +62 -0
  56. package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
  57. package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
  58. package/dist/types/src/managers/AssetManager.d.ts +116 -0
  59. package/dist/types/src/managers/AudioManager.d.ts +259 -0
  60. package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
  61. package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
  62. package/dist/types/src/managers/GLManager.d.ts +84 -0
  63. package/dist/types/src/managers/GLState.d.ts +37 -0
  64. package/dist/types/src/managers/IDManager.d.ts +31 -0
  65. package/dist/types/src/managers/InputManager.d.ts +290 -0
  66. package/dist/types/src/managers/NetworkManager.d.ts +93 -0
  67. package/dist/types/src/managers/RenderStats.d.ts +34 -0
  68. package/dist/types/src/managers/SceneManager.d.ts +44 -0
  69. package/dist/types/src/managers/ShaderManager.d.ts +55 -0
  70. package/dist/types/src/managers/TextureManager.d.ts +102 -0
  71. package/dist/types/src/particlesystem/Particle.d.ts +64 -0
  72. package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
  73. package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
  74. package/index.js +72 -0
  75. package/package.json +74 -60
  76. package/src/Animator.js +95 -0
  77. package/src/BitmapText.js +6 -5
  78. package/src/Camera.js +183 -0
  79. package/src/CameraController.js +192 -0
  80. package/src/CanvasText.js +281 -0
  81. package/src/CollisionLayers.js +86 -0
  82. package/src/Color.js +18 -18
  83. package/src/Coroutine.js +259 -0
  84. package/src/DebugOverlay.js +246 -0
  85. package/src/Drawable.js +842 -555
  86. package/src/Easing.js +57 -0
  87. package/src/Emerald.js +1150 -459
  88. package/src/EmeraldDB.js +328 -0
  89. package/src/FPSCounter.js +43 -43
  90. package/src/GLUtils.js +60 -67
  91. package/src/Instance.js +41 -5
  92. package/src/InstancedTexture.js +251 -118
  93. package/src/Interpolator.js +124 -0
  94. package/src/Material.js +202 -0
  95. package/src/MathUtils.js +133 -0
  96. package/src/ParticleEmitter.js +284 -0
  97. package/src/Physics.js +186 -5
  98. package/src/Pool.js +85 -0
  99. package/src/PostEffects.js +296 -0
  100. package/src/PostProcessor.js +304 -0
  101. package/src/RenderTarget.js +134 -0
  102. package/src/Scene.js +115 -83
  103. package/src/ScreenEffects.js +266 -0
  104. package/src/Serializer.js +131 -0
  105. package/src/Shaders.js +150 -165
  106. package/src/Shapes.js +118 -129
  107. package/src/SpatialGrid.js +111 -0
  108. package/src/SpriteBatch.js +299 -0
  109. package/src/StateMachine.js +82 -0
  110. package/src/Storage.js +175 -47
  111. package/src/Texture.js +58 -67
  112. package/src/TextureAtlas.js +96 -0
  113. package/src/Tilemap.js +274 -0
  114. package/src/Time.js +51 -6
  115. package/src/Timer.js +99 -0
  116. package/src/Transform.js +100 -7
  117. package/src/Tween.js +160 -0
  118. package/src/UI.js +394 -0
  119. package/src/components/Behaviour.js +90 -0
  120. package/src/components/BoxCollider.js +22 -3
  121. package/src/components/CircleCollider.js +19 -3
  122. package/src/components/CircleColliderDebug.js +24 -24
  123. package/src/components/Collider.js +140 -34
  124. package/src/components/GameObject.js +130 -21
  125. package/src/components/RigidBody.js +123 -2
  126. package/src/importers/Aseprite.js +142 -0
  127. package/src/importers/TiledMap.js +158 -0
  128. package/src/lights/DirectionalLight.js +6 -15
  129. package/src/lights/PointLight.js +4 -4
  130. package/src/managers/AssetManager.js +239 -0
  131. package/src/managers/AudioManager.js +565 -146
  132. package/src/managers/EventManager.js +488 -477
  133. package/src/managers/GLManager.js +57 -0
  134. package/src/managers/GLState.js +70 -0
  135. package/src/managers/IDManager.js +24 -2
  136. package/src/managers/InputManager.js +779 -0
  137. package/src/managers/NetworkManager.js +178 -0
  138. package/src/managers/RenderStats.js +34 -0
  139. package/src/managers/SceneManager.js +30 -0
  140. package/src/managers/ShaderManager.js +0 -2
  141. package/src/managers/TextureManager.js +218 -0
  142. package/src/particlesystem/Particle.js +82 -7
  143. package/src/particlesystem/ParticleSettings.js +21 -3
  144. package/src/particlesystem/Particles.js +80 -31
  145. package/dist/types/Drawable.d.ts +0 -157
  146. package/dist/types/Emerald.d.ts +0 -73
  147. package/dist/types/GLUtils.d.ts +0 -6
  148. package/dist/types/Scene.d.ts +0 -39
  149. package/dist/types/Shaders.d.ts +0 -4
  150. package/dist/types/Storage.d.ts +0 -46
  151. package/dist/types/Time.d.ts +0 -22
  152. package/dist/types/Transform.d.ts +0 -41
  153. package/dist/types/components/BoxColliderDebug.d.ts +0 -19
  154. package/dist/types/components/CircleColliderDebug.d.ts +0 -19
  155. package/dist/types/components/Collider.d.ts +0 -53
  156. package/dist/types/components/GameObject.d.ts +0 -72
  157. package/dist/types/components/RigidBody.d.ts +0 -104
  158. package/dist/types/managers/AudioManager.d.ts +0 -60
  159. package/dist/types/managers/GLManager.d.ts +0 -47
  160. package/dist/types/managers/IDManager.d.ts +0 -21
  161. package/dist/types/managers/SceneManager.d.ts +0 -22
  162. package/dist/types/particlesystem/Particle.d.ts +0 -42
@@ -22,7 +22,8 @@ class CircleCollider extends Collider {
22
22
  friction,
23
23
  restitution,
24
24
  isSensor = false,
25
- parentObject = null
25
+ parentObject = null,
26
+ filter = null
26
27
  ) {
27
28
  super(rigidbody, isSensor, parentObject);
28
29
  this.collider = rigidbody
@@ -36,7 +37,21 @@ class CircleCollider extends Collider {
36
37
  this.radius = radius;
37
38
  this.rigidbody.setCollider(this);
38
39
  this.name = "CircleCollider" + this.id;
39
- this.debugShape = new CircleColliderDebug(this.rigidbody);
40
+ this._applyFilterSpec(filter);
41
+ }
42
+
43
+ /**
44
+ * @method debugShape
45
+ * @description Lazily builds the debug visualization on first access, so a
46
+ * collider stays free of any GameObject/GL allocation until it is debugged.
47
+ * @returns {CircleColliderDebug} - The debug shape
48
+ */
49
+ get debugShape() {
50
+ if (!this._debugShape) {
51
+ /** @private */
52
+ this._debugShape = new CircleColliderDebug(this.rigidbody);
53
+ }
54
+ return this._debugShape;
40
55
  }
41
56
 
42
57
  /**
@@ -55,9 +70,10 @@ class CircleCollider extends Collider {
55
70
  * @description Hides the debug shape of the collider
56
71
  */
57
72
  hideDebugShape() {
73
+ if (!this._debugShape) return;
58
74
  var scene = SceneManager.getScene();
59
75
  if (scene) {
60
- scene.remove(this.debugShape.gameObject);
76
+ scene.remove(this._debugShape.gameObject);
61
77
  }
62
78
  }
63
79
 
@@ -1,4 +1,4 @@
1
- import Color from "../Color.js";
1
+ import Color from "../Color.js";
2
2
  import { Vector2, Vector3 } from "../Physics.js";
3
3
  import { Circle2D } from "../Shapes.js";
4
4
  import GameObject from "./GameObject.js";
@@ -9,28 +9,28 @@ import GameObject from "./GameObject.js";
9
9
  * @param {Color} color - The color of the collider
10
10
  */
11
11
  class CircleColliderDebug {
12
- constructor(rigidbody, color = null) {
13
- this.rigidbody = rigidbody;
14
- this.physics = this.rigidbody.physics;
15
- this.scale = this.physics.getScale();
16
- this.color = color || new Color(255, 0, 0, 255);
17
- this.gameObject = new GameObject(
18
- "CircleColliderDebug",
19
- new Vector3(
20
- this.rigidbody.getBody().getPosition().x * this.scale,
21
- this.rigidbody.getBody().getPosition().y * this.scale,
22
- 100
23
- ),
24
- this.rigidbody.getAngle(),
25
- new Vector2(
26
- this.rigidbody.getCollider().getRadius() * this.scale,
27
- this.rigidbody.getCollider().getRadius() * this.scale
28
- )
29
- );
30
- this.gameObject.addComponent(new Circle2D());
31
- this.gameObject.getComponent(Circle2D).setColor(this.color);
32
- this.gameObject.setOpacity(0.3);
33
- }
12
+ constructor(rigidbody, color = null) {
13
+ this.rigidbody = rigidbody;
14
+ this.physics = this.rigidbody.physics;
15
+ this.scale = this.physics.getScale();
16
+ this.color = color || new Color(255, 0, 0, 255);
17
+ this.gameObject = new GameObject(
18
+ "CircleColliderDebug",
19
+ new Vector3(
20
+ this.rigidbody.getBody().getPosition().x * this.scale,
21
+ this.rigidbody.getBody().getPosition().y * this.scale,
22
+ 100
23
+ ),
24
+ this.rigidbody.getAngle(),
25
+ new Vector2(
26
+ this.rigidbody.getCollider().getRadius() * this.scale,
27
+ this.rigidbody.getCollider().getRadius() * this.scale
28
+ )
29
+ );
30
+ this.gameObject.addComponent(new Circle2D());
31
+ this.gameObject.getComponent(Circle2D).setColor(this.color);
32
+ this.gameObject.setOpacity(0.3);
33
+ }
34
34
  }
35
35
 
36
- export default CircleColliderDebug;
36
+ export default CircleColliderDebug;
@@ -1,4 +1,5 @@
1
1
  import IDManager from "../managers/IDManager.js";
2
+ import CollisionLayers from "../CollisionLayers.js";
2
3
 
3
4
  /**
4
5
  * @class Collider
@@ -7,45 +8,150 @@ import IDManager from "../managers/IDManager.js";
7
8
  * @param {GameObject} parentObject - The parent object of the collider
8
9
  */
9
10
  class Collider {
10
- constructor(rigidbody, isSensor = false, parentObject = null) {
11
- this.rigidbody = rigidbody;
12
- this.parentObject = parentObject;
13
- this.isSensor = isSensor;
14
- this.id = IDManager.generateUniqueID();
15
- this.name = "Collider" + this.id;
16
- }
11
+ constructor(rigidbody, isSensor = false, parentObject = null) {
12
+ this.rigidbody = rigidbody;
13
+ this.parentObject = parentObject;
14
+ this.isSensor = isSensor;
15
+ this.id = IDManager.generateUniqueID();
16
+ this.name = "Collider" + this.id;
17
17
 
18
- /**
19
- * @method getRigidBody
20
- * @description Returns the rigidbody of the collider
21
- */
22
- getRigidBody() {
23
- return this.rigidbody;
24
- }
18
+ /** @private */
19
+ this._debugShape = null;
20
+ }
25
21
 
26
- /**
27
- * @method getCollider
28
- * @description Returns the collider
29
- */
30
- getCollider() {
31
- return this.collider;
32
- }
22
+ /**
23
+ * @method syncDebugShape
24
+ * @description Mirrors a transform onto the debug shape, but only if one has
25
+ * already been created. Never forces lazy creation.
26
+ * @param {Transform} transform - The source transform
27
+ */
28
+ syncDebugShape(transform) {
29
+ if (!this._debugShape) return;
30
+ const t = this._debugShape.gameObject.transform;
31
+ t.position.x = transform.position.x;
32
+ t.position.y = transform.position.y;
33
+ t.rotation = transform.rotation;
34
+ }
33
35
 
34
- /**
35
- * @method setParent
36
- * @description Sets the parent object of the collider
37
- */
38
- setParent(parent) {
39
- this.parentObject = parent;
36
+ /**
37
+ * @method setFilter
38
+ * @description Sets the raw planck collision filter on this collider's
39
+ * fixture. Two fixtures collide only when each one's category bit is present
40
+ * in the other's mask. Subclasses must have created `this.collider`
41
+ * (the fixture) first.
42
+ * @param {Object} filter - { category, mask, group }
43
+ * @param {number} [filter.category=0x0001] - This fixture's category bits
44
+ * @param {number} [filter.mask=0xFFFF] - Bits this fixture collides with
45
+ * @param {number} [filter.group=0] - Group index (>0 always collide, <0 never)
46
+ * @returns {Collider} - this
47
+ */
48
+ setFilter({ category = 0x0001, mask = 0xffff, group = 0 } = {}) {
49
+ if (this.collider && typeof this.collider.setFilterData === "function") {
50
+ this.collider.setFilterData({
51
+ categoryBits: category,
52
+ maskBits: mask,
53
+ groupIndex: group,
54
+ });
55
+ /** @private */
56
+ this._filter = { category, mask, group };
40
57
  }
58
+ return this;
59
+ }
60
+
61
+ /**
62
+ * @method setCategory
63
+ * @description Sets which named layer this collider belongs to (its category
64
+ * bits), preserving the current mask/group. See {@link CollisionLayers}.
65
+ * @param {string|number} layer - Layer name or raw category bits
66
+ * @returns {Collider} - this
67
+ */
68
+ setCategory(layer) {
69
+ const category =
70
+ typeof layer === "number" ? layer : CollisionLayers.bit(layer);
71
+ const current = this._filter || {};
72
+ return this.setFilter({
73
+ category,
74
+ mask: current.mask ?? 0xffff,
75
+ group: current.group ?? 0,
76
+ });
77
+ }
78
+
79
+ /**
80
+ * @method setCollidesWith
81
+ * @description Sets which layers this collider can collide with (its mask),
82
+ * preserving the current category/group.
83
+ * @param {string|string[]|number} layers - Layer name(s) or raw mask bits
84
+ * @returns {Collider} - this
85
+ */
86
+ setCollidesWith(layers) {
87
+ const current = this._filter || {};
88
+ return this.setFilter({
89
+ category: current.category ?? 0x0001,
90
+ mask: CollisionLayers.mask(layers),
91
+ group: current.group ?? 0,
92
+ });
93
+ }
41
94
 
42
- /**
43
- * @method getParent
44
- * @description Returns the parent object of the collider
45
- */
46
- getParent() {
47
- return this.parentObject;
95
+ /**
96
+ * @method getFilter
97
+ * @description Returns the last applied filter, or null if none was set.
98
+ * @returns {{category:number, mask:number, group:number}|null}
99
+ */
100
+ getFilter() {
101
+ return this._filter || null;
102
+ }
103
+
104
+ /**
105
+ * @method _applyFilterSpec
106
+ * @description Applies a friendly filter spec from a constructor, accepting
107
+ * layer names or raw bits. Spec: { category, collidesWith, group }.
108
+ * @private
109
+ */
110
+ _applyFilterSpec(spec) {
111
+ if (!spec) return;
112
+ if (spec.category != null) this.setCategory(spec.category);
113
+ if (spec.collidesWith != null) this.setCollidesWith(spec.collidesWith);
114
+ if (spec.group != null) {
115
+ const f = this._filter || {};
116
+ this.setFilter({
117
+ category: f.category ?? 0x0001,
118
+ mask: f.mask ?? 0xffff,
119
+ group: spec.group,
120
+ });
48
121
  }
122
+ }
123
+
124
+ /**
125
+ * @method getRigidBody
126
+ * @description Returns the rigidbody of the collider
127
+ */
128
+ getRigidBody() {
129
+ return this.rigidbody;
130
+ }
131
+
132
+ /**
133
+ * @method getCollider
134
+ * @description Returns the collider
135
+ */
136
+ getCollider() {
137
+ return this.collider;
138
+ }
139
+
140
+ /**
141
+ * @method setParent
142
+ * @description Sets the parent object of the collider
143
+ */
144
+ setParent(parent) {
145
+ this.parentObject = parent;
146
+ }
147
+
148
+ /**
149
+ * @method getParent
150
+ * @description Returns the parent object of the collider
151
+ */
152
+ getParent() {
153
+ return this.parentObject;
154
+ }
49
155
  }
50
156
 
51
- export default Collider;
157
+ export default Collider;
@@ -4,6 +4,7 @@ import Transform from "../Transform.js";
4
4
  import RigidBody from "./RigidBody.js";
5
5
  import Collider from "./Collider.js";
6
6
  import Drawable from "../Drawable.js";
7
+ import Behaviour from "./Behaviour.js";
7
8
 
8
9
  /**
9
10
  * @class GameObject
@@ -24,7 +25,35 @@ class GameObject {
24
25
  this.id = IDManager.generateUniqueID();
25
26
  this.isActive = false;
26
27
  this.transform = new Transform(position, rotation, scale);
27
- this.opacity = 1.0; // 0..1 opacity multiplier for this GameObject
28
+ this.opacity = 1.0;
29
+
30
+ this.layer = 0;
31
+ this.screenSpace = false;
32
+ }
33
+
34
+ /**
35
+ * @method setLayer
36
+ * @description Sets the render layer. Objects are drawn by layer first, then
37
+ * by z within a layer.
38
+ * @param {number} layer - The layer index
39
+ * @returns {GameObject} - this
40
+ */
41
+ setLayer(layer) {
42
+ this.layer = layer;
43
+ return this;
44
+ }
45
+
46
+ /**
47
+ * @method setScreenSpace
48
+ * @description When true, the object ignores the camera (fixed on screen) —
49
+ * useful for HUD/UI. Position is then in pixels from the viewport center.
50
+ * Note: not supported for InstancedTexture-based objects.
51
+ * @param {boolean} value
52
+ * @returns {GameObject} - this
53
+ */
54
+ setScreenSpace(value) {
55
+ this.screenSpace = value;
56
+ return this;
28
57
  }
29
58
 
30
59
  /**
@@ -55,11 +84,57 @@ class GameObject {
55
84
  if (component instanceof RigidBody) {
56
85
  component.destroy();
57
86
  }
87
+ if (component instanceof Behaviour) {
88
+ component.onDestroy();
89
+ }
58
90
  this.components = this.components.filter(
59
91
  (comp) => comp.id !== component.id
60
92
  );
61
93
  }
62
94
 
95
+ /**
96
+ * @method update
97
+ * @description Ticks the lifecycle of Behaviour components on this object.
98
+ * Only Behaviours are ticked here so component types with their own update
99
+ * signature (e.g. InstancedTexture) are left untouched.
100
+ * @param {number} deltaTime - Seconds since the previous frame (time-scaled)
101
+ */
102
+ update(deltaTime) {
103
+ for (const comp of this.components) {
104
+ if (comp instanceof Behaviour && comp.enabled) {
105
+ if (!comp._started) {
106
+ comp._started = true;
107
+ comp.start();
108
+ }
109
+ comp.update(deltaTime);
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * @method setParent
116
+ * @description Parents this object to another GameObject (or detaches with
117
+ * null). The object's transform then composes on top of the parent's, so
118
+ * moving/rotating/scaling the parent moves its children.
119
+ * @param {GameObject|null} parent - The parent GameObject, or null
120
+ * @returns {GameObject} - this
121
+ */
122
+ setParent(parent) {
123
+ this.transform.setParent(parent ? parent.transform : null);
124
+ return this;
125
+ }
126
+
127
+ /**
128
+ * @method addChild
129
+ * @description Parents another GameObject to this one.
130
+ * @param {GameObject} child - The child GameObject
131
+ * @returns {GameObject} - this
132
+ */
133
+ addChild(child) {
134
+ child.setParent(this);
135
+ return this;
136
+ }
137
+
63
138
  /**
64
139
  * @method setIsActive
65
140
  * @description Sets the active state of the game object
@@ -118,6 +193,56 @@ class GameObject {
118
193
  return null;
119
194
  }
120
195
 
196
+ /**
197
+ * @method syncPhysics
198
+ * @description Copies simulation state (position + rotation) from this
199
+ * object's dynamic rigidbody onto its transform, and mirrors it onto any
200
+ * visible collider debug shape. Called once per frame from the render loop so
201
+ * draw() stays read-only and physics isn't re-applied per camera. Also
202
+ * forwards to components that own their own physics-driven content (e.g.
203
+ * InstancedTexture).
204
+ */
205
+ syncPhysics() {
206
+ const rigidBody = this.getComponent(RigidBody);
207
+ if (rigidBody && rigidBody.getType() === "dynamic") {
208
+ rigidBody.syncTransform(this.transform);
209
+ const collider = rigidBody.getCollider() || this.getComponent(Collider);
210
+ if (collider && typeof collider.syncDebugShape === "function") {
211
+ collider.syncDebugShape(this.transform);
212
+ }
213
+ }
214
+ for (const comp of this.components) {
215
+ if (comp !== rigidBody && typeof comp.syncPhysics === "function") {
216
+ comp.syncPhysics();
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * @method destroy
223
+ * @description Permanently tears the object down: disposes every Drawable's
224
+ * GPU resources, destroys physics bodies, and runs Behaviour.onDestroy().
225
+ * Use it (or Scene.remove(obj, { dispose: true })) when an object will not
226
+ * be re-added — plain Scene.remove() keeps GPU resources alive for re-use.
227
+ * Safe to call twice.
228
+ */
229
+ destroy() {
230
+ if (this._destroyed) return;
231
+ /** @private */
232
+ this._destroyed = true;
233
+ this.isActive = false;
234
+ for (const comp of this.components) {
235
+ if (comp instanceof Drawable && typeof comp.dispose === "function") {
236
+ comp.dispose();
237
+ } else if (comp instanceof RigidBody) {
238
+ comp.destroy();
239
+ } else if (comp instanceof Behaviour) {
240
+ comp.onDestroy();
241
+ }
242
+ }
243
+ this.components = [];
244
+ }
245
+
121
246
  /**
122
247
  * @method draw
123
248
  * @description Draws the game object
@@ -127,31 +252,15 @@ class GameObject {
127
252
  */
128
253
  draw(globalViewMatrix, uniformLocation, currentTime) {
129
254
  const drawable = this.getComponent(Drawable);
130
- const rigidBody = this.getComponent(RigidBody);
131
- const collider = this.getComponent(Collider);
132
- if (rigidBody && rigidBody.getType() === "dynamic") {
133
- const updatedPos = rigidBody.getBody().getPosition();
134
- const rigidBodyOffset = rigidBody.getOffset();
135
- const physicsScale = rigidBody.getPhysics().getScale();
136
-
137
- this.transform.position.x =
138
- updatedPos.x * physicsScale - rigidBodyOffset.x;
139
- this.transform.position.y =
140
- updatedPos.y * physicsScale - rigidBodyOffset.y;
141
-
142
- if (collider) {
143
- collider.debugShape.gameObject.transform.position.x =
144
- this.transform.position.x;
145
- collider.debugShape.gameObject.transform.position.y =
146
- this.transform.position.y;
147
- }
148
- }
149
255
  if (drawable) {
256
+ const worldTransform = this.transform.parent
257
+ ? this.transform.getWorldTransform()
258
+ : this.transform;
150
259
  drawable.draw(
151
260
  globalViewMatrix,
152
261
  uniformLocation,
153
262
  currentTime,
154
- this.transform
263
+ worldTransform
155
264
  );
156
265
  }
157
266
  }
@@ -37,6 +37,8 @@ class RigidBody {
37
37
  this.collider = null;
38
38
  this.id = IDManager.generateUniqueID();
39
39
  this.name = "RigidBody" + this.id;
40
+
41
+ this.body.setUserData(this);
40
42
  }
41
43
 
42
44
  /**
@@ -53,6 +55,42 @@ class RigidBody {
53
55
  );
54
56
  }
55
57
 
58
+ /**
59
+ * @method getWorldX
60
+ * @description The single source of truth for body(physics) -> world(pixel)
61
+ * conversion on X: undo the scale and the spawn offset.
62
+ * @returns {number} - The live world-space x of the body
63
+ * @private
64
+ */
65
+ getWorldX() {
66
+ return this.body.getPosition().x * this.physics.scale - this.offset.x;
67
+ }
68
+
69
+ /**
70
+ * @method getWorldY
71
+ * @description World-space y counterpart of getWorldX.
72
+ * @returns {number} - The live world-space y of the body
73
+ * @private
74
+ */
75
+ getWorldY() {
76
+ return this.body.getPosition().y * this.physics.scale - this.offset.y;
77
+ }
78
+
79
+ /**
80
+ * @method syncTransform
81
+ * @description Writes the body's live world position and angle into a
82
+ * Transform. This is the one place body state is copied onto a renderable, so
83
+ * position and rotation stay in lock-step. Static/kinematic bodies are not
84
+ * driven by the simulation, so only dynamic bodies write back.
85
+ * @param {Transform} transform - The transform to update in place
86
+ */
87
+ syncTransform(transform) {
88
+ if (this.type !== "dynamic") return;
89
+ transform.position.x = this.getWorldX();
90
+ transform.position.y = this.getWorldY();
91
+ transform.rotation = this.body.getAngle();
92
+ }
93
+
56
94
  /**
57
95
  * @method getOffset
58
96
  * @description Returns the offset of the rigidbody
@@ -82,12 +120,95 @@ class RigidBody {
82
120
  this.body.setAngle(rotation);
83
121
  }
84
122
 
123
+ /**
124
+ * @method setLinearVelocity
125
+ * @description Sets the body's linear velocity in world (pixel) units per
126
+ * second. Converts to physics units internally.
127
+ * @param {number} vx - Horizontal velocity (world units/sec)
128
+ * @param {number} vy - Vertical velocity (world units/sec)
129
+ */
130
+ setLinearVelocity(vx, vy) {
131
+ this.body.setLinearVelocity(
132
+ new planck.Vec2(vx / this.physics.scale, vy / this.physics.scale)
133
+ );
134
+ }
135
+
136
+ /**
137
+ * @method getLinearVelocity
138
+ * @description Returns the body's linear velocity in world (pixel) units/sec.
139
+ * @returns {{x:number, y:number}}
140
+ */
141
+ getLinearVelocity() {
142
+ const v = this.body.getLinearVelocity();
143
+ return { x: v.x * this.physics.scale, y: v.y * this.physics.scale };
144
+ }
145
+
146
+ /**
147
+ * @method applyImpulse
148
+ * @description Applies a linear impulse (world units) at the body's center.
149
+ * @param {number} ix
150
+ * @param {number} iy
151
+ */
152
+ applyImpulse(ix, iy) {
153
+ this.body.applyLinearImpulse(
154
+ new planck.Vec2(ix / this.physics.scale, iy / this.physics.scale),
155
+ this.body.getWorldCenter(),
156
+ true
157
+ );
158
+ }
159
+
160
+ /**
161
+ * @method setAwake
162
+ * @description Wakes or sleeps the body.
163
+ * @param {boolean} awake
164
+ */
165
+ setAwake(awake) {
166
+ this.body.setAwake(awake);
167
+ }
168
+
169
+ /**
170
+ * @method setContinuous
171
+ * @description Enables continuous collision detection (CCD) for this body by
172
+ * marking it a "bullet". Fast-moving bodies (e.g. a dash, a projectile, a
173
+ * player falling at high speed) otherwise sweep so far in a single fixed step
174
+ * that they tunnel straight through thin static geometry; with CCD on, planck
175
+ * solves the swept path against static bodies so the body stops at the wall
176
+ * instead of teleporting past it. Costs more per step, so reserve it for the
177
+ * handful of bodies that actually move fast.
178
+ * @param {boolean} [enabled=true]
179
+ * @returns {RigidBody} - this
180
+ */
181
+ setContinuous(enabled = true) {
182
+ this.body.setBullet(!!enabled);
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * @method isContinuous
188
+ * @description Whether CCD (bullet mode) is enabled for this body.
189
+ * @returns {boolean}
190
+ */
191
+ isContinuous() {
192
+ return this.body.isBullet();
193
+ }
194
+
85
195
  /**
86
196
  * @method getPosition
87
- * @description Returns the position of the rigidbody
88
- * @returns {Vector2} - The position of the rigidbody
197
+ * @description Returns the live world-space position of the body (kept in sync
198
+ * with the simulation), not the spawn position. Use getInitialPosition() for
199
+ * the position the body was created at.
200
+ * @returns {Vector2} - The current world-space position of the rigidbody
89
201
  */
90
202
  getPosition() {
203
+ return new Vector2(this.getWorldX(), this.getWorldY());
204
+ }
205
+
206
+ /**
207
+ * @method getInitialPosition
208
+ * @description Returns the world-space position the body was created at.
209
+ * @returns {Vector2} - The spawn position of the rigidbody
210
+ */
211
+ getInitialPosition() {
91
212
  return this.position;
92
213
  }
93
214