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-camera-and-drawing
3
- description: "Use this skill for camera control and immediate-mode drawing in melonJS — following a target, viewport bounds, shake and fade, secondary cameras and what each one draws, and drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, baking with CanvasRenderTarget, and reading pixels back. Triggers on: Camera2d, viewport, follow, offset, look ahead, setBounds, minimap, second camera, split screen, cullMask, visibilityLayer, hide from a camera, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d, lookAt, setBasis, toImageData, getImageData, toBlob, toDataURL, screenshot, read pixels."
3
+ description: "Use this skill for camera control and immediate-mode drawing in melonJS — following a target, viewport bounds, shake and fade, secondary cameras and what each one draws, and drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, baking with CanvasRenderTarget, and reading pixels back. Triggers on: Camera2d, viewport, follow, offset, look ahead, setBounds, minimap, second camera, split screen, cullMask, visibilityLayer, hide from a camera, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, setGlobalAlpha, fillArc, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d, lookAt, setBasis, toImageData, getImageData, toBlob, toDataURL, screenshot, read pixels."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -143,6 +143,12 @@ app.viewport.worldToLocal(x, y); // world → screen
143
143
  app.viewport.localToWorld(x, y); // screen → world
144
144
  ```
145
145
 
146
+ They agree with where the camera draws: a level smaller than the view is
147
+ centred by offsetting the world container (`world.pos`), which the default
148
+ camera draws and a minimap, corner or zoomed camera leaves out, and each
149
+ camera's conversions follow suit (20.13 and later; before, a minimap's were off
150
+ by `world.pos`).
151
+
146
152
  Both return a `Vector2d`; with no third argument that vector comes from the
147
153
  shared pool, so in a per-frame path pass your own `v` as the third argument, or
148
154
  release it with `getPool("vector2d").release(v)`.
@@ -205,6 +211,14 @@ and an overridden `postDraw`:
205
211
  this.cameras.set("minimap", new MinimapCamera());
206
212
  ```
207
213
 
214
+ A stage's cameras draw in the order they were added, each over the ones before
215
+ it, so a full-screen second camera is a layer: a `Camera3d` drawing a 3D
216
+ background first, the game's own camera over it, each kept to its part of the
217
+ world with `visibilityLayer` / `cullMask` (below). A camera's post effects
218
+ apply to what that camera draws: the first camera's pass starts the frame, and
219
+ a later camera's is blended over what is already there (20.13 and later;
220
+ before, it wiped out the cameras before it).
221
+
208
222
  View state is per camera, not per scene: a `Camera3d` carries its own distance
209
223
  fog (`setFog`), so a minimap or split-screen view fogs independently of the main
210
224
  one — and a `Camera2d` never fogs. See `melonjs-3d`.
@@ -228,6 +242,9 @@ minimap.cullMask = ~PARTICLES; // the minimap draws every layer but 2
228
242
  set it on an emitter and none of its particles are drawn. The world itself
229
243
  obeys it too, and so does what it contains: a child is drawn only if it and
230
244
  every container above it share a bit with the camera.
245
+ - Particles are on their emitter's layers, and follow a change to them on the
246
+ emitter's next update, so a camera that draws only the emitter's layer draws
247
+ its particles too.
231
248
  - A renderable on layer `0` is drawn by no camera; a `cullMask` of `0` draws
232
249
  nothing.
233
250
  - It decides drawing only: what a camera leaves out still updates, collides and
@@ -256,6 +273,23 @@ draw(renderer) {
256
273
  }
