melonjs 20.11.0 → 20.13.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 +3 -1
  2. package/build/application/application.d.ts.map +1 -1
  3. package/build/application/settings.d.ts +6 -2
  4. package/build/application/settings.d.ts.map +1 -1
  5. package/build/audio/backend/sound.d.ts.map +1 -1
  6. package/build/audio/playback.d.ts.map +1 -1
  7. package/build/audio/spatial.d.ts +13 -2
  8. package/build/audio/spatial.d.ts.map +1 -1
  9. package/build/audio/types.d.ts +25 -3
  10. package/build/audio/types.d.ts.map +1 -1
  11. package/build/camera/camera2d.d.ts +2 -0
  12. package/build/camera/camera2d.d.ts.map +1 -1
  13. package/build/camera/camera3d.d.ts.map +1 -1
  14. package/build/camera/effects/fade_effect.d.ts.map +1 -1
  15. package/build/camera/effects/pixelate_transition_effect.d.ts +67 -0
  16. package/build/camera/effects/pixelate_transition_effect.d.ts.map +1 -0
  17. package/build/geometries/ellipse.d.ts +11 -4
  18. package/build/geometries/ellipse.d.ts.map +1 -1
  19. package/build/geometries/line.d.ts +2 -2
  20. package/build/geometries/line.d.ts.map +1 -1
  21. package/build/geometries/polygon.d.ts +5 -4
  22. package/build/geometries/polygon.d.ts.map +1 -1
  23. package/build/geometries/rectangle.d.ts +2 -2
  24. package/build/geometries/rectangle.d.ts.map +1 -1
  25. package/build/geometries/roundrect.d.ts +2 -2
  26. package/build/geometries/roundrect.d.ts.map +1 -1
  27. package/build/index.d.ts +14 -2
  28. package/build/index.d.ts.map +1 -1
  29. package/build/index.js +12716 -7233
  30. package/build/index.js.map +4 -4
  31. package/build/input/gamepad.d.ts.map +1 -1
  32. package/build/input/pointerevent.d.ts.map +1 -1
  33. package/build/lang/console.d.ts +16 -1
  34. package/build/lang/console.d.ts.map +1 -1
  35. package/build/level/tiled/TMXLayer.d.ts +48 -14
  36. package/build/level/tiled/TMXLayer.d.ts.map +1 -1
  37. package/build/level/tiled/TMXTileMap.d.ts +1 -1
  38. package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
  39. package/build/level/tiled/TMXTileset.d.ts.map +1 -1
  40. package/build/level/tiled/TMXUtils.d.ts.map +1 -1
  41. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
  42. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
  43. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
  44. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
  45. package/build/level/tiled/renderer/TMXRenderer.d.ts +1 -1
  46. package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
  47. package/build/level/tiled/renderer/TMXStaggeredRenderer.d.ts.map +1 -1
  48. package/build/loader/loader.d.ts +55 -11
  49. package/build/loader/loader.d.ts.map +1 -1
  50. package/build/loader/loadingscreen.d.ts.map +1 -1
  51. package/build/math/color.d.ts.map +1 -1
  52. package/build/math/math.d.ts +6 -1
  53. package/build/math/math.d.ts.map +1 -1
  54. package/build/math/matrix2d.d.ts +3 -2
  55. package/build/math/matrix2d.d.ts.map +1 -1
  56. package/build/math/matrix3d.d.ts +4 -1
  57. package/build/math/matrix3d.d.ts.map +1 -1
  58. package/build/math/observableVector2d.d.ts +6 -6
  59. package/build/math/observableVector2d.d.ts.map +1 -1
  60. package/build/math/observableVector3d.d.ts +8 -8
  61. package/build/math/observableVector3d.d.ts.map +1 -1
  62. package/build/particles/emitter.d.ts +15 -3
  63. package/build/particles/emitter.d.ts.map +1 -1
  64. package/build/particles/particle.d.ts +6 -0
  65. package/build/particles/particle.d.ts.map +1 -1
  66. package/build/particles/settings.d.ts +22 -5
  67. package/build/particles/settings.d.ts.map +1 -1
  68. package/build/physics/bounds.d.ts +5 -5
  69. package/build/physics/bounds.d.ts.map +1 -1
  70. package/build/physics/builtin/body.d.ts +15 -4
  71. package/build/physics/builtin/body.d.ts.map +1 -1
  72. package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
  73. package/build/physics/builtin/detector.d.ts.map +1 -1
  74. package/build/physics/builtin/raycast.d.ts.map +1 -1
  75. package/build/physics/world.d.ts.map +1 -1
  76. package/build/renderable/animation.d.ts +6 -1
  77. package/build/renderable/animation.d.ts.map +1 -1
  78. package/build/renderable/container.d.ts +40 -6
  79. package/build/renderable/container.d.ts.map +1 -1
  80. package/build/renderable/entity/entity.d.ts.map +1 -1
  81. package/build/renderable/frameAnimation.d.ts.map +1 -1
  82. package/build/renderable/imagelayer.d.ts.map +1 -1
  83. package/build/renderable/instanced_mesh.d.ts.map +1 -1
  84. package/build/renderable/mesh.d.ts.map +1 -1
  85. package/build/renderable/renderable.d.ts.map +1 -1
  86. package/build/renderable/sprite.d.ts +8 -2
  87. package/build/renderable/sprite.d.ts.map +1 -1
  88. package/build/renderable/text/bitmaptext.d.ts +12 -3
  89. package/build/renderable/text/bitmaptext.d.ts.map +1 -1
  90. package/build/renderable/text/text.d.ts +147 -4
  91. package/build/renderable/text/text.d.ts.map +1 -1
  92. package/build/renderable/text/textmetrics.d.ts +10 -2
  93. package/build/renderable/text/textmetrics.d.ts.map +1 -1
  94. package/build/renderable/text/textstyle.d.ts.map +1 -1
  95. package/build/renderable/trail3d.d.ts.map +1 -1
  96. package/build/renderable/ui/panelpaint.d.ts +5 -1
  97. package/build/renderable/ui/panelpaint.d.ts.map +1 -1
  98. package/build/renderable/ui/progressbar.d.ts +267 -95
  99. package/build/renderable/ui/progressbar.d.ts.map +1 -1
  100. package/build/renderable/ui/uibaseelement.d.ts +436 -50
  101. package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
  102. package/build/renderable/ui/uicheckbox.d.ts +383 -0
  103. package/build/renderable/ui/uicheckbox.d.ts.map +1 -0
  104. package/build/renderable/ui/uifocus.d.ts +6 -0
  105. package/build/renderable/ui/uifocus.d.ts.map +1 -0
  106. package/build/renderable/ui/uilabel.d.ts +31 -0
  107. package/build/renderable/ui/uilabel.d.ts.map +1 -0
  108. package/build/renderable/ui/uipanel.d.ts +589 -116
  109. package/build/renderable/ui/uipanel.d.ts.map +1 -1
  110. package/build/renderable/ui/uiskin.d.ts +25 -0
  111. package/build/renderable/ui/uiskin.d.ts.map +1 -0
  112. package/build/renderable/ui/uislider.d.ts +518 -0
  113. package/build/renderable/ui/uislider.d.ts.map +1 -0
  114. package/build/renderable/ui/uispriteelement.d.ts +161 -85
  115. package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
  116. package/build/renderable/ui/uitextbutton.d.ts +369 -63
  117. package/build/renderable/ui/uitextbutton.d.ts.map +1 -1
  118. package/build/state/stage.d.ts +3 -2
  119. package/build/state/stage.d.ts.map +1 -1
  120. package/build/state/state.d.ts +31 -6
  121. package/build/state/state.d.ts.map +1 -1
  122. package/build/system/legacy_pool.d.ts.map +1 -1
  123. package/build/system/pool.d.ts.map +1 -1
  124. package/build/system/save.d.ts +2 -1
  125. package/build/system/save.d.ts.map +1 -1
  126. package/build/tweens/tween.d.ts +5 -2
  127. package/build/tweens/tween.d.ts.map +1 -1
  128. package/build/utils/fetchdata.d.ts.map +1 -1
  129. package/build/video/canvas/canvas_renderer.d.ts +1 -5
  130. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  131. package/build/video/effects/bloom.d.ts +3 -2
  132. package/build/video/effects/bloom.d.ts.map +1 -1
  133. package/build/video/effects/blur.d.ts +1 -1
  134. package/build/video/effects/chromaticAberration.d.ts +1 -1
  135. package/build/video/effects/dissolve.d.ts.map +1 -1
  136. package/build/video/effects/dropShadow.d.ts +1 -1
  137. package/build/video/effects/glow.d.ts +1 -1
  138. package/build/video/effects/glsl_realization.d.ts.map +1 -1
  139. package/build/video/effects/hologram.d.ts.map +1 -1
  140. package/build/video/effects/local_space.d.ts +19 -0
  141. package/build/video/effects/local_space.d.ts.map +1 -0
  142. package/build/video/effects/mesh_splice.d.ts.map +1 -1
  143. package/build/video/effects/outline.d.ts +1 -1
  144. package/build/video/effects/pixelate.d.ts +1 -1
  145. package/build/video/effects/radialGradient.d.ts +4 -12
  146. package/build/video/effects/radialGradient.d.ts.map +1 -1
  147. package/build/video/effects/scanline.d.ts.map +1 -1
  148. package/build/video/effects/shadereffect.d.ts.map +1 -1
  149. package/build/video/effects/shine.d.ts +5 -0
  150. package/build/video/effects/shine.d.ts.map +1 -1
  151. package/build/video/effects/sway.d.ts +9 -4
  152. package/build/video/effects/sway.d.ts.map +1 -1
  153. package/build/video/effects/vignette.d.ts.map +1 -1
  154. package/build/video/effects/wave.d.ts +9 -7
  155. package/build/video/effects/wave.d.ts.map +1 -1
  156. package/build/video/effects/wgsl/parse.d.ts.map +1 -1
  157. package/build/video/effects/wgsl/scaffold.d.ts.map +1 -1
  158. package/build/video/effects/wgsl_realization.d.ts.map +1 -1
  159. package/build/video/renderer.d.ts +22 -5
  160. package/build/video/renderer.d.ts.map +1 -1
  161. package/build/video/renderstate.d.ts +26 -3
  162. package/build/video/renderstate.d.ts.map +1 -1
  163. package/build/video/rendertarget/webgpurendertarget.d.ts +1 -0
  164. package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
  165. package/build/video/texture/atlas.d.ts +31 -31
  166. package/build/video/texture/atlas.d.ts.map +1 -1
  167. package/build/video/utils/pixel_snap.d.ts +44 -0
  168. package/build/video/utils/pixel_snap.d.ts.map +1 -0
  169. package/build/video/utils/tessellation.d.ts +11 -0
  170. package/build/video/utils/tessellation.d.ts.map +1 -1
  171. package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
  172. package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
  173. package/build/video/webgl/batchers/quad_batcher.d.ts +2 -1
  174. package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
  175. package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts.map +1 -1
  176. package/build/video/webgl/webgl_renderer.d.ts +3 -6
  177. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  178. package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -1
  179. package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
  180. package/build/video/webgpu/batchers/primitive_batcher.d.ts.map +1 -1
  181. package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
  182. package/build/video/webgpu/effect_binding.d.ts.map +1 -1
  183. package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts.map +1 -1
  184. package/build/video/webgpu/webgpu_renderer.d.ts +6 -10
  185. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  186. package/package.json +1 -1
  187. package/skills/AGENTS.md +2 -1
  188. package/skills/melonjs/SKILL.md +3 -2
  189. package/skills/melonjs-audio/SKILL.md +31 -8
  190. package/skills/melonjs-camera-and-drawing/SKILL.md +41 -1
  191. package/skills/melonjs-display-and-scaling/SKILL.md +19 -5
  192. package/skills/melonjs-effects-and-shaders/SKILL.md +53 -17
  193. package/skills/melonjs-events/SKILL.md +3 -1
  194. package/skills/melonjs-getting-started/SKILL.md +1 -2
  195. package/skills/melonjs-input/SKILL.md +61 -3
  196. package/skills/melonjs-lighting/SKILL.md +4 -3
  197. package/skills/melonjs-loading-assets/SKILL.md +57 -15
  198. package/skills/melonjs-math/SKILL.md +14 -3
  199. package/skills/melonjs-particles-and-trails/SKILL.md +72 -8
  200. package/skills/melonjs-performance/SKILL.md +3 -0
  201. package/skills/melonjs-physics/SKILL.md +53 -4
  202. package/skills/melonjs-plugins/SKILL.md +25 -1
  203. package/skills/melonjs-renderables/SKILL.md +11 -3
  204. package/skills/melonjs-renderer-backends/SKILL.md +2 -2
  205. package/skills/melonjs-scenes-and-state/SKILL.md +32 -2
  206. package/skills/melonjs-sprites-and-animation/SKILL.md +14 -0
  207. package/skills/melonjs-tilemaps/SKILL.md +96 -2
  208. package/skills/melonjs-ui-and-text/SKILL.md +560 -23
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-particles-and-trails
3
- description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, 3D particle motion through elevation and minSpread/maxSpread in 20.8, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: Trail3d, ribbon, exhaust trail, speed streak, ski track, tyre mark, ribbonNormal, ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter, tintOverLife, colour over life, color over life, flame, fire."
3
+ description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, 3D particle motion through elevation and minSpread/maxSpread in 20.8, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: Trail3d, ribbon, exhaust trail, speed streak, ski track, tyre mark, ribbonNormal, ParticleEmitter, particles, particle editor, tune particles, spawn depth, volume, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter, tintOverLife, colour over life, color over life, flame, fire."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -28,6 +28,50 @@ emitter.burstParticles(); // one shot
28
28
  `duration` setting, which defaults to `Infinity` — a stream runs until