257
274
  ```
258
275
 
276
+ **The colour's alpha and the global alpha are two things**, as on a 2D
277
+ context. A fill or a stroke is drawn at the alpha of `setColor` times
278
+ `setGlobalAlpha` (which is where the renderable's opacity comes in), an image at
279
+ the global alpha (times the tint) alone, and `getGlobalAlpha()` never moves with
280
+ `setColor`. So to fade a translucent shape, lower the global alpha and keep the
281
+ colour; folding the colour's alpha into `setGlobalAlpha` as well counts it twice.
282
+
283
+ `lineWidth`, `lineJoin`, the colour and the global alpha are all part of what
284
+ `save()` / `restore()` keep, and every renderable's `draw()` runs between the
285
+ two, so set them freely there: the next renderable gets them back.
286
+
287
+ `fillArc` / `strokeArc` read their angles like `CanvasRenderingContext2D.arc`:
288
+ an end before the start wraps round (`end + 2π`), and a span of 2π or more in
289
+ the arc's own direction is the whole circle. A filled part-circle differs by
290
+ backend, though: Canvas closes it with a straight chord, the GPU renderers fill
291
+ a pie slice from the centre.
292
+
259
293
  Geometry available: `Rect`, `RoundRect`, `Ellipse`, `Polygon`, `Line`, `Point`
260
294
  (and a `Bounds`). `renderer.fill(shape)` and `renderer.stroke(shape)` take any
261
295
  of them and throw `Invalid geometry for fill/stroke` for anything else.
@@ -383,6 +417,7 @@ const data = await rt.toImageData(0, 0, w, h);
383
417
 
384
418
  | symptom | cause |
385
419
  |---|---|
420
+ | a second camera's post effect wipes out what the first camera drew | before 20.13 a later camera's post-effect pass replaced the whole frame; update, or put the effects on a container instead of the camera |
386
421
  | `follow()` set but the view never moves | viewport bounds still at renderer size |
387
422
  | `invalid target for me.Camera2d.follow` | passed `player.pos` — pass the renderable, a `Vector2d` or a `Vector3d` |
388
423
  | camera drag jumps around | using world (`gameWorldX`) instead of screen (`gameScreenX`) coordinates |
@@ -390,6 +425,11 @@ const data = await rt.toImageData(0, 0, w, h);
390
425
  | custom drawing appears at the origin | `draw()` not reading `this.pos` |
391
426
  | custom drawing offset by half its size | centred `anchorPoint` not zeroed |
392
427
  | `setLineWidth` is not a function | it is the `lineWidth` property |
428
+ | after a translucent `setColor`, images, later colours and the next renderables come out faded (WebGL / WebGPU) | before 20.13 the colour's alpha leaked into the global alpha; update |
429
+ | a translucent colour comes out fainter than asked | its alpha folded into `setGlobalAlpha` as well, so it counts twice |
430
+ | a `lineWidth` set in one `draw()` thickens everything drawn after it (WebGL / WebGPU) | before 20.13 `restore()` did not put `lineWidth` back; update, or put it back yourself |
431
+ | `fillArc` with the end angle before the start fills the wrong part of the circle (WebGL / WebGPU) | before 20.13 the GPU renderers did not wrap the angles like Canvas; update |
432
+ | a minimap's `worldToLocal` / `localToWorld` are off by the level's centring offset | before 20.13 they applied `world.pos`, which a non-default camera does not draw; update |
393
433
  | clipping in the wrong place inside a container | container clips in local coordinates, after its own translate |
394
434
  | `container.clipping = true` does nothing | container has no explicit size, so `width`/`height` are `Infinity` |
395
435
  | baked target draws nothing / throws | passed the `CanvasRenderTarget`, not its `.canvas` |
@@ -195,11 +195,25 @@ A repeating pattern is left to wrap.
195
195
 
196
196
  ## `subPixel` is about position, not scale
197
197
 
198
- `subPixel: false` (the default) truncates a renderable's translation to whole
199
- pixels inside the frame. It is `|0`, so it truncates **toward zero** rather
200
- than rounding, which is worth knowing when something travels a short distance:
201
- an offset that should swing between -3 and +3 lands on -2 and +2, and parks at
202
- the extremes for longer than it should.
198
+ `subPixel: false` (the default) draws everything on whole pixels. The snap
199
+ happens **once, where a draw lands**, the same on every renderer: each edge of
200
+ a sprite, a shape or a clip is rounded to the nearest pixel after every
201
+ translation and scale it is drawn under. So:
202
+
203
+ - nested containers add their positions up exactly first (10.6 then -6.5
204
+ lands on 4, the pixel 4.1 is in);
205
+ - a flipped sprite covers the pixels it covers unflipped;
206
+ - a sprite and a `fillRect` at the same position cover the same pixels, and a
207
+ shape has no half-grey edge on the Canvas renderer;
208
+ - a position floating point error leaves a hair short of a pixel, such as a
209
+ body resting on the floor at `223.99999999999997` after a collision
210
+ push-out, draws on that pixel (224), so it does not shake every other frame.
211
+
212
+ The camera's view starts on a whole pixel too, in world units (zoomed 2x, it
213
+ moves in 2 px steps), and culling and the pointer use that same position:
214
+ things standing still stay still against each other while the view scrolls,
215
+ and a click lands on what is drawn under it. A rotated draw, and anything a
216
+ `Camera3d` draws, is left where it is.
203
217
 
204
218
  It does nothing about how the frame reaches the screen. A game that carefully
205
219
  snaps positions and is then scaled by 2.667 has thrown that work away at the
@@ -138,18 +138,44 @@ as transparent, as if the sprite were drawn on its own, so they never show
138
138
  along its edges and the atlas needs no padding for them. Past the texture's own
139
139
  edge (a standalone image, or the outside of an atlas) nothing changes.
140
140
 
141
+ **The effects that place a pattern place it on the renderable they draw.**
142
+ `SwayEffect`'s planted row, `WaveEffect`'s `anchor`, the centre of
143
+ `VignetteEffect`, `RadialGradientEffect` and `ScanlineEffect`'s curvature all
144
+ land on the sprite itself: cut from an atlas, flipped, rotated, or with other
145
+ effects after it, the pattern sits where it sits on a sprite with a texture of
146
+ its own and that one effect. What is measured in the texture stays measured in
147
+ it: `ShineEffect`'s sweep, bands and width, `WaveEffect`'s amplitude and
148
+ frequency, `DissolveEffect`'s grain, the lines of `HologramEffect` and
149
+ `ScanlineEffect` (on an atlas sprite, fractions of the atlas, so a single shine
150
+ crosses an atlas sprite only part of the time). With other effects after them
151
+ they all stay on the sprite. `SwayEffect` measures everything across the
152
+ sprite.
153
+
141
154
  **Their widths, offsets and sizes are in pixels of the texture they draw, with
142
155
  nothing to pass.** Each of those effects takes the size of what it draws: the
143
156
  sprite's whole texture (the atlas image, not the frame), the screen for a
144
- camera's effect, a mesh's texture. Do not pass `textureSize` to get this: a
157
+ camera's effect, a mesh's texture. With other effects after it, it still
158
+ measures in the sprite's own pixels, at any scale and under a camera's zoom
159
+ (on a container, in the container's own), so adding a second effect never
160
+ changes how wide the first comes out. Do not pass `textureSize` to get this: a
145
161
  `textureSize` (or `setTextureSize()`) fixes the size instead, for a width
146
162
  meant in some other texture's pixels.
147
163
 
148
164
  ```js
149
- // 2 pixels of the sprite's art, whatever the size of its atlas
165
+ // 2 pixels of the sprite's art, whatever the size of its atlas, its scale,
166
+ // the camera's zoom, or the effects after it
150
167
  sprite.addPostEffect(new OutlineEffect(app.renderer, { width: 2 }));
168
+ sprite.addPostEffect(new GlowEffect(app.renderer, { width: 6 }));
151
169
  ```
152
170
 
171
+ With two or more effects, the sprite is drawn into a capture and each effect
172
+ reads that, at the game's texture filter, as a sprite reads its own texture:
173
+ under `"nearest"` (or `antiAlias` off) a width that lands between two screen
174
+ pixels, such as 2 pixels of a sprite drawn at 0.3, stays crisp, and under
175
+ `"linear"` it blends, on WebGL and WebGPU alike. The result lands inside the
176
+ clip the sprite is drawn under, a minimap camera's view or a clipping
177
+ container, as with one effect.
178
+
153
179
  `SwayEffect` leans a sprite or a layer from side to side, as if in a breeze: the
154
180
  top sways most and everything from its `anchor` row down stays put, so a tree's
155
181
  trunk or a reed's root stays planted. It suits anything planted at its bottom;
@@ -166,17 +192,18 @@ tree.addPostEffect(sway);
166
192
  sway.setTime(timer.getTime() / 1000);
167
193
  ```
168
194
 
169
- Rows are measured across the texture the effect samples, so on a sprite cut from
170
- an atlas the anchor is a row of the atlas: it suits a standalone image or a
171
- layer best.
195
+ Its strength (a fraction of the sprite's width), its `anchor` and how it bends
196
+ are all measured across the sprite, so a sprite cut from an atlas sways exactly
197
+ like the same art with a texture of its own. `WaveEffect`'s `anchor` is a row of
198
+ the sprite too, but its amplitude and frequency are measured across the texture,
199
+ so on an atlas sprite they are fractions of the atlas.
172
200
 
173
- `RadialGradientEffect` is a procedural soft spot: solid at the centre of its
174
- host quad, fading linearly to transparent at the edge, and elliptical for free
175
- on a non-square quad. It is the one to reach for instead of baking a disc into
176
- a canvas, for a pickup highlight, a trigger marker or a damage flash. Its
177
- falloff assumes the quad samples a FULL texture rect, so pair it with a
178
- standalone image or `Renderer.getWhitePixel()` rather than a sprite that draws
179
- a sub-region of an atlas, where the centre would land off-quad.
201
+ `RadialGradientEffect` is a procedural soft spot: solid at the centre of the
202
+ renderable it draws, fading linearly to transparent at the edge, and elliptical
203
+ for free on a non-square one. It is the one to reach for instead of baking a
204
+ disc into a canvas, for a pickup highlight, a trigger marker or a damage flash,
205
+ on a standalone image, `Renderer.getWhitePixel()` or a sprite cut from an atlas
206
+ alike.
180
207
 
181
208
  `SquishEffect` bulges a sprite's middle out (`setBulge(0.3)`) or pinches it in
182
209
  (`setBulge(-0.3)`), its top and bottom rows staying put. Bulging out widens the
@@ -524,6 +551,10 @@ has changes nothing). What splits the batch:
524
551
  - a different effect instance, or a sprite without the effect in between;
525
552
  - a `noise_uv` effect on sprites of different frames or sizes (its rect is
526
553
  per sprite);
554
+ - one of the effects that place a pattern on the sprite (`SwayEffect`,
555
+ `WaveEffect`, `ShineEffect`, `VignetteEffect`, `RadialGradientEffect`,
556
+ `ScanlineEffect`, `DissolveEffect`, `HologramEffect`): each sprite is told
557
+ where it is, so each draws on its own;
527
558
  - a `screen_texture` effect, which draws each sprite on its own, as each one
528
559
  samples a capture of everything drawn before it.
529
560
 