29
29
  `stopStream()`. `burstParticles(n)` launches `n`, or `totalParticles`.
30
30
 
31
+ Without an `image`, particles are white squares of `textureSize` pixels (8 by
32
+ default), coloured by `tint`. Every such emitter draws the renderer's one
33
+ shared white pixel, stretched, so an emitter per explosion or a `reset()` on
34
+ every tweak costs no texture.
35
+
36
+ ## Tuning an effect: the Particle Editor
37
+
38
+ Settings are easier to find by eye than by reasoning. The
39
+ [Particle Editor](https://melonjs.github.io/melonJS/examples/#/particle-editor)
40
+ runs one emitter live, with every setting on a slider, from a preset (fire,
41
+ smoke, rain, explosions, 3D bursts and rings…), a built-in texture or a sprite
42
+ (sheet) of your own, under a 2D camera or an orbiting 3D one. It writes out the
43
+ matching `new ParticleEmitter(...)` call, listing only what differs from the
44
+ defaults, and the same settings as JSON, in the emitter's own units, to load at
45
+ run time:
46
+
47
+ ```js
48
+ // preloaded with { name: "smoke-fx", type: "json", src: "data/fx/smoke.json" }
49
+ const effect = loader.getJSON("smoke-fx");
50
+ const emitter = new ParticleEmitter(x, y, {
51
+ ...effect.settings,
52
+ // no `image` for the plain square: `getImage` would give null
53
+ ...(effect.image ? { image: loader.getImage(effect.image) } : {}),
54
+ });
55
+ ```
56
+
57
+ Point a user there when they are tuning numbers by trial and error.
58
+
59
+ It covers every setting that changes how one emitter looks, but not:
60
+
61
+ - **Layered effects.** It shows one emitter at a time. A campfire (flames,
62
+ sparks, smoke) or an explosion (flash, debris, smoke) is several emitters
63
+ at the same spot: tune each in the editor, then add them side by side,
64
+ the one drawn on top last (or with a higher `z`).
65
+ - **Trails.** `Trail` and `Trail3d` are separate renderables; see below.
66
+ - **Code-only settings.** `onComplete`, `autoDestroyOnComplete`,
67
+ `accurateBounds`, and a `Container` as `referenceSpace` (it offers
68
+ `"local"` and `"world"`). `textureAdditive` is left out: `blendMode`
69
+ replaces it.
70
+ - **Changes at run time.** Calling `burstParticles()` when something
71
+ happens, or assigning `emitter.blendMode` while particles are alive, is
72
+ game code. Its "moving emitter" option previews the common case: an
73
+ emitter attached to something that moves.
74
+
31
75
  ## Sprite-sheet particles
32
76
 
33
77
  Use `framewidth` and `frameheight` (the same names as on `Sprite`) to divide
@@ -107,15 +151,33 @@ fountain and wrong for an explosion: additive sprites piled on one point clip
107
151
  all three channels there however well they are tinted, so a burst reads as one
108
152
  bright flash rather than as debris.
109
153
 
110
- Give it the size of whatever broke. The area is measured from the emitter's own
111
- position, NOT centred on it, so offset by half or the whole burst sits down and
112
- to the right of its source:
154
+ Give it the size of whatever broke. The constructor centres the area on the
155
+ point it is given, so pass the centre of the source:
113
156
 
114
157
  ```js
115
158
  const area = radius * 2;
116
- const emitter = new ParticleEmitter(x - area / 2, y - area / 2, {
117
- width: area, height: area,
118
- });
159
+ const emitter = new ParticleEmitter(x, y, { width: area, height: area });
160
+ ```
161
+
162
+ After that, `pos` is the area's top-left corner, as on any renderable. To move
163
+ the emitter to a new source later, offset by half, or the burst sits down and
164
+ to the right of it:
165
+
166
+ ```js
167
+ emitter.pos.set(x - area / 2, y - area / 2); // re-aimed at (x, y)
168
+ emitter.burstParticles();
169
+ ```
170
+
171
+ Under a `Camera3d`, `depth` (20.13) gives the area its third side: each
172
+ particle is born at a random depth within it, centered on the emitter's own
173
+ depth, unlike `width` and `height`. Without it every particle starts on one
174
+ plane facing the camera, so rain or dust seen from the side is a flat sheet. A
175
+ 2D camera does not show depth, though with an order-dependent blend the
176
+ particles are still sorted by it. It is the spawn box's size, not the
177
+ emitter's `depth` property, which is where the emitter sits along Z.
178
+
179
+ ```js
180
+ new ParticleEmitter(x, y, { width: 600, height: 20, depth: 400 }); // a volume of rain
119
181
  ```
120
182
 
121
183
  ## Particles in 3D
@@ -321,6 +383,7 @@ answer when you want discrete puffs rather than a continuous ribbon.
321
383
  |---|---|
322
384
  | trail looks flat under a `Camera3d` | `Trail` is 2D; use `Trail3d` |
323
385
  | particles clutter a minimap or a second camera | every camera draws them: set the emitter's `visibilityLayer` to a layer that camera's `cullMask` leaves out (see `melonjs-camera-and-drawing`) |
386
+ | a camera that draws only the emitter's layer shows no particles | before 20.13 particles stayed on layer 1 whatever their emitter was on; update |
324
387
  | `Trail3d` draws nothing, with a console warning | it needs a `Camera3d`; the app's `cameraClass` is still the 2D default |
325
388
  | `trail.tint` does nothing | `Trail` cannot be tinted at all; `Trail3d` can |
326
389
  | assigning `maxPoints` throws | `Trail3d` sizes its geometry once; pass `length` to the constructor |
@@ -335,7 +398,8 @@ answer when you want discrete puffs rather than a continuous ribbon.
335
398
  | frame rate drops with many effects | an emitter per event instead of one re-aimed emitter |
336
399
  | a 3D burst is invisible under a `Camera3d` | before 20.8 a particle was culled at twice its depth; update |
337
400
  | the whole burst launches from one pixel | `width`/`height` are the spawn area and default to `1` |
338
- | the burst sits down-right of its source | the spawn area starts at the emitter's position; offset by half |
401
+ | the burst sits down-right of its source | the emitter was moved by setting `pos` to the source: `pos` is the area's top-left corner, so offset by half (only the constructor centres it) |
402
+ | the burst sits up-left of its source | the constructor was given the top-left corner: it centres the area on (x, y), so pass the centre |
339
403
  | the middle of a burst goes white | additive sprites overlapping — `textureAdditive: false`, or spread them |
340
404
  | debris chunks look like squares | the default sprite is a filled square; pass your own `image` |
341
405
  | a ring tilts with the camera instead of staying on a surface | the launch axis is in WORLD axes, not the camera basis |
@@ -222,6 +222,9 @@ queries one at a time, usually not. See `melonjs-3d` for the decision table.
222
222
 
223
223
  - `stream: true` on long music tracks plays through a streaming element instead
224
224
  of decoding the whole file into memory.
225
+ - `preload: false` on music a player may never reach (a boss theme, the
226
+ ending) keeps it out of the initial download: it is fetched and streamed
227
+ when first played.
225
228
  - `pool` on an audio clip sizes the reuse pool for finished instances — it is
226
229
  **not** a concurrency cap.
227
230
  - `loader.unloadAll()` between large levels drops every loaded asset and
@@ -152,6 +152,11 @@ class Player extends Sprite {
152
152
  The engine forwards it to the active adapter when the renderable is added with
153
153
  `Container.addChild`, and only if that container is already attached to the root
154
154
  world; the broadphase picks the renderable up on the next `world.update()`.
155
+ Nesting is fine: adding a container registers the body of every renderable
156
+ below it, at any depth, and removing it — destroyed, pooled or kept alive —
157
+ takes them all out again, so moving a whole group with `other.addChild(group)`
158
+ keeps each body simulated, once. A persistent container's children keep their
159
+ bodies through `world.reset()` (a state change or `level.load`).
155
160
  Calling `adapter.addBody()` yourself registers the body but leaves the
156
161
  renderable out of the scene graph, so it integrates but never collides.
157
162
 
@@ -270,6 +275,13 @@ samples it down and keeps the extent. Anything the 2D backends cannot express
270
275
  is refused loudly on both, rather than leaving a body with no collision
271
276
  geometry that silently never collides.
272
277
 
278
+ A polygon may be wound either way: its edge normals are oriented outward from
279
+ its signed area. Before 20.13 they pointed inward on a counter-clockwise
280
+ polygon — a Tiled polygon drawn that way, anything mirrored with
281
+ `scale(-1, 1)` or a `Rect` of negative width — and the built-in circle test
282
+ then collided a circle with it from tens of pixels away, as soon as their
283
+ bounding boxes met. Polygon against polygon was never affected.
284
+
273
285
  Whatever a backend really simulates is what `getBodyShapes()` reports, so the
274
286
  debug panel is the fastest way to see an approximation for what it is.
275
287
 
@@ -533,6 +545,15 @@ and only active under a 3D broadphase — i.e. `world.sortOn === "depth"`, which
533
545
  `world.adapter.capabilities.raycasts3d` before calling either. `raycast` itself
534
546
  is capability-gated by `capabilities.raycasts`.
535
547
 
548
+ On the built-in adapter a 2D ray measures each shape the way the narrowphase
549
+ measures it against a planar shape: polygons, `Rect`, `RoundRect` and `Line`
550
+ by their edges, an `Ellipse` with its rotation, a `Box3d` by its XY footprint
551
+ and a `Sphere` as a circle of its radius (a 2D ray is unbounded along Z). A
552
+ `Point` has no area to cross and is skipped, as is an unknown shape type,
553
+ with a one-time console warning. Before 20.13 a body carrying a `Point`,
554
+ `Sphere` or `Box3d` made every ray that reached it throw, and a rotated
555
+ ellipse was hit where it would have been unrotated.
556
+
536
557
  The legacy `collision.rayCast(line, result)` is a different API: it takes a
537
558
  `Line`, returns an **array** of every intersecting renderable, and only exists
538
559
  on the built-in path.
@@ -629,8 +650,9 @@ group.resize(width, height);
629
650
  ```
630
651
 
631
652
  `enableChildBoundsUpdate` also gives it one, but it re-measures the whole child
632
- list on every update — reach for it only when the extent genuinely IS the
633
- members' union and moves with them (a flock, a squad), not to give a fixed
653
+ list on every update (after the children have moved, and before each culling
654
+ check while it is off screen) — reach for it only when the extent genuinely IS
655
+ the members' union and moves with them (a flock, a squad), not to give a fixed
634
656
  group a size.
635
657
 
636
658
  ## Built-in world quirks
@@ -656,13 +678,25 @@ real rigid-body engine:
656
678
  - **`applyForce(x, y)` is linear only** unless you pass the optional
657
679
  `(pointX, pointY)` application point, which generates a torque
658
680
  `τ = r × F` into `body.angularVelocity` via `body.pseudoInertia`.
659
- `applyTorque(τ)` is the direct form.
681
+ `applyTorque(τ)` is the direct form. The point is in **world** coordinates,
682
+ as on matter and planck, and the lever arm is measured from the centre of
683
+ the shapes where they collide: the renderable's drawn frame (so the anchor
684
+ counts) plus any parent container's position. `renderable.getBounds()` is
685
+ in that space, so `b.centerX, b.top` is the top centre.
660
686
  - **Force accumulators reset at end-of-step** — for every body, including
661
687
  static, paused and off-screen ones — so forces must be applied every frame to
662
688
  persist.
663
689
  - **`isGrounded` is flag-based, not contact-based** — the adapter returns
664
690
  `!body.falling && !body.jumping`, flags updated by the last resolved
665
- collision, not a live contact test.
691
+ collision, not a live contact test. The DIRECTION of the push-out decides
692
+ them: pushed up out of a floor while not rising is a landing, pushed down
693
+ by a ceiling while rising (or `jumping`) ends the jump and falls, and a
694
+ side contact, a body touched from above while standing, or a body with
695
+ `gravityScale` 0 leaves them alone. Before 20.13 a magnitude test decided
696
+ instead, so anything that sank a pixel or more per step — gravity above
697
+ about `1.02`, mass above about `1.03`, `timer.tick > 1` at a low frame
698
+ rate, a floor moving up under it — stayed `falling` while sitting flush on
699
+ the floor, and could never jump.
666
700
  - **`def.density` maps 1:1 to `body.mass`** (default `1`), and `def.friction` is
667
701
  **ignored** entirely. `body.friction` is a per-axis `Vector2d` of per-step
668
702
  velocity damping fed from `def.frictionAir`, not a surface coefficient.
@@ -694,6 +728,12 @@ shape that is close enough at every angle (a circle, or a square for
694
728
  90° steps), or rotate the shapes yourself with `body.rotate(angle)` — which
695
729
  does rotate them, unlike `setAngle()`.
696
730
 
731
+ The visual turn pivots on the centre of the body's shapes, where they are
732
+ drawn and collide, whatever the renderable's `anchorPoint`. Before 20.13 it
733
+ ignored the anchor, so a centre-anchored renderable turned about its
734
+ bottom-right corner and swung away from where it stood; only a `(0, 0)`
735
+ anchor turned in place.
736
+
697
737
  `body.rotate()` applies a **delta** and does not update `body.angle`; the two
698
738
  are unlinked, so a body driven by `rotate()` still reports an angle of `0`.
699
739
  Track it yourself if you need to read it back. Before 20.7 it also threw on a
@@ -775,12 +815,21 @@ use them. `adapter.capabilities` (`constraints`,
775
815
  | a small shape sits at the sprite's corner | a shape's offset is measured from the drawn frame's TOP-LEFT; `new Sphere(0, 0, 0, r)` is the corner, `(width / 2, height / 2)` is the centre |
776
816
  | a rotated renderable reports an unrotated bounding box | `autoTransform: false` opts `updateBounds()` out of the transform |
777
817
  | a body in a pile sinks into the floor (built-in) | fixed in 20.7 for anything with an immovable side; bodies pinned only by other DYNAMIC bodies still overlap by a pixel or two, which is what planck/matter are for |
818
+ | `applyForce` with a point spins a body pushed through its centre, or tips it the wrong way (built-in) | before 20.13 the point was measured against the body's LOCAL bounds, so only a body at the world origin got the right torque; upgrade, and drop any `pos` you subtracted to compensate |
778
819
  | forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
779
820
  | `body.position` disagrees with `renderable.pos` on matter | matter stores the centroid, melonJS the top-left — the adapter offsets between them |
821
+ | a `bodyDef` two containers down never falls nor collides (`body` stays `undefined`) | before 20.13 only the added container's own children were registered; upgrade |
822
+ | `other.addChild(group)` throws "renderable is already adapter-managed" | before 20.13 a group detached with keepalive (which moving it does) left its children's bodies registered, still simulated off-stage; upgrade |
823
+ | the children of a persistent container stop falling and colliding after a state change or `level.load` | before 20.13 `world.reset()` kept only the bodies whose own renderable was persistent; upgrade, or flag each child `isPersistent` |
780
824
  | `bodyDef.shapes: "…"` throws "is not loaded" | the name must match the `name` the JSON was preloaded under, not its filename |
781
825
  | everything falls far too fast or too slow after swapping backends | gravity is a different unit per backend; see the table above |
782
826
  | debris lands holding its spawn angle and never tumbles | `fixedRotation: true`, or matter-adapter < 1.4.0 / planck-adapter < 1.5.0 |
783
827
  | a rotated sprite collides as if upright | the built-in solver rotates visually only — `body.angle` never rotates the shapes. Use planck/matter, or `body.rotate(angle)` |
828
+ | `setAngle` swings a sprite away from where it stood (built-in) | before 20.13 the pivot ignored `anchorPoint`, so a centre-anchored renderable turned about its bottom-right corner; upgrade |
829
+ | `isGrounded()` stays `false` on the floor, so the player cannot jump (built-in) | before 20.13 a body sinking 1px or more per step (gravity or mass above ~1.02, `timer.tick > 1`, a rising floor) never landed; upgrade |
830
+ | a circle body is shoved by a polygon it is nowhere near (built-in) | before 20.13 a counter-clockwise polygon (Tiled, mirrored with `scale(-1, 1)`) had inward edge normals; upgrade |
831
+ | `raycast` throws "reading 'x'" / "reading 'length'" (built-in) | before 20.13 a body carrying a `Point`, `Sphere` or `Box3d` broke the ray walk; upgrade |
832
+ | a ray passes through a rotated ellipse, or stops short of it (built-in) | before 20.13 the raycast ignored `Ellipse#angle`; upgrade |
784
833
  | the debug hitbox does not follow a spinning body | pre-1.2.1 matter-adapter / pre-1.3.1 planck-adapter returned the authored shapes rather than the rotated ones |
785
834
  | a Tiled polyline becomes a solid box on matter | matter cannot build a zero-area polygon; the adapter falls back to its AABB |
786
835
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-plugins
3
- description: "Use this skill when adding a melonJS plugin or adapter — the debug panel, Spine skeletal animation, compressed Tiled map support, Capacitor packaging — or when writing your own plugin. Covers plugin.register and its version gate, plugin.cache, plugin.patch, and why physics adapters are not plugins. Triggers on: plugin, plugin.register, plugin.cache, plugin.get, plugin.patch, BasePlugin, debug-plugin, DebugPanelPlugin, SpinePlugin, TiledInflatePlugin, CapacitorPlugin, adapter, custom plugin, plugin version mismatch, already registered."
3
+ description: "Use this skill when adding a melonJS plugin or adapter — the debug panel, Spine skeletal animation, compressed Tiled map support, on-screen touch controls, Capacitor packaging — or when writing your own plugin. Covers plugin.register and its version gate, plugin.cache, plugin.patch, and why physics adapters are not plugins. Triggers on: plugin, plugin.register, plugin.cache, plugin.get, plugin.patch, BasePlugin, debug-plugin, DebugPanelPlugin, SpinePlugin, TiledInflatePlugin, GamepadPlugin, gamepad-plugin, touch controls, virtual joystick, floating joystick, floating button, CapacitorPlugin, adapter, custom plugin, plugin version mismatch, already registered."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -88,6 +88,30 @@ This is the answer for deforming animated characters — glTF import does not re
88
88
  vertex skinning. It also registers a Tiled object class, so Spine skeletons can
89
89
  be placed from a map.
90
90
 
91
+ ### `@melonjs/gamepad-plugin`
92
+
93
+ On-screen touch controls, reported as a gamepad: one or two sticks and any
94
+ number of buttons, drawn over the game and read by the engine as a pad in a
95
+ gamepad slot. The game's `bindGamepad` bindings drive it unchanged, so one set
96
+ of bindings covers a real pad and a touch screen.
97
+
98
+ ```js
99
+ import { GamepadPlugin } from "@melonjs/gamepad-plugin";
100
+ plugin.register(GamepadPlugin, "gamepad", {
101
+ buttons: [{ label: "A" }, { label: "B" }],
102
+ });
103
+ // plugin.cache.gamepad.visible — showing, to word prompts ("tap" or a key)
104
+ ```
105
+
106
+ Shown on touch screens by default (`enabled`), hidden while a real pad is in
107
+ use. The left stick also presses the D-pad; a button can press a key instead
108
+ of a pad button. Since 1.1 a stick or button can be `floating` (shows up
109
+ under the finger anywhere in its `area`), `hideAtRest` (drawn only while
110
+ held), and a stick can take a `tap` (a quick touch that does not steer presses
111
+ a key or button) — so the mobile layouts, two thumbs or one thumb with
112
+ tap-to-act, need no code of their own. See `melonjs-input` for the bindings,
113
+ the layouts and the overlap rules.
114
+
91
115
  ### `@melonjs/capacitor-plugin`
92
116
 
93
117
  Bridges Capacitor's native lifecycle and hardware-back events into the engine:
@@ -174,9 +174,10 @@ Use `addPostEffect()` / `getPostEffect()` / `removePostEffect()`. Assigning
174
174
  `renderable.shader = …` is deprecated since 19.2.0.
175
175
 
176
176
  Post effects need a programmable pipeline. A `ShaderEffect` constructed against
177
- the Canvas renderer (`renderer.shaderLanguage === null`) logs a warning, stays
178
- at `enabled === false` and no-ops every method rather than throwing — and
179
- `beginPostEffect` filters disabled effects out. Since `video.AUTO` picks the
177
+ the Canvas renderer (`renderer.shaderLanguage === null`) stays at
178
+ `enabled === false` and no-ops every method rather than throwing — and
179
+ `beginPostEffect` filters disabled effects out. The first one logs a warning,
180
+ the rest stay quiet, so a decorative effect needs no renderer check. Since `video.AUTO` picks the
180
181
  backend at runtime, an effect that works on your machine may do nothing on
181
182
  another.
182
183
 
@@ -290,6 +291,12 @@ other correct ending.
290
291
  `obj.alwaysUpdate === true`. An off-screen object with the default
291
292
  `alwaysUpdate = false` does not tick at all.
292
293
 
294
+ A `Container` is culled by its own bounds, which do not follow its children
295
+ unless it has a size of its own (`resize`) or `enableChildBoundsUpdate` is on.
296
+ With the flag, the bounds are the children's union, re-measured on every update
297
+ and, while the group is off screen, before each culling check — so a squad that
298
+ walks back into view is drawn again.
299
+
293
300
  ## Anchors and floating
294
301
 
295
302
  - `anchorPoint` defaults to centre `(0.5, 0.5)` on `Renderable`, but `Container`
@@ -321,6 +328,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
321
328
  | effect silently does nothing | running on the Canvas renderer |
322
329
  | object appears frozen while its state changes | `update()` not returning `true` |
323
330
  | off-screen object never ticks | `update()` is gated on `inViewport` — set `alwaysUpdate = true` |
331
+ | a group of moving children vanishes once they leave where they started | a sizeless container's bounds did not follow its children: give it a size, or `enableChildBoundsUpdate` (which only followed them from 20.13.0) |
324
332
  | custom draw offset by half the size | centred `anchorPoint` default not zeroed |
325
333
  | spatial query never finds an object | `isKinematic` left `true` — not in the broadphase |
326
334
  | `z` unexpectedly 0 after a `set` | `Vector3d.set(x, y)` defaults `z` to 0 — `setPosition(x, y)` on the renderable leaves the depth alone |
@@ -47,8 +47,8 @@ once and then degrades or no-ops, so the game runs and looks wrong.
47
47
  Two Canvas rows are worth spelling out because they are easy to misread as total
48
48
  failures. A `Light2d` still *draws* — the Canvas renderer bakes a radial gradient
49
49
  per light and blits it — what disappears is per-pixel normal-map shading, which
50
- warns once (`Light2d normal-map lighting requires the WebGL renderer`) and then
51
- renders sprites unlit. A `Mesh` still draws too, sorted with the painter's
50
+ warns once (`Light2d normal-map lighting requires a GPU backend`), the first time
51
+ a normal-mapped sprite is drawn under a light, and then renders sprites unlit. A `Mesh` still draws too, sorted with the painter's
52
52
  algorithm and no depth buffer, so convex models look right and anything
53
53
  self-overlapping does not. `"none"` (replace, alpha included) is the one blend
54
54
  mode Canvas cannot express — there is no `globalCompositeOperation` that
@@ -50,7 +50,10 @@ a transition's reveal follow once the shaders the stage queued are ready, which
50
50
  is a few milliseconds once the browser has compiled them before, and at most
51
51
  1.5s on a first visit. So build the stage in `onResetEvent`: an object a
52
52
  `STATE_CHANGE` listener adds is not waited for, and its first draw may still
53
- compile what it needs.
53
+ compile what it needs. That first frame steps the world once before drawing it,
54
+ however short it is, so everything `onResetEvent` added is in it (the same goes
55
+ for the frame after a `level.load` made while the game runs). Before 20.12 it
56
+ was drawn unstepped, and showed the stage's background alone for one frame.
54
57
 
55
58
  `state.change` **throws** `Undefined Stage for state '<id>'` if nothing was
56
59
  registered for that id, and `state.set` throws if the second argument is not a
@@ -104,11 +107,15 @@ this.HUD.floating = true; // screen coordinates, not world
104
107
  app.world.addChild(this.HUD, 100); // explicit z — see melonjs-renderables
105
108
  ```
106
109
 
110
+ Everything inside a persistent container is kept with it, physics bodies
111
+ included (before 20.13 the children's bodies were dropped by the reset).
112
+
107
113
  ## Transitions and pausing
108
114
 
109
115
  ```js
110
116
  state.transition("fade", "#FFFFFF", 250);
111
117
  state.transition("mask", "#000", 500, new Ellipse(0, 0, 1, 1)); // shape required
118
+ state.transition("pixelate", "#000", 300); // blocks up to 32px; a 4th argument sets the size
112
119
  state.pause(); // freeze update, keep drawing
113
120
  state.resume();
114
121
  app.freeze(150); // brief hit-stop (proxies state.freeze)
@@ -116,7 +123,18 @@ app.freeze(150); // brief hit-stop (proxies state.fr
116
123
 
117
124
  `state.transition` is global and sticky — once set it applies to every later
118
125
  `state.change`. The `"mask"` form needs its fourth argument, an `Ellipse` or
119
- `Polygon`; without one it warns and falls back to a direct switch.
126
+ `Polygon`; without one it warns and falls back to a direct switch. The
127
+ `"pixelate"` form breaks the screen up into blocks (its fourth argument is the
128
+ largest block size in pixels). It is a shader effect, so on Canvas it does
129
+ nothing and the state changes directly.
130
+
131
+ A `state.change` asked for while a transition covers the screen (Enter pressed
132
+ twice on a menu, a timer firing as the player skips a screen) needs no guard:
133
+ the transition already under way makes it, switching to the last state asked
134
+ for with the last arguments, and a second cover never goes up. A change to a
135
+ state with no transition of its own (`state.setTransition(id, false)`) is made
136
+ at once, and the cover comes down with it. Before 20.12 the second change left
137
+ the first cover on the camera for good, the screen black over a running game.
120
138
 
121
139
  ### A Stage's own `update()` is NOT paused
122
140
 
@@ -299,6 +317,14 @@ Four traps:
299
317
  - **Stop a tween before destroying its target**, or `onUpdate` fires against a
300
318
  dead renderable.
301
319
 
320
+ **A target can be an array: a path.** `to({ x: [200, 120, 300] })` runs from
321
+ the current value through each entry in turn, and
322
+ `interpolation: Tween.Interpolation.CatmullRom` smooths the corners. Each
323
+ `start()` builds the path afresh from the current value, so a replay or a chain
324
+ that comes back round runs the same path, and with `yoyo` the return leg plays
325
+ it back the way it came. Before 20.13 a restart ran a longer path each time and
326
+ a `yoyo` path turned the property into `NaN`.
327
+
302
328
  **To animate something that is not a property, tween a holder and push the
303
329
  value in `onUpdate`.** A shader uniform and a light's intensity are both set
304
330
  through a method, so there is nothing for `to()` to interpolate directly. The
@@ -372,6 +398,7 @@ import { save } from "melonjs";
372
398
 
373
399
  save.add({ hiscore: 0 }); // once; idempotent, loads any stored value
374
400
  save.hiscore = 1200; // plain assignment writes to localStorage
401
+ save.remove("hiscore"); // gone from localStorage; set it again to save it again
375
402
  ```
376
403
 
377
404
  **In TypeScript, keep what `add()` returns.** The namespace carries a
@@ -401,9 +428,12 @@ both.
401
428
  | the world freezes on blur but the game keeps advancing | simulation lives in `Stage.update`, which the pause does not gate — only `Container.update` checks `state.isPaused()` |
402
429
  | the player character is left behind and scrolls off after a lost window | same cause: the stage advanced the camera while its world children were paused |
403
430
  | a tween does nothing | `.start()` never called |
431
+ | an array (path) tween wanders further on each replay, or goes `NaN` with `yoyo` | before 20.13 `start()` re-prepended the start value to the path and `yoyo` swapped the array into the start value; upgrade |
432
+ | a repeating or `yoyo` tween runs a single cycle when started again (a pulse pulses once, a yoyo stays at the far end) | before 20.13 `start()` did not restore the `repeat` count the first run had used up; upgrade |
404
433
  | effect stops during a hit-stop | tween needs `updateWhenPaused = true` |
405
434
  | crash after a stage switch | callback ran during teardown — guard with `state.isCurrent` |
406
435
  | HUD scrolls away with the camera | missing `floating = true` |
436
+ | a `save` key set again after `save.remove()` reads back fine, then is gone (or back at its default) after a reload (before 20.13) | the write skipped the list of keys restored at boot; update |
407
437
 
408
438
  ## Related skills
409
439
 
@@ -95,6 +95,15 @@ Re-selecting the animation that is already current is a no-op, so calling
95
95
  `setCurrentAnimation` every frame is safe, and an `isCurrentAnimation` check
96
96
  around it adds nothing. An unknown id throws.
97
97
 
98
+ `addAnimation` with an id that exists replaces it, which is how you change an
99
+ animation's frames or speed (`hero.addAnimation("walk", [4, 5, 6, 7], 200)`).
100
+ Redefining the one that is playing takes effect at once: the playing frame is
101
+ kept if the new strip still has it, playback restarts on frame 0 if not, a
102
+ finished `loop: false` run holds the new last frame, and the
103
+ `setCurrentAnimation` options stay. Before 20.13 it kept the old frame count, so
104
+ a shorter strip threw on the next `update()` and a longer one never reached its
105
+ new frames.
106
+
98
107
  ## Texture atlases
99
108
 
100
109
  `TextureAtlas` reads several formats:
@@ -125,6 +134,9 @@ sprite.flipX(true); // mirror horizontally
125
134
  sprite.flipY(true);
126
135
  sprite.blendMode = "additive"; // see the effects skill for the full list
127
136
  sprite.scale(2, 2); // ← multiplicative, see below
137
+ sprite.flicker(1000, function () { // blink for a second, then…
138
+ this.ancestor.removeChild(this); // …`this` is the sprite; the flicker has ended
139
+ });
128
140
  ```
129
141
 
130
142
  **`scale()` is multiplicative, not absolute.** Calling it every frame compounds.
@@ -185,6 +197,8 @@ Two consequences:
185
197
  | event handlers leak on pooled objects | bound in the constructor rather than `onActivateEvent` |
186
198
  | animation never advances | no `framewidth`/`frameheight`, so the sheet is one frame |
187
199
  | animation plays once and stops | `{ loop: false }`, or a callback that returned `false` |
200
+ | `TypeError` reading `offset` or `delay` in `update()` after re-adding the playing animation with fewer frames, or a longer one never shows its new frames | before 20.13 redefining the current animation kept its old frame count; upgrade |
201
+ | `this` is wrong in a `flicker(ms, callback)` callback, or a flicker started from it stops at once | before 20.13.0 the callback ran on an internal object and the flicker was stopped after it; now `this` is the sprite and the flicker has already ended |
188
202
  | sprites do not batch | more distinct textures in one frame than `renderer.maxTextures` |
189
203
  | nine-slice corners stretch anyway | `insetX`/`insetY` instead of `insetx`/`insety` |
190
204
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-tilemaps
3
- description: "Use this skill for Tiled maps in melonJS — loading TMX/TSX levels, spawning entities from Tiled objects, collision shapes authored in Tiled, isometric and hexagonal maps, and image layers. Covers registerTiledObjectClass and the pool.register name contract, camera bounds, compressed maps needing the inflate plugin, and the level director API. Triggers on: Tiled, TMX, TSX, tilemap, level.load, level.load async, await level.load, tileset, ImageLayer, isometric, hexagonal, staggered, pool.register, Collectable, Trigger, object layer, collision layer, parallax, registerTiledObjectClass, registerTiledObjectFactory, TMXObject, resolvedProperty, resolvedProperties, tile class, tile properties."
3
+ description: "Use this skill for Tiled maps in melonJS — loading TMX/TSX levels, spawning entities from Tiled objects, collision shapes authored in Tiled, isometric and hexagonal maps, and image layers, and reading or changing tiles while the game runs. Covers registerTiledObjectClass and the pool.register name contract, camera bounds, compressed maps needing the inflate plugin, and the level director API. Triggers on: Tiled, TMX, TSX, tilemap, setTile, clearTile, cellAt, getTile, getTileById, change a tile, tile at run time, game board, grid, tile properties at run time, getTileProperties, map properties, level.load, level.load async, await level.load, tileset, ImageLayer, isometric, hexagonal, staggered, pool.register, Collectable, Trigger, object layer, collision layer, parallax, registerTiledObjectClass, registerTiledObjectFactory, TMXObject, resolvedProperty, resolvedProperties, tile class, tile properties."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -185,6 +185,90 @@ knowing before you spend an afternoon on a map that loads at the wrong scale.
185
185
  `async` is excluded deliberately — the trigger needs to sequence its own
186
186
  transition.
187
187
 
188
+ ## Reading and changing tiles at run time
189
+
190
+ A tile layer is a live grid, not just a picture: read it as the game board,
191
+ and change cells while the game runs — a door opening, a floor panel lighting
192
+ up, a wall blown away. A loaded tile layer is a `TMXLayer` in the world, under
193
+ the name it has in Tiled:
194
+
195
+ ```js
196
+ await level.load("map1", { container: this.app.world, async: true });
197
+ const board = this.app.world.getChildByName("ground")[0]; // a TMXLayer
198
+ board.cols; board.rows; // the grid, in cells
199
+ board.tilewidth; board.tileheight; // a cell, in pixels
200
+ ```
201
+
202
+ Two coordinate systems, and the methods do not share one:
203
+
204
+ | method | takes | returns |
205
+ |---|---|---|
206
+ | `cellAt(col, row)` | column and row | the `Tile` there, or `null` for an empty cell or outside the grid |
207
+ | `getTile(x, y)` | **world pixels** | the same `Tile` object `cellAt` gives, or `null` |
208
+ | `setTile(tile, col, row)` | column and row | puts `tile` there, and returns it |
209
+ | `clearTile(col, row)` | column and row | empties the cell: `cellAt` then returns `null` |
210
+ | `getTileById(gid, col, row)` | a global tile id, column and row | a new `Tile`, to pass to `setTile` |
211
+
212
+ Columns and rows are the map's tile coordinates, the ones Tiled shows. On an
213
+ **infinite** map a layer can hold tiles left of or above the map's origin:
214
+ there `board.startx` / `board.starty` are negative, its cells run from
215
+ `startx` to `startx + cols - 1` (and `starty` to `starty + rows - 1`), and
216
+ `cellAt(-3, -2)` is the tile Tiled shows at (-3, -2), drawn where Tiled draws
217
+ it. On a finite map both are 0:
218
+
219
+ ```js
220
+ for (let row = board.starty; row < board.starty + board.rows; row++) {
221
+ for (let col = board.startx; col < board.startx + board.cols; col++) {
222
+ const tile = board.cellAt(col, row); // every cell, infinite maps included
223
+ }
224
+ }
225
+ ```
226
+
227
+ The viewport bounds still cover only the map's `width` x `height`: set them
228
+ yourself to scroll to tiles painted outside it.
229
+
230
+ A `Tile` carries `tileId` (its **global** id), `col`, `row` and `tileset`.
231
+ Tiled shows a tile's *local* id in the tileset editor; the global id is
232
+ `tileset.firstgid + localId`, and only the global one works with `setTile`.
233
+ The tile's custom properties and class, set in Tiled's tileset editor, come
234
+ from its tileset by global id:
235
+
236
+ ```js
237
+ const tile = board.cellAt(col, row);
238
+ const props = tile && tile.tileset.getTileProperties(tile.tileId); // { kind: "floor" } or undefined
239
+ const cls = tile && tile.tileset.getTileClass(tile.tileId);
240
+ ```
241
+
242
+ To swap a cell for another tile of the tileset, build the tile and set it. Look
243
+ global ids up by property once, rather than hard-coding them, so a designer
244
+ can rearrange the tileset:
245
+
246
+ ```js
247
+ const tileset = board.tilesets.getTilesetByIndex(0); // the layer's first tileset
248
+ const gidByKind = new Map();
249
+ for (let gid = tileset.firstgid; gid <= tileset.lastgid; gid++) {
250
+ const kind = tileset.getTileProperties(gid)?.kind;
251
+ if (kind !== undefined) gidByKind.set(kind, gid);
252
+ }
253
+
254
+ // light a floor panel up
255
+ board.setTile(board.getTileById(gidByKind.get("floor-lit"), col, row), col, row);
256
+ // knock a wall down
257
+ board.clearTile(col, row);
258
+ ```
259
+
260
+ The layer redraws the changed cell by itself. What it does **not** change is
261
+ collision: solid ground comes from collision object layers (below), so a tile
262
+ set or cleared changes what is drawn and what `cellAt` returns, not what a body
263
+ runs into. Keep a layer you edit often off `preRender` (see the symptom table).
264
+
265
+ The map's own custom properties (Map → Map Properties in Tiled) are set on the
266
+ map object, which `level.getCurrentLevel()` returns once it is loaded: a
267
+ `theme` string property is `level.getCurrentLevel().theme`. They are set
268
+ **over** the map's own fields, so a property named like one replaces it: a
269
+ custom `name` property becomes the level's `name`. Give level settings names of
270
+ their own (`title`, not `name`).
271
+
188
272
  ## Camera bounds
189
273
 
190
274
  `level.load` sets the viewport bounds to the map size for you —
@@ -344,6 +428,7 @@ unanimated layer into the offscreen-bake path instead.
344
428
 
345
429
  | symptom | cause |
346
430
  |---|---|
431
+ | objects a screen added just before `level.load` draw in the order they were added (a background over everything) | before 20.13 the reset inside `level.load` dropped the sort still pending for the persistent children it kept; update, or call `sort()` on their container once the level is loaded |
347
432
  | a Tiled object becomes a plain shape with no behaviour | its class/name does not match any registered factory, or it was registered after `level.load` |
348
433
  | the world is still empty right after `level.load` | the load is deferred to a timer while the loop runs — `await level.load(id, { async: true })`, or use `onLoaded` / `LEVEL_LOADED` |
349
434
  | `await level.load(id)` returned `true` rather than a promise | without `async: true` the call returns a boolean; `await true` resolves immediately and the load has not run yet — pass the flag when you mean to await |
@@ -361,7 +446,16 @@ unanimated layer into the offscreen-bake path instead.
361
446
  | a property set on a tile is undefined on the object's `settings` | tile properties are not copied onto `settings`: read them with `settings.resolvedProperty(name)` |
362
447
  | objects placed from a classed tile come out as plain sprites | that class is not registered, it is a built-in name, or the object has a class or a registered name of its own, which come first |
363
448
  | missing tiles or a blank map | the `.tsx` or tileset image was not preloaded |
364
- | a tileset's transparent color (`trans`) still shows (the console says it could not be applied) | the image cannot be read back: one from another origin needs CORS headers on the server and `loader.setOptions({ crossOrigin: "anonymous" })` (without them WebGL and WebGPU cannot draw it at all), and a compressed texture cannot be changed: export it with an alpha channel instead |
449
+ | a tileset's transparent color (`trans`) still shows (the console says it could not be applied) | the image cannot be read back: an image the game made itself from another origin without CORS (an `<img>` with no `crossOrigin`) cannot be, so load it with the loader, which only loads an image another origin's server allows (its CORS headers) and can always read back; and a compressed texture cannot be changed: export it with an alpha channel instead |
450
+ | `cellAt` returns `null` (or the wrong tile) for a cell that has one | it was given pixels: `cellAt`, `setTile` and `clearTile` take column and row, only `getTile` takes world pixels |
451
+ | `setTile` shows the wrong tile, or nothing | it was given Tiled's local tile id: use the global id, `tileset.firstgid + localId`, through `getTileById` |
452
+ | an infinite map's tiles sit in the wrong row, or tiles left of or above the origin are missing | an older melonJS ignored where the chunks start (update); and loop from `layer.startx` / `layer.starty`, not 0, to reach cells left of or above the origin |
453
+ | on a staggered map `getTile` gives the neighbouring tile on every row but the first, or a scrolled view draws nothing | a bug of older melonJS versions in the staggered renderer: update |
454
+ | on an isometric (or staggered, oblique) map `getTile` gives an edge tile for a point just outside the tiles | an older melonJS rounded a fractional column or row toward 0: update |
455
+ | tiles of a second tileset draw from the first tileset's image (that first one has a `margin`) | an older melonJS let its gid range overrun into the next tileset (update): the range is the `tilecount` Tiled saved |
456
+ | a layer is `undefined` from `getChildByName(name)[0]` | the level is not in the world yet (await `level.load(id, { async: true })`), or the name differs from the layer's in Tiled |
457
+ | a tile set or cleared at run time does not block or free movement | tile layers carry no collision: collision comes from collision object layers |
458
+ | the level's `name` is not its id after loading | a map custom property named `name` replaced it: map properties are set over the map's own fields |
365
459
  | changing tiles at runtime is slow on a pre-rendered layer | outside an orthogonal map whose tiles fit their cells, a changed tile re-bakes the whole layer before it next draws: keep layers you edit often off `preRender` |
366
460
  | thin lines between tiles on the Canvas renderer, with smoothing on | filtering blends in the next tile of the tileset or its spacing, which a canvas cannot prevent: set `antiAlias: false`, or `preRender` the layer. WebGL and WebGPU keep each tile to itself |
367
461
  | tilemap slow on some machines | layer fell back off the shader path — check the console warning for the reason |