@@ -603,7 +634,9 @@ For refraction, distortion and anything that samples what is already drawn:
603
634
  - **`screen_texture`** — a sampler kept filled with everything drawn so far
604
635
  (GLSL: annotate the sampler `uniform sampler2D tex : screen_texture;`)
605
636
  - **`screen_uv`** — this fragment's 0..1 position in that capture
606
- - **`noise_uv`** — 0..1 across the sprite regardless of its atlas frame
637
+ - **`noise_uv`** — 0..1 across the sprite regardless of its atlas frame, and
638
+ across the renderable when it has several effects (it does not slide with
639
+ the camera)
607
640
 
608
641
  In WGSL the capture is sampled through `screen_sampler` (clamped) or
609
642
  `screen_sampler_repeat` (wrapping) instead of GLSL's `: screen_texture`
@@ -698,11 +731,13 @@ that against your request detects the fallback.
698
731
 
699
732
  ## The Canvas fallback
700
733
 
701
- `ShaderEffect` on a Canvas renderer logs a warning, leaves `enabled = false`, and
702
- turns every method into a no-op. Nothing throws. The same happens on a GPU
703
- backend when no body matches its language — a GLSL-only effect under WebGPU.
734
+ `ShaderEffect` on a Canvas renderer leaves `enabled = false` and turns every
735
+ method into a no-op. Nothing throws, and the console gets one warning, from the
736
+ first such effect; the rest stay quiet. The same happens on a GPU backend when
737
+ no body matches its language — a GLSL-only effect under WebGPU.
704
738
 
705
- That is fine for decoration. It is **not** fine when the shader draws your
739
+ That is fine for decoration, which needs no guard: create and add the effect as
740
+ on any renderer. It is **not** fine when the shader draws your
706
741
  content — the carrier renderable then shows through raw. A 1×1 white texture
707
742
  stretched to 280×156 renders as a white box where the effect should be.
708
743
 
@@ -723,6 +758,7 @@ same question after construction.
723
758
 
724
759
  | symptom | cause |
725
760
  |---|---|
761
+ | a camera's post effect hides another camera drawn before it | before 20.13 a later camera's pass replaced the whole frame; update. Since then each camera's effects apply to what it draws, over the cameras before it |
726
762
  | effect does nothing, warning in console | Canvas fallback — no programmable pipeline |
727
763
  | effect does nothing on some machines only | GLSL-only shader, `video.AUTO` chose WebGPU |
728
764
  | a white or solid box where the effect should be | carrier renderable drawn while the effect is disabled |
@@ -87,7 +87,7 @@ right times and there is nothing to unsubscribe:
87
87
  | callback | when |
88
88
  |---|---|
89
89
  | `onActivateEvent()` | `addChild`-ed into a container that is already attached to the world root (a `Container` forwards it to its children) |
90
- | `onDeactivateEvent()` | removed from its container — including the automatic world clear on a state change |
90
+ | `onDeactivateEvent()` | removed from its container — including the automatic world clear on a state change. Once per `onActivateEvent`: the children of a removed container, at any depth, are told once |
91
91
  | `onDestroyEvent()` | `destroy()` ran; called with no arguments from the container-removal path |
92
92
  | `onCollisionStart` / `onCollisionActive` / `onCollisionEnd` | contacts |
93
93
 
@@ -154,6 +154,8 @@ Use `removeChild()`, or flag the work and drain it from `Stage.update`.
154
154
  | crash after a collision | `removeChildNow()` inside a contact callback — use `removeChild()` |
155
155
  | progress callbacks never fire | assigned `loader.onProgress` (removed in 20.3) instead of subscribing to `LOADER_PROGRESS` |
156
156
  | `onActivateEvent` never runs | the child was added to a container not yet attached to the world |
157
+ | `onDeactivateEvent` runs twice when a group is removed (a count goes negative, a light or listener is released twice) | the group's `onDeactivateEvent` and its `destroy()` both reached the children, before 20.13.0 — now once per `onActivateEvent` |
158
+ | `onDeactivateEvent` never runs on a child of a group built off-world and destroyed | it never had `onActivateEvent` either: the pair is only sent to children that entered the world |
157
159
 
158
160
  ## Related skills
159
161
 
@@ -137,7 +137,6 @@ with the formats you ship before any audio asset is preloaded.
137
137
  ```js
138
138
  import { audio, loader } from "melonjs";
139
139
 
140
- loader.setOptions({ crossOrigin: "anonymous" }); // needed for remote assets
141
140
  audio.init("mp3,ogg"); // BEFORE preloading audio
142
141
  await loader.preload([
143
142
  { name: "tileset", type: "image", src: "data/img/tileset.png" },
@@ -173,7 +172,7 @@ placement.
173
172
  | scene content appears once and never resets | scene built in the constructor instead of `onResetEvent` |
174
173
  | everything offset by half a sprite | top-left math against the centred `anchorPoint` default |
175
174
  | a `Tween` or `ParticleEmitter` does nothing | `.start()` / `streamParticles()` never called |
176
- | cross-origin textures fail to load | missing `loader.setOptions({ crossOrigin: "anonymous" })` |
175
+ | an asset on another origin fails to load, the error naming it | that server sends no `Access-Control-Allow-Origin` header (see melonjs-loading-assets) |
177
176
 
178
177
  ## Related skills
179
178
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-input
3
- description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, KEYDOWN, KEYUP, press any key, splash screen, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
3
+ description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, virtual gamepad, touch controls, joystick, gamepad-plugin, floating stick, floating button, floating joystick, two thumbs, tap to fire, hideAtRest, mobile controls, KEYDOWN, KEYUP, press any key, splash screen, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -273,7 +273,60 @@ device, so a pad unplugged and plugged back into the same slot keeps working.
273
273
 
274
274
  ## Virtual controls
275
275
 
276
- On-screen buttons synthesise key events, again reaching the same actions:
276
+ For on-screen touch controls, use `@melonjs/gamepad-plugin` rather than
277
+ writing them: sticks and buttons drawn over the game and reported as a pad in
278
+ a gamepad slot, so the `bindGamepad` bindings above answer to them unchanged.
279
+
280
+ ```js
281
+ import { GamepadPlugin } from "@melonjs/gamepad-plugin";
282
+
283
+ // once the application is created: a stick and two buttons, on touch screens
284
+ plugin.register(GamepadPlugin, "gamepad", {
285
+ buttons: [{ label: "A" }, { label: "B" }],
286
+ });
287
+ ```
288
+
289
+ The left stick presses the D-pad buttons (`GAMEPAD.BUTTONS.UP` ...) in 8
290
+ directions and reports `AXES.LX`/`LY`; the buttons press `FACE_1`, `FACE_2`...
291
+ in turn, or a key with `{ key: input.KEY.SPACE }`. Each finger is followed on
292
+ its own, a touch starting on the controls does not reach the game, and they
293
+ hide while a real pad is in use. `plugin.cache.gamepad.visible` says whether
294
+ they show, to word a prompt ("tap" or a key name).
295
+
296
+ Sticks and buttons are fixed in a corner by default. **`floating: true`**
297
+ puts one under the finger wherever it lands inside its `area` (`"half"`, its
298
+ own half of the screen, by default; `"screen"`; or `{ x, y, width, height }`),
299
+ and **`hideAtRest: true`** draws it only while held. The usual mobile
300
+ two-thumb layout, nothing on screen until touched (1.1):
301
+
302
+ ```js
303
+ plugin.register(GamepadPlugin, "gamepad", {
304
+ sticks: [{ floating: true, hideAtRest: true }], // left half
305
+ buttons: [{ floating: true, hideAtRest: true, label: "A" }], // right half
306
+ });
307
+ ```
308
+
309
+ Or one thumb for both: a stick over the whole screen whose quick tap (lifted
310
+ within `tap.time`, 250 ms, without leaning to `dpadThreshold`) presses a key
311
+ or button for one frame, and no button at all:
312
+
313
+ ```js
314
+ plugin.register(GamepadPlugin, "gamepad", {
315
+ sticks: [{ floating: true, area: "screen", hideAtRest: true,
316
+ tap: { key: input.KEY.SPACE } }],
317
+ buttons: 0,
318
+ });
319
+ ```
320
+
321
+ Where areas overlap, a finger takes the first free control there: fixed
322
+ buttons, fixed sticks, floating buttons, floating sticks. So a floating button
323
+ on its `"half"` carves the right half out of a `"screen"` stick, and several
324
+ floating buttons sharing an area split it by distance to where each rests.
325
+ Every press lasts at least one frame, so a 15 ms thumb tap still reaches
326
+ `isKeyPressed`, and Safari's pinch gesture is cancelled while the controls
327
+ show, so a second thumb works on iOS.
328
+
329
+ A single key can also be pressed directly, reaching the same actions:
277
330
 
278
331
  ```js
279
332
  input.triggerKeyEvent(input.KEY.LEFT, true); // press
@@ -293,12 +346,17 @@ input.triggerKeyEvent(input.KEY.LEFT, false); // release
293
346
  | jump fires every frame while held | `bindKey` without its third `lock` argument |
294
347
  | `invalid event type` thrown at registration | non-pointer name (`"click"`, `"mousedown"`, …) passed to `registerPointerEvent` |
295
348
  | `no action defined for keycode N` | `bindGamepad` called before `bindKey` for that keycode |
349
+ | floating stick ignores the right half of the screen | its `area` defaults to `"half"`, its own side — use `area: "screen"` |
350
+ | `area: "screen"` stick still ignores part of the screen | a floating button's area comes first there — the overlap order is fixed buttons, fixed sticks, floating buttons, floating sticks |
351
+ | a quick tap on a stick moves instead of acting | it leaned past `dpadThreshold` or outlasted `tap.time` before lifting |
296
352
  | stick has to be pushed well past its `threshold` before it registers | `threshold` is ADDED to the deadzone, not used instead of it |
297
353
  | one half of a stick does nothing | each half is a separate `bindGamepad`, chosen by the sign of `threshold` |
298
- | a widget under an overlapping panel still reacts | the panel consumes nothing — return `false` from its `onClick` and `onOver` |
354
+ | a widget under an overlapping panel still lights up | the panel consumes no hover: return `false` from its `onOver` (presses already stop at UI elements; `UIPanel` with `blocking` does both) |
299
355
  | a widget stays highlighted after the pointer moves onto a panel over it | the panel consumes no `pointermove`, so nothing takes the pointer off the widget |
300
356
  | a child's big `z` does not put it in front of another container | `pos.z` is container-local — use `ancestor.moveToTop()` on the container |
301
357
  | every click in the game stops working | a handler on `app.viewport` returning `false`, which is asked first |
358
+ | touch controls do nothing | nothing bound to their slot — `bindGamepad` on the plugin's `index` (0 by default), or give the buttons a `key` |
359
+ | touch controls never show on a desktop | the gamepad plugin's `enabled` defaults to `device.touch` — pass `enabled: true` to try them there |
302
360
 
303
361
  ## Related skills
304
362
 
@@ -174,9 +174,10 @@ the soft one is one shader pass on the GPU backends and an offscreen canvas on
174
174
  Canvas. So the lit-spot look, soft or hard, works everywhere, Canvas included.
175
175
 
176
176
  What Canvas cannot do is **per-pixel normal-map lighting**:
177
- `renderer.setLightUniforms()` is a no-op there, and the first frame that passes a
178
- non-empty light list logs a one-shot warning. Normal-mapped sprites simply render
179
- unlit — no error.
177
+ `renderer.setLightUniforms()` is a no-op there, and the first normal-mapped
178
+ sprite drawn while lights are on logs a one-shot warning (a scene with lights
179
+ and no normal maps stays quiet). Normal-mapped sprites simply render unlit — no
180
+ error.
180
181
 
181
182
  ```js
182
183
  if (app.renderer.shaderLanguage === null) {
@@ -13,8 +13,6 @@ Every asset is `{ name, type, src }` plus type-specific extras:
13
13
  ```js
14
14
  import { loader } from "melonjs";
15
15
 
16
- loader.setOptions({ crossOrigin: "anonymous" }); // needed for remote assets
17
-
18
16
  await loader.preload([
19
17
  { name: "tiles", type: "image", src: "data/img/tiles.png" },
20
18
  { name: "map1", type: "tmx", src: "data/map/map1.tmx" },
@@ -183,7 +181,8 @@ the hard way:
183
181
  ### `DefaultLoadingScreen` lives in the WORLD, not on the screen
184
182
 
185
183
  The built-in loading screen is written for a `Camera2d`. Its progress bar and
186
- logo are added with `app.world.addChild(bar, 1)` / `addChild(logo, 2)` and are
184
+ logo are added together, once the logo is decoded (a frame or so after the
185
+ screen comes up), with `app.world.addChild(bar, 1)` / `addChild(logo, 2)`, and are
187
186
  **`floating === false`** — ordinary world-space renderables. They read as an
188
187
  overlay only because that stage's world is otherwise empty.
189
188
 
@@ -195,7 +194,7 @@ in front of a bar sitting at z = 1. What does NOT rescue it, all tried:
195
194
  depth, so the scene's own z values are geometry, not layering, and the bar
196
195
  still loses to the depth test.
197
196
  - **`moveToTop()`** — it early-returns for a child already at index 0, which the
198
- loading screen's children always are, having been added before anything else.
197
+ loading screen's children usually are, having been added before anything else.
199
198
  It silently does nothing.
200
199
  - **Flipping them to `floating = true`** for the duration — the scene still
201
200
  covers them.
@@ -348,6 +347,20 @@ event.on(event.LOADER_COMPLETE, () => { /* … */ });
348
347
  event.on(event.LOADER_ERROR, (res) => { /* … */ });
349
348
  ```
350
349
 
350
+ **Each `preload()` has only its own callback.** One given none calls nothing
351
+ on completion, so a later `await loader.preload(levelAssets)` does not re-run
352
+ the boot callback. Before 20.13 the loader kept the last callback it was given
353
+ and called it again for a preload that had none — usually a `state.change`
354
+ straight back to the first screen. `loader.reload(src)` after a failure
355
+ completes the preload that asset failed in, with that preload's callback, once
356
+ all of its assets are in.
357
+
358
+ **Progress is per preload:** `LOADER_PROGRESS` goes from that call's first
359
+ asset to `1` with its last, every time. A preload started while another is
360
+ still loading joins it, and the value covers both until everything in flight
361
+ has settled; the next preload starts from zero again. A failed asset keeps
362
+ its preload short of `1`, and only that preload.
363
+
351
364
  ## Unloading
352
365
 
353
366
  ```js
@@ -376,20 +389,45 @@ state.change(state.PLAY, true);
376
389
 
377
390
  ## Cross-origin and authenticated assets
378
391
 
379
- Two settings, both global and both applying to **every** asset type — there is
380
- no per-asset or per-type spelling to remember:
392
+ **Nothing to set for the usual cases.** Every fetched asset (image, json,
393
+ binary, tmx, tsx, shader, gltf/glb, obj, mtl, aseprite, buffered audio) is
394
+ requested the same way:
395
+
396
+ - **On the game's own origin**, it carries the browser's cookies and HTTP
397
+ auth, as the page and its scripts do. A game behind a login or a session
398
+ cookie loads its own assets with no option.
399
+ - **On another origin** (a CDN), it is a CORS request: it loads when that
400
+ server sends `Access-Control-Allow-Origin`, and fails at load when not, with
401
+ an error naming the asset and the header it needs. No `crossOrigin` needed.
402
+
403
+ | asset on… | default | `withCredentials: true` |
404
+ |---|---|---|
405
+ | the game's own origin | loads, with its cookies | loads, with its cookies |
406
+ | another origin sending `Access-Control-Allow-Origin` | loads, no cookies | **blocked** if it answers `*`; needs the page's exact origin plus `Access-Control-Allow-Credentials: true` |
407
+ | another origin allowing credentials for the page | loads, no cookies | loads, with that origin's cookies |
408
+ | another origin with no CORS headers | fails at load, naming the asset | fails at load, naming the asset |
409
+
410
+ Two global settings cover the rest, for every asset type — there is no
411
+ per-asset or per-type spelling to remember:
381
412
 
382
413
  ```js
383
414
  loader.setOptions({
384
- crossOrigin: "anonymous", // CORS mode for remote assets
385
- withCredentials: true, // send cookies / auth with the request
415
+ withCredentials: true, // send cookies / auth to OTHER origins too
416
+ crossOrigin: "anonymous", // attribute of the <video>, <script> and glTF
417
+ // image elements the loader creates
386
418
  });
387
419
  ```
388
420
 
389
- `withCredentials` is what an asset behind a session cookie or an
390
- `Authorization`-style login needs. Every fetched asset — image, json, binary,
391
- tmx, tsx, shader, gltf/glb, obj, mtl, aseprite, video — carries it, and so does
392
- buffered audio.
421
+ `withCredentials` is for assets on **another** origin behind a session cookie
422
+ or a login; that server must then allow credentials for the page's origin, or
423
+ the browser blocks the request. `crossOrigin: "use-credentials"` does the same
424
+ for fetched assets, and sets the attribute on the elements.
425
+
426
+ Before 20.13 both were needed far more often: assets on the game's own origin
427
+ went out with no cookies unless `withCredentials` was set (a 401 or 403 behind
428
+ a login), and an asset on another origin without `crossOrigin: "anonymous"`
429
+ was read as an empty response, failing later with "Unexpected end of input" or
430
+ "The source image could not be decoded" even when its server allowed it.
393
431
 
394
432
  **Set them through `setOptions`.** `loader.crossOrigin` and
395
433
  `loader.withCredentials` are read-only module bindings; assigning to them
@@ -397,7 +435,7 @@ throws, the same trap as the removed `loader.onload` / `onProgress`.
397
435
 
398
436
  | | |
399
437
  | --- | --- |
400
- | one streamed clip (`stream: true` / `html5: true`) ignores `withCredentials` | it plays through an `<audio>` element, which needs a `crossorigin` attribute rather than fetch credentials. Preload it buffered if it is behind auth |
438
+ | one streamed clip (`stream: true` / `html5: true` / `preload: false`) ignores `withCredentials` | it plays through an `<audio>` element, which needs a `crossorigin` attribute rather than fetch credentials. Preload it buffered if it is behind auth |
401
439
  | credentials silently dropped for audio before 20.4 | the loader forwarded them under a pre-20.3 name the backend never read — a hung preload rather than an error |
402
440
 
403
441
  ## Symptom → cause
@@ -407,13 +445,17 @@ throws, the same trap as the removed `loader.onload` / `onProgress`.
407
445
  | audio 404s | `src` given as a file instead of a directory |
408
446
  | audio fails, `preload()` rejects with "Failed loading resource" | `audio.init()` not called before preloading — the real message is replaced by the loader's |
409
447
  | blank level, missing tiles | `.tsx` or tileset image not listed in the resources |
410
- | cross-origin textures fail | missing `loader.setOptions({ crossOrigin: "anonymous" })` |
411
- | assets behind a login 401 / audio preload hangs | missing `loader.setOptions({ withCredentials: true })` — one switch, all asset types |
448
+ | `Failed to load "…" (Failed to fetch). An asset on another origin loads only if its server allows it…` | that origin sends no `Access-Control-Allow-Origin` header — or, with `withCredentials`, sends `*` or no `Access-Control-Allow-Credentials: true`. Fix the server's headers, or serve the asset from the game's own origin |
449
+ | an asset on another origin behind a login 401s / audio preload hangs | missing `loader.setOptions({ withCredentials: true })` — one switch, all asset types. Assets on the game's own origin need nothing (before 20.13 they needed it too) |
450
+ | a remote JSON fails with "Unexpected end of input", or an image with "The source image could not be decoded" | melonJS before 20.13, which read assets on another origin without CORS unless `crossOrigin: "anonymous"` was set |
412
451
  | `TypeError` assigning `loader.withCredentials` / `crossOrigin` | read-only module bindings; use `loader.setOptions({ … })` |
413
452
  | preload never finishes, blank screen, no error | a failing sound with `audio.setStopOnAudioError(false)` before 20.4 — it disabled audio *and* failed the preload |
414
453
  | text renders in a fallback font | web font not preloaded as `"fontface"` |
415
454
  | `BitmapText` renders nothing | the `.fnt` (as `"binary"`) or its image is missing |
416
455
  | `TypeError` assigning `loader.onProgress` / `onload` | removed in 20.3; they were never writable — use the `LOADER_*` events |
456
+ | `await loader.preload(level)` sends the game back to its title screen, or runs the boot setup twice (before 20.13) | a preload with no callback re-ran the previous preload's callback; update, each preload now calls only its own |
457
+ | the loading bar starts part-full on a second preload, or never reaches the end after one asset failed (before 20.13) | the progress counters ran for the whole session; update, progress is per preload |
458
+ | `loader.reload(src)` of a custom asset type throws `unload : unknown or invalid resource type` (before 20.13) | update: a type registered with `setParser` can now be retried |
417
459
  | `load : unknown or invalid resource type` | a custom type in the manifest before its `loader.setParser` call |
418
460
  | black or frozen screen when a stage is first entered, the profile shows your own code (noise, a canvas bake) | a costly derived asset built on stage entry — bake it inside the preload as a [custom asset type](#custom-asset-types) |
419
461
  | a custom asset preloads but the result is nowhere to be found | `onload` takes no value and the loader stores nothing for custom types — keep the result in your own module |
@@ -187,13 +187,22 @@ which is how a renderable knows its bounds went stale.
187
187
 
188
188
  ```js
189
189
  v.setCallback(() => { ... });
190
- v.setMuted(true); // write without firing it
190
+ v.setMuted(x, y, z); // write without firing it
191
+ v.setCallback(undefined); // stop observing
191
192
  ```
192
193
 
193
194
  **`setMuted` is what you want when writing several components**, so a
194
195
  half-updated vector does not fire a callback that reads the other half.
195
196
  `set()` already does this internally: it disables the callback, writes all
196
- three, fires once, and re-enables.
197
+ three, fires once, and re-enables. Every mutating method (`add`, `scale`,
198
+ `negateSelf`, `floorSelf`, `clampSelf`, `normalize`, `lerp`, …) goes through
199
+ `set()`, so it fires once and returns the observable vector itself. Before
200
+ 20.13 `negateSelf`, `floorSelf`, `ceilSelf` and `clampSelf` (and the 3D
201
+ `normalize` and `perp`) fired once per component and returned the wrapped
202
+ plain vector instead.
203
+
204
+ `clone()` takes from a pool, and gets the callback you pass it or none: before
205
+ 20.13 a recycled clone kept the callback of the vector released before it.
197
206
 
198
207
  Note the observable `set(x = 0, y = 0, z = 0)` defaults EVERY argument, so
199
208
  `set(10, 20)` zeroes z exactly as the plain vector does.
@@ -286,7 +295,7 @@ math.degToRad(deg); math.radToDeg(rad);
286
295
  math.random(lo, hi); // integer
287
296
  math.randomFloat(lo, hi);
288
297
  math.weightedRandom(lo, hi); // integer, biased toward LO
289
- math.round(n, decimals);
298
+ math.round(n, decimals); // nearest; a tie goes up, as Math.round: -2.5 → -2
290
299
  math.pow(n); // n squared
291
300
  math.DEG_TO_RAD math.RAD_TO_DEG math.TAU math.ETA math.EPSILON
292
301
  ```
@@ -317,6 +326,8 @@ and `Math.random()` every frame jitters instead of drifting.
317
326
  | z unchanged after scaling | `scale(s)` leaves z alone; pass `scale(s, s, s)` |
318
327
  | z became 0 for no reason | `set(x, y)` defaults z to 0 |
319
328
  | the original vector changed | the method mutates; `copy()` into something first |
329
+ | a position callback fires twice and sees a half-updated vector, or `pos.floorSelf()` returns something that is not `pos` | before 20.13 the observable `negateSelf` / `floorSelf` / `ceilSelf` / `clampSelf` wrote component by component; upgrade |
330
+ | `math.round(-2.7)` is -2, or a large value comes back negative | before 20.13 `round` truncated toward zero through 32-bit integers; upgrade |
320
331
  | memory climbs in a loop | `clone()` or `negate()`/`floor()`/`ceil()` per frame |
321
332
  | two helper results are the same object | a shared scratch vector was returned |
322
333
  | `.z` is a type error | `Renderable.pos` is declared `Vector2d` (#1510) |