melonjs 20.12.0 → 20.14.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 (185) hide show
  1. package/README.md +66 -22
  2. package/build/application/application.d.ts +6 -1
  3. package/build/application/application.d.ts.map +1 -1
  4. package/build/application/header.d.ts.map +1 -1
  5. package/build/application/resize.d.ts.map +1 -1
  6. package/build/application/settings.d.ts +13 -8
  7. package/build/application/settings.d.ts.map +1 -1
  8. package/build/audio/audio.d.ts +8 -4
  9. package/build/audio/audio.d.ts.map +1 -1
  10. package/build/audio/backend/engine.d.ts.map +1 -1
  11. package/build/audio/playback.d.ts +2 -2
  12. package/build/audio/playback.d.ts.map +1 -1
  13. package/build/audio/spatial.d.ts.map +1 -1
  14. package/build/audio/types.d.ts +15 -5
  15. package/build/audio/types.d.ts.map +1 -1
  16. package/build/camera/camera2d.d.ts +12 -9
  17. package/build/camera/camera2d.d.ts.map +1 -1
  18. package/build/camera/camera3d.d.ts +11 -8
  19. package/build/camera/camera3d.d.ts.map +1 -1
  20. package/build/camera/effects/fade_effect.d.ts.map +1 -1
  21. package/build/geometries/ellipse.d.ts +3 -0
  22. package/build/geometries/ellipse.d.ts.map +1 -1
  23. package/build/geometries/line.d.ts.map +1 -1
  24. package/build/geometries/path2d.d.ts +9 -8
  25. package/build/geometries/path2d.d.ts.map +1 -1
  26. package/build/geometries/polygon.d.ts +2 -1
  27. package/build/geometries/polygon.d.ts.map +1 -1
  28. package/build/index.d.ts +14 -3
  29. package/build/index.d.ts.map +1 -1
  30. package/build/index.js +50215 -45051
  31. package/build/index.js.map +4 -4
  32. package/build/input/gamepad.d.ts.map +1 -1
  33. package/build/input/keyboard.d.ts.map +1 -1
  34. package/build/input/pointerevent.d.ts.map +1 -1
  35. package/build/level/gltf/GLTFModel.d.ts.map +1 -1
  36. package/build/level/level.d.ts.map +1 -1
  37. package/build/level/tiled/TMXLayer.d.ts +50 -15
  38. package/build/level/tiled/TMXLayer.d.ts.map +1 -1
  39. package/build/level/tiled/TMXTileMap.d.ts +14 -4
  40. package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
  41. package/build/level/tiled/TMXTileset.d.ts.map +1 -1
  42. package/build/level/tiled/TMXUtils.d.ts.map +1 -1
  43. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
  44. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
  45. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +1 -0
  46. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
  47. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
  48. package/build/level/tiled/renderer/TMXRenderer.d.ts +7 -3
  49. package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
  50. package/build/level/tiled/renderer/TMXStaggeredRenderer.d.ts.map +1 -1
  51. package/build/lighting/light3d.d.ts +55 -0
  52. package/build/lighting/light3d.d.ts.map +1 -1
  53. package/build/loader/cache.d.ts +146 -97
  54. package/build/loader/cache.d.ts.map +1 -1
  55. package/build/loader/internal-types.d.ts +69 -0
  56. package/build/loader/internal-types.d.ts.map +1 -0
  57. package/build/loader/loader.d.ts +589 -253
  58. package/build/loader/loader.d.ts.map +1 -1
  59. package/build/loader/parsers/audio.d.ts.map +1 -1
  60. package/build/loader/parsers/fontface.d.ts.map +1 -1
  61. package/build/loader/parsers/gltf.d.ts.map +1 -1
  62. package/build/loader/parsers/tmx.d.ts.map +1 -1
  63. package/build/math/color.d.ts +4 -0
  64. package/build/math/color.d.ts.map +1 -1
  65. package/build/math/math.d.ts +6 -1
  66. package/build/math/math.d.ts.map +1 -1
  67. package/build/math/noise.d.ts +16 -16
  68. package/build/math/noise.d.ts.map +1 -1
  69. package/build/math/observableVector2d.d.ts +6 -6
  70. package/build/math/observableVector2d.d.ts.map +1 -1
  71. package/build/math/observableVector3d.d.ts +8 -8
  72. package/build/math/observableVector3d.d.ts.map +1 -1
  73. package/build/particles/emitter.d.ts +15 -3
  74. package/build/particles/emitter.d.ts.map +1 -1
  75. package/build/particles/particle.d.ts +6 -0
  76. package/build/particles/particle.d.ts.map +1 -1
  77. package/build/particles/settings.d.ts +22 -5
  78. package/build/particles/settings.d.ts.map +1 -1
  79. package/build/physics/adapter.d.ts +64 -25
  80. package/build/physics/adapter.d.ts.map +1 -1
  81. package/build/physics/builtin/body.d.ts +23 -4
  82. package/build/physics/builtin/body.d.ts.map +1 -1
  83. package/build/physics/builtin/builtin-adapter.d.ts +25 -3
  84. package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
  85. package/build/physics/builtin/detector.d.ts.map +1 -1
  86. package/build/physics/builtin/raycast.d.ts.map +1 -1
  87. package/build/physics/world.d.ts +40 -10
  88. package/build/physics/world.d.ts.map +1 -1
  89. package/build/renderable/container.d.ts +41 -6
  90. package/build/renderable/container.d.ts.map +1 -1
  91. package/build/renderable/draggable.d.ts.map +1 -1
  92. package/build/renderable/frameAnimation.d.ts.map +1 -1
  93. package/build/renderable/instanced_mesh.d.ts +16 -14
  94. package/build/renderable/instanced_mesh.d.ts.map +1 -1
  95. package/build/renderable/mesh.d.ts +62 -5
  96. package/build/renderable/mesh.d.ts.map +1 -1
  97. package/build/renderable/renderable.d.ts.map +1 -1
  98. package/build/renderable/sprite.d.ts +4 -2
  99. package/build/renderable/sprite.d.ts.map +1 -1
  100. package/build/renderable/text/bitmaptext.d.ts +12 -3
  101. package/build/renderable/text/bitmaptext.d.ts.map +1 -1
  102. package/build/renderable/text/text.d.ts +147 -4
  103. package/build/renderable/text/text.d.ts.map +1 -1
  104. package/build/renderable/text/textmetrics.d.ts +10 -2
  105. package/build/renderable/text/textmetrics.d.ts.map +1 -1
  106. package/build/renderable/text/textstyle.d.ts.map +1 -1
  107. package/build/renderable/trigger.d.ts +5 -0
  108. package/build/renderable/trigger.d.ts.map +1 -1
  109. package/build/renderable/ui/panelpaint.d.ts +5 -1
  110. package/build/renderable/ui/panelpaint.d.ts.map +1 -1
  111. package/build/renderable/ui/progressbar.d.ts +267 -95
  112. package/build/renderable/ui/progressbar.d.ts.map +1 -1
  113. package/build/renderable/ui/uibaseelement.d.ts +443 -50
  114. package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
  115. package/build/renderable/ui/uicheckbox.d.ts +383 -0
  116. package/build/renderable/ui/uicheckbox.d.ts.map +1 -0
  117. package/build/renderable/ui/uifocus.d.ts +6 -0
  118. package/build/renderable/ui/uifocus.d.ts.map +1 -0
  119. package/build/renderable/ui/uilabel.d.ts +31 -0
  120. package/build/renderable/ui/uilabel.d.ts.map +1 -0
  121. package/build/renderable/ui/uipanel.d.ts +589 -116
  122. package/build/renderable/ui/uipanel.d.ts.map +1 -1
  123. package/build/renderable/ui/uiskin.d.ts +25 -0
  124. package/build/renderable/ui/uiskin.d.ts.map +1 -0
  125. package/build/renderable/ui/uislider.d.ts +518 -0
  126. package/build/renderable/ui/uislider.d.ts.map +1 -0
  127. package/build/renderable/ui/uispriteelement.d.ts +161 -85
  128. package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
  129. package/build/renderable/ui/uitextbutton.d.ts +369 -63
  130. package/build/renderable/ui/uitextbutton.d.ts.map +1 -1
  131. package/build/state/stage.d.ts.map +1 -1
  132. package/build/system/device.d.ts +11 -2
  133. package/build/system/device.d.ts.map +1 -1
  134. package/build/system/fullscreen.d.ts.map +1 -1
  135. package/build/system/pool.d.ts.map +1 -1
  136. package/build/system/save.d.ts +2 -1
  137. package/build/system/save.d.ts.map +1 -1
  138. package/build/tweens/tween.d.ts +5 -2
  139. package/build/tweens/tween.d.ts.map +1 -1
  140. package/build/utils/fetchdata.d.ts.map +1 -1
  141. package/build/utils/utils.d.ts.map +1 -1
  142. package/build/video/canvas/canvas_renderer.d.ts +16 -19
  143. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  144. package/build/video/canvas_factory.d.ts.map +1 -1
  145. package/build/video/effects/shadereffect.d.ts +127 -0
  146. package/build/video/effects/shadereffect.d.ts.map +1 -1
  147. package/build/video/renderer.d.ts +20 -4
  148. package/build/video/renderer.d.ts.map +1 -1
  149. package/build/video/renderstate.d.ts +26 -3
  150. package/build/video/renderstate.d.ts.map +1 -1
  151. package/build/video/utils/tessellation.d.ts +11 -0
  152. package/build/video/utils/tessellation.d.ts.map +1 -1
  153. package/build/video/webgl/batchers/lit_mesh_batcher.d.ts.map +1 -1
  154. package/build/video/webgl/webgl_renderer.d.ts +3 -6
  155. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  156. package/build/video/webgpu/batchers/lit_mesh_batcher.d.ts.map +1 -1
  157. package/build/video/webgpu/batchers/primitive_batcher.d.ts.map +1 -1
  158. package/build/video/webgpu/webgpu_renderer.d.ts +3 -6
  159. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  160. package/package.json +1 -1
  161. package/skills/melonjs/SKILL.md +41 -17
  162. package/skills/melonjs-20-migration/SKILL.md +50 -7
  163. package/skills/melonjs-3d/SKILL.md +138 -38
  164. package/skills/melonjs-3d-assets/SKILL.md +83 -33
  165. package/skills/melonjs-audio/SKILL.md +62 -19
  166. package/skills/melonjs-camera-and-drawing/SKILL.md +135 -8
  167. package/skills/melonjs-deployment/SKILL.md +92 -14
  168. package/skills/melonjs-display-and-scaling/SKILL.md +8 -3
  169. package/skills/melonjs-effects-and-shaders/SKILL.md +47 -15
  170. package/skills/melonjs-events/SKILL.md +6 -3
  171. package/skills/melonjs-getting-started/SKILL.md +6 -2
  172. package/skills/melonjs-input/SKILL.md +102 -16
  173. package/skills/melonjs-lighting/SKILL.md +13 -1
  174. package/skills/melonjs-loading-assets/SKILL.md +255 -39
  175. package/skills/melonjs-math/SKILL.md +26 -8
  176. package/skills/melonjs-particles-and-trails/SKILL.md +77 -9
  177. package/skills/melonjs-performance/SKILL.md +18 -8
  178. package/skills/melonjs-physics/SKILL.md +435 -128
  179. package/skills/melonjs-plugins/SKILL.md +50 -11
  180. package/skills/melonjs-renderables/SKILL.md +31 -7
  181. package/skills/melonjs-renderer-backends/SKILL.md +38 -17
  182. package/skills/melonjs-scenes-and-state/SKILL.md +45 -5
  183. package/skills/melonjs-sprites-and-animation/SKILL.md +37 -2
  184. package/skills/melonjs-tilemaps/SKILL.md +149 -17
  185. package/skills/melonjs-ui-and-text/SKILL.md +674 -42
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-loading-assets
3
- description: "Use this skill for loading and managing game assets in melonJS — the resource descriptor format, the full asset type list, preload versus load, retrieving loaded assets, base URLs and cross-origin settings, and the loading screen. Triggers on: loader, loader.preload, loader.load, resources, getImage, getJSON, getTMX, getGLTF, getVideo, getFont, setBaseURL, setOptions, crossOrigin, withCredentials, credentials, cookies, authenticated assets, CORS, asset, preload, LOADER_COMPLETE, loading screen, unload, setParser, custom asset type, generated texture, procedural texture."
3
+ description: "Use this skill for loading and managing game assets in melonJS — the resource descriptor format, the full asset type list, organising a multi-level game's assets as bundles (an assets file exporting common plus one bundle per level, passed to loader.preload), loading and freeing a level's bundle, preload versus load, retrieving loaded assets, base URLs and cross-origin settings, and the loading screen. Triggers on: loader, loader.preload, loader.load, resources, resources.js, assets file, getImage, getJSON, getTMX, getGLTF, getVideo, getFont, setBaseURL, setOptions, crossOrigin, withCredentials, credentials, cookies, authenticated assets, CORS, asset, preload, LOADER_COMPLETE, loading screen, unload, unloadAll, bundle, asset bundle, setBundle, getBundle, isLoaded, several levels, per-level assets, free memory between levels, setParser, custom asset type, generated texture, procedural texture."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -8,28 +8,13 @@ license: MIT
8
8
 
9
9
  ## The resource descriptor
10
10
 
11
- Every asset is `{ name, type, src }` plus type-specific extras:
11
+ Every asset is `{ name, type, src }` plus type-specific extras. The name is
12
+ what the asset is read back by; the type picks the parser. Type and name
13
+ together identify an asset, so an atlas's image and its JSON share a name:
12
14
 
13
15
  ```js
14
- import { loader } from "melonjs";
15
-
16
- loader.setOptions({ crossOrigin: "anonymous" }); // needed for remote assets
17
-
18
- await loader.preload([
19
- { name: "tiles", type: "image", src: "data/img/tiles.png" },
20
- { name: "map1", type: "tmx", src: "data/map/map1.tmx" },
21
- { name: "tileset", type: "tsx", src: "data/map/tileset.tsx" },
22
- { name: "sfx", type: "audio", src: "data/sfx/" },
23
- { name: "font", type: "fontface", src: "data/font/PressStart2P.ttf" },
24
- { name: "atlas", type: "json", src: "data/img/atlas.json" },
25
- ]);
26
- ```
27
-
28
- `preload()` has been awaitable since 19.8. The callback form still works and
29
- takes a third argument to suppress the built-in loading screen:
30
-
31
- ```js
32
- loader.preload(resources, onLoaded, /* switchToLoadState */ false);
16
+ { name: "texture", type: "image", src: "data/img/texture.png" }
17
+ { name: "texture", type: "json", src: "data/img/texture.json" }
33
18
  ```
34
19
 
35
20
  ## The type list
@@ -38,7 +23,7 @@ The type strings are melonJS-specific — this is not a generic loader:
38
23
 
39
24
  | type | for |
40
25
  |---|---|
41
- | `"image"` | textures, sprite sheets, normal maps |
26
+ | `"image"` | textures, sprite sheets, normal maps; `src` may be an array tried in order, such as compressed formats (`.ktx`, `.ktx2`, `.dds`, `.pvr`, `.pkm`) by priority with a PNG last |
42
27
  | `"json"` | data, texture-atlas descriptors, collision shapes from a shape editor |
43
28
  | `"tmx"` / `"tsx"` | Tiled maps and external tilesets |
44
29
  | `"audio"` | sound — **`src` is a directory**, see below |
@@ -52,6 +37,128 @@ The type strings are melonJS-specific — this is not a generic loader:
52
37
  | `"js"` | scripts |
53
38
  | any type you register | your own formats, and things you generate rather than download — see [Custom asset types](#custom-asset-types) |
54
39
 
40
+ ## Organise the assets: `common` plus one bundle per level
41
+
42
+ For any game with **more than one level or screen**, the assets file exports a
43
+ **record of bundles**: what every level uses under `common`, and each level's
44
+ own assets in a bundle of its own. The boot call takes the record as it is:
45
+
46
+ ```js
47
+ // resources.js
48
+ export const resources = {
49
+ common: [ // everything every level uses
50
+ { name: "player", type: "image", src: "data/img/player.png" },
51
+ { name: "tileset", type: "image", src: "data/img/tileset.png" },
52
+ { name: "tileset", type: "tsx", src: "data/map/tileset.tsx" },
53
+ { name: "music", type: "audio", src: "data/bgm/" },
54
+ { name: "jump", type: "audio", src: "data/sfx/" },
55
+ ],
56
+ level1: [
57
+ { name: "level1", type: "tmx", src: "data/map/level1.tmx" },
58
+ { name: "hills", type: "image", src: "data/img/hills.png" },
59
+ ],
60
+ level2: [
61
+ { name: "level2", type: "tmx", src: "data/map/level2.tmx" },
62
+ { name: "hills", type: "image", src: "data/img/hills.png" }, // shared: listed again
63
+ { name: "lava", type: "image", src: "data/img/lava.png" },
64
+ ],
65
+ };
66
+ ```
67
+
68
+ ```js
69
+ // game.js
70
+ import { audio, loader, state } from "melonjs";
71
+ import { resources } from "./resources.js";
72
+
73
+ audio.init("mp3,ogg"); // before any audio is loaded
74
+ await loader.preload(resources); // every bundle, behind the loading screen
75
+ state.change(state.PLAY);
76
+ ```
77
+
78
+ `preload(record)` declares each key as a bundle and loads them all, with one
79
+ progress bar; an asset several bundles list is fetched once. Nothing else
80
+ changes: a bundle is a lifetime, not a namespace, so assets keep their own
81
+ names, and `getImage("hills")`, `new Sprite(x, y, { image: "player" })` and
82
+ `level.load("level1")` work as with a flat list. What bundles add is that a
83
+ level can later be freed and loaded again **by its id** (next section). Starting
84
+ with bundles costs nothing even if the game never unloads anything.
85
+
86
+ - **Name a level's bundle after its map.** Bundle ids and asset names are
87
+ separate, so `"level1"` can be both, and `level.load(id)` and
88
+ `loader.load(id)` then take the same string.
89
+ - **List a shared asset in every bundle that uses it.** It is still fetched
90
+ once, and it is what keeps the asset alive when one of those bundles is
91
+ unloaded.
92
+ - **A map's dependencies go with it**: its `.tsx` and tileset images in the
93
+ same bundle, or in `common`.
94
+ - **Effects that outlive a level go in `common`** (a HUD's or a camera's
95
+ shader): unloading the level's bundle would destroy them under whatever
96
+ still draws with them.
97
+
98
+ **A flat array is the simplest form**, and is fine for a single-screen game, a
99
+ prototype or a jam game. It works as it always has, and everything in it stays
100
+ in memory for the whole game:
101
+
102
+ ```js
103
+ await loader.preload([
104
+ { name: "tiles", type: "image", src: "data/img/tiles.png" },
105
+ { name: "map1", type: "tmx", src: "data/map/map1.tmx" },
106
+ { name: "sfx", type: "audio", src: "data/sfx/" },
107
+ ]);
108
+ ```
109
+
110
+ Moving to bundles later is a change to the assets file alone: wrap the list in
111
+ `{ common: [ … ] }` and split the levels out.
112
+
113
+ Either way, `preload()` can be awaited (since 19.8) or given a callback, and a
114
+ third argument suppresses the built-in loading screen:
115
+
116
+ ```js
117
+ loader.preload(resources, onLoaded, /* switchToLoadState */ false);
118
+ ```
119
+
120
+ ## Level by level: load before, unload after
121
+
122
+ With every bundle preloaded at boot, nothing more is needed to play. To keep
123
+ memory bounded in a game whose levels are many or large:
124
+
125
+ ```js
126
+ import { level, loader } from "melonjs";
127
+
128
+ // leaving level1 for good: load the next, switch, then free the previous
129
+ await loader.load("level2"); // fetches nothing if still in memory
130
+ await level.load("level2", { async: true });
131
+ loader.unload("level1"); // "hills" stays: level2 lists it too
132
+
133
+ // going back to a level that was unloaded
134
+ await loader.load("level1"); // fetches what unloading freed
135
+ await level.load("level1", { async: true });
136
+ ```
137
+
138
+ - **`await loader.load(id)` before entering a level that may not be in
139
+ memory.** When the bundle is still loaded it resolves at once with no
140
+ request, so it is cheap to call every time. It does not show the loading
141
+ screen (the game keeps running); use `loader.preload([id])` for that, or
142
+ `loader.isLoaded(id)` to decide.
143
+ - **`loader.unload(id)` for a level the player will not come back to soon,
144
+ after the next level is in.** It frees what no other loaded bundle lists.
145
+ Unloading the level being played leaves its world as it is, but with no
146
+ current level, so `level.reload()` has nothing to reload.
147
+
148
+ **When not to unload:**
149
+
150
+ - **Levels that loop back** — a hub, a retry after game over, a last level
151
+ whose exit leads to the first. Unloading one means fetching it again, and a
152
+ hitch, every time the player returns.
153
+ - **A small game.** If every level fits comfortably in memory, keep
154
+ everything loaded. The platformer example does exactly this: `resources.ts`
155
+ exports `{ common, map1, map2 }`, `preload(resources)` loads it all at boot,
156
+ and since map2's exit leads back to map1, nothing is ever unloaded.
157
+
158
+ Unload when the levels are large (big tilesets and backgrounds, per-level
159
+ music, 3D scenes), numerous, or linear (a level behind the player for good),
160
+ and on memory-constrained targets such as phones and mini-game platforms.
161
+
55
162
  ## What belongs in the manifest — and what cannot
56
163
 
57
164
  Preloading is not only for things with a URL. Three tiers, and the middle one
@@ -119,8 +226,8 @@ const app = new Application(1280, 720, {
119
226
 
120
227
  **You do not call this yourself.** `preload()` calls `renderer.prewarm()` once
121
228
  the assets are in and **before** `LOADER_COMPLETE` fires, so it happens while
122
- the progress bar is still up. Every `preload()` warms, not just the first, so a
123
- per-level preload warms whatever that level brought with it.
229
+ the progress bar is still up. Every `preload()` warms, not just the first, and
230
+ so does `load(id)`, so loading a level's bundle warms whatever it brought.
124
231
 
125
232
  The one precondition for this part: **a game that never calls
126
233
  `loader.preload()` does not get it**. If you load assets another way, call
@@ -349,15 +456,85 @@ event.on(event.LOADER_COMPLETE, () => { /* … */ });
349
456
  event.on(event.LOADER_ERROR, (res) => { /* … */ });
350
457
  ```
351
458
 
352
- ## Unloading
459
+ **Each `preload()` has only its own callback.** One given none calls nothing
460
+ on completion, so a later `await loader.preload(levelAssets)` does not re-run
461
+ the boot callback. Before 20.13 the loader kept the last callback it was given
462
+ and called it again for a preload that had none — usually a `state.change`
463
+ straight back to the first screen. `loader.reload(src)` after a failure
464
+ completes the preload that asset failed in, with that preload's callback, once
465
+ all of its assets are in.
466
+
467
+ **Progress is per preload:** `LOADER_PROGRESS` goes from that call's first
468
+ asset to `1` with its last, every time. A preload started while another is
469
+ still loading joins it, and the value covers both until everything in flight
470
+ has settled; the next preload starts from zero again. A failed asset keeps
471
+ its preload short of `1`, and only that preload.
472
+
473
+ ## Bundle and unloading reference
474
+
475
+ Bundles can also be declared one by one, which is what `preload(record)` does
476
+ for each key:
353
477
 
354
478
  ```js
355
- loader.unload({ name: "map1", type: "tmx" });
356
- loader.unloadAll();
479
+ loader.setBundle("level3", [
480
+ { name: "level3", type: "tmx", src: "data/map/level3.tmx" },
481
+ { name: "hills", type: "image", src: "data/img/hills.png" },
482
+ ]);
483
+ await loader.preload(["common", "level3"]); // ids and descriptors mix freely
357
484
  ```
358
485
 
359
- `unloadAll()` also releases audio and GPU resources such as shader programs, so
360
- it is the right teardown for switching between large levels.
486
+ - **Strings are bundles, objects are assets.** `loader.unload("map1")`
487
+ releases the bundle `"map1"`; `loader.unload({ name: "map1", type: "tmx" })`
488
+ frees that one asset at once, whoever holds it (the bundles listing it then
489
+ report `isLoaded(id) === false`, and loading one again fetches it again).
490
+ - **`load(id)`** goes through the same path as `preload()`: `LOADER_PROGRESS`
491
+ for the bundle's own assets, the shader warm-up, then `LOADER_COMPLETE`. It
492
+ does not switch to the loading screen, and always returns a promise
493
+ (`onload` / `onerror` callbacks are optional extras).
494
+ - **`load(id, assets)` declares and loads in one call** — `setBundle(id, assets)`
495
+ then `load(id)`. Called again with the same assets (type, name, src) it just
496
+ loads; with different ones it redefines the bundle, which throws while that
497
+ bundle is loaded. Handy where a level's assets are declared next to the code
498
+ that enters it.
499
+ - **`preload(record)` again** fetches nothing already in memory. A bundle
500
+ already declared with the same assets is left as it is. A single descriptor
501
+ passed by mistake throws: wrap it in an array.
502
+ - **Progress:** each asset of the bundle is one step; one already in memory
503
+ counts as a step done at once. An asset listed twice is one asset, and an
504
+ empty bundle is allowed (it is always loaded).
505
+ - **Nothing is fetched twice.** An asset already in memory is not requested
506
+ again, however it got there, and two bundles loading the same asset at once
507
+ share one request.
508
+ - **Ownership.** Each loaded bundle listing an asset holds it; `unload(id)`
509
+ frees the assets no other loaded bundle lists. An asset loaded the old way —
510
+ a flat `preload` list, or `load(asset)` — is **pinned**: no bundle unload
511
+ frees it, only `unload(asset)` or `unloadAll()`.
512
+ - **`isLoaded(id)`** is true when every asset of the bundle is in memory,
513
+ however it got there. `getBundle(id)` returns a copy of the list, or
514
+ `undefined` for an id never declared.
515
+ - **Freeing a map unregisters its level.** After `unload(id)`, `level.load` of
516
+ a map (or glTF scene) that bundle freed throws "level … not found" until the
517
+ bundle is loaded again, which registers it again.
518
+ - **Freeing releases the GPU side too:** an image's texture (since 20.14), a
519
+ shader's program, a font's place in `document.fonts`. `getShader()` hands out
520
+ the loader's shared instance and does not track who took it, hence
521
+ "effects that outlive a level go in `common`".
522
+ - **A failing asset rejects `load(id)`**, like `preload`; the assets that did
523
+ load stay, held by the bundle, and `loader.reload(src)` completes it.
524
+ - **Errors are synchronous.** An unknown id throws in `load`, `unload`,
525
+ `preload` and `isLoaded`. Redefining a bundle with `setBundle` throws while
526
+ it is loading or loaded: unload it first. A bundle lists descriptors only,
527
+ not other bundle ids.
528
+ - **`unloadAll()`** frees every asset, audio included, whoever holds it. The
529
+ bundles stay declared, all back to not loaded, ready to be loaded again.
530
+ Levels stay registered (unlike a bundle unload), so load a level's bundle
531
+ again before `level.load` of it.
532
+ - **`load(asset)`, with a descriptor**, loads one asset while the game runs:
533
+ no loading screen, progress or loader event, only its promise (or the
534
+ `onload` / `onerror` callbacks). It is pinned. One already in memory
535
+ resolves at once with no request (the callback form returns `0` and never
536
+ calls `onload`), except a `"binary"`, `"fontface"`, `"aseprite"` or `"js"`
537
+ asset, which is fetched again.
361
538
 
362
539
  ## Loading between scenes
363
540
 
@@ -377,20 +554,45 @@ state.change(state.PLAY, true);
377
554
 
378
555
  ## Cross-origin and authenticated assets
379
556
 
380
- Two settings, both global and both applying to **every** asset type — there is
381
- no per-asset or per-type spelling to remember:
557
+ **Nothing to set for the usual cases.** Every fetched asset (image, json,
558
+ binary, tmx, tsx, shader, gltf/glb, obj, mtl, aseprite, buffered audio) is
559
+ requested the same way:
560
+
561
+ - **On the game's own origin**, it carries the browser's cookies and HTTP
562
+ auth, as the page and its scripts do. A game behind a login or a session
563
+ cookie loads its own assets with no option.
564
+ - **On another origin** (a CDN), it is a CORS request: it loads when that
565
+ server sends `Access-Control-Allow-Origin`, and fails at load when not, with
566
+ an error naming the asset and the header it needs. No `crossOrigin` needed.
567
+
568
+ | asset on… | default | `withCredentials: true` |
569
+ |---|---|---|
570
+ | the game's own origin | loads, with its cookies | loads, with its cookies |
571
+ | 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` |
572
+ | another origin allowing credentials for the page | loads, no cookies | loads, with that origin's cookies |
573
+ | another origin with no CORS headers | fails at load, naming the asset | fails at load, naming the asset |
574
+
575
+ Two global settings cover the rest, for every asset type — there is no
576
+ per-asset or per-type spelling to remember:
382
577
 
383
578
  ```js
384
579
  loader.setOptions({
385
- crossOrigin: "anonymous", // CORS mode for remote assets
386
- withCredentials: true, // send cookies / auth with the request
580
+ withCredentials: true, // send cookies / auth to OTHER origins too
581
+ crossOrigin: "anonymous", // attribute of the <video>, <script> and glTF
582
+ // image elements the loader creates
387
583
  });
388
584
  ```
389
585
 
390
- `withCredentials` is what an asset behind a session cookie or an
391
- `Authorization`-style login needs. Every fetched asset — image, json, binary,
392
- tmx, tsx, shader, gltf/glb, obj, mtl, aseprite, video — carries it, and so does
393
- buffered audio.
586
+ `withCredentials` is for assets on **another** origin behind a session cookie
587
+ or a login; that server must then allow credentials for the page's origin, or
588
+ the browser blocks the request. `crossOrigin: "use-credentials"` does the same
589
+ for fetched assets, and sets the attribute on the elements.
590
+
591
+ Before 20.13 both were needed far more often: assets on the game's own origin
592
+ went out with no cookies unless `withCredentials` was set (a 401 or 403 behind
593
+ a login), and an asset on another origin without `crossOrigin: "anonymous"`
594
+ was read as an empty response, failing later with "Unexpected end of input" or
595
+ "The source image could not be decoded" even when its server allowed it.
394
596
 
395
597
  **Set them through `setOptions`.** `loader.crossOrigin` and
396
598
  `loader.withCredentials` are read-only module bindings; assigning to them
@@ -408,16 +610,30 @@ throws, the same trap as the removed `loader.onload` / `onProgress`.
408
610
  | audio 404s | `src` given as a file instead of a directory |
409
611
  | audio fails, `preload()` rejects with "Failed loading resource" | `audio.init()` not called before preloading — the real message is replaced by the loader's |
410
612
  | blank level, missing tiles | `.tsx` or tileset image not listed in the resources |
411
- | cross-origin textures fail | missing `loader.setOptions({ crossOrigin: "anonymous" })` |
412
- | assets behind a login 401 / audio preload hangs | missing `loader.setOptions({ withCredentials: true })` — one switch, all asset types |
613
+ | `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 |
614
+ | 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) |
615
+ | 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 |
413
616
  | `TypeError` assigning `loader.withCredentials` / `crossOrigin` | read-only module bindings; use `loader.setOptions({ … })` |
414
617
  | preload never finishes, blank screen, no error | a failing sound with `audio.setStopOnAudioError(false)` before 20.4 — it disabled audio *and* failed the preload |
415
618
  | text renders in a fallback font | web font not preloaded as `"fontface"` |
416
619
  | `BitmapText` renders nothing | the `.fnt` (as `"binary"`) or its image is missing |
417
620
  | `TypeError` assigning `loader.onProgress` / `onload` | removed in 20.3; they were never writable — use the `LOADER_*` events |
621
+ | `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 |
622
+ | 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 |
623
+ | `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 |
418
624
  | `load : unknown or invalid resource type` | a custom type in the manifest before its `loader.setParser` call |
419
625
  | 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) |
420
626
  | 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 |
627
+ | `level X not found` after switching levels with bundles | that map's bundle was unloaded (it unregisters the level), or never loaded: `await loader.load(id)` before `level.load` |
628
+ | `loader: unknown bundle "…"` | a typo, or `setBundle` not called yet; ids and asset names are separate, a bundle is not found by an asset's name |
629
+ | `setBundle(…): the bundle is loaded` | redefining a loaded bundle, by `setBundle` or by `load(id, assets)` with a different list: `loader.unload(id)` first |
630
+ | a shared image disappears (blank layer) after unloading the previous level's bundle | the next level's bundle does not list it: list a shared asset in every bundle that uses it |
631
+ | an asset survives `unload(id)` and memory stays up | it was also loaded the old way (a flat `preload` list, or `load(asset)`), which pins it; load it only through bundles, or `unload(asset)` |
632
+ | a HUD or camera effect goes blank, or throws, after a level's bundle is unloaded | its shader came from that bundle; effects that outlive a level belong in a bundle that outlives it (`"common"`) |
633
+ | `level.reload()` throws `level undefined not found` | the current level's own bundle was unloaded; unload the previous level's bundle after the next level is in |
634
+ | a pause and new network requests every time the player goes back to an earlier level | that level's bundle is unloaded on leaving it although the game loops back to it; keep it loaded (see [When not to unload](#level-by-level-load-before-unload-after)) |
635
+ | memory grows level after level although assets are unloaded (before 20.14) | unloading an image left its GPU texture in the renderer; update |
636
+ | an asset given as a `data:` URI fails, or the default loading screen shows no logo, in a sandboxed embed or under a strict Content-Security-Policy (before 20.11) | the `data:` URI was fetched, which a `connect-src` without `data:` refuses; update, it is decoded in place |
421
637
 
422
638
  ## Related skills
423
639
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-math
3
- description: "Use this skill for vector and matrix maths in melonJS — Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z., moveTowards ignores z, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix, Noise, getNoise1d, flicker, wobble."
3
+ description: "Use this skill for vector and matrix maths in melonJS — Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z, moveTowards ignores z, setPosition, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix, Noise, getNoise1d, flicker, wobble."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -97,6 +97,9 @@ v.scale(2, 2, 2); // (1,2,3) -> (2,4,6) what you meant
97
97
  v.set(10, 20); // z is now 0, whatever it was
98
98
  ```
99
99
 
100
+ To move a renderable, `setPosition(x, y)` (20.8) is the call that keeps its
101
+ depth: leave out `z` and it stays as it was, where `pos.set(x, y)` zeroes it.
102
+
100
103
  **`Vector3d.pos` on a renderable is typed `Vector2d`.** `Renderable.pos` is an
101
104
  `ObservableVector3d` at run time but is declared through `Polygon` as a
102
105
  `Vector2d`, so `.z`, `.setMuted` and `.setCallback` need a cast from
@@ -117,7 +120,7 @@ The 2D one is the z component of the 3D cross, which is the useful scalar for
117
120
  ```js
118
121
  a.lerp(b, t); // straight line, t in 0..1
119
122
  a.slerp(b, t); // along the ARC, constant angular rate
120
- a.damp(b, lambda, dt); // frame-rate independent ease toward b
123
+ a.damp(b, lambda, dt); // frame-rate independent ease toward b (dt in seconds)
121
124
  a.moveTowards(b, step); // fixed distance per call, never overshoots
122
125
  ```
123
126
 
@@ -187,13 +190,22 @@ which is how a renderable knows its bounds went stale.
187
190
 
188
191
  ```js
189
192
  v.setCallback(() => { ... });
190
- v.setMuted(true); // write without firing it
193
+ v.setMuted(x, y, z); // write without firing it
194
+ v.setCallback(undefined); // stop observing
191
195
  ```
192
196
 
193
197
  **`setMuted` is what you want when writing several components**, so a
194
198
  half-updated vector does not fire a callback that reads the other half.
195
199
  `set()` already does this internally: it disables the callback, writes all
196
- three, fires once, and re-enables.
200
+ three, fires once, and re-enables. Every mutating method (`add`, `scale`,
201
+ `negateSelf`, `floorSelf`, `clampSelf`, `normalize`, `lerp`, …) goes through
202
+ `set()`, so it fires once and returns the observable vector itself. Before
203
+ 20.13 `negateSelf`, `floorSelf`, `ceilSelf` and `clampSelf` (and the 3D
204
+ `normalize` and `perp`) fired once per component and returned the wrapped
205
+ plain vector instead.
206
+
207
+ `clone()` takes from a pool, and gets the callback you pass it or none: before
208
+ 20.13 a recycled clone kept the callback of the vector released before it.
197
209
 
198
210
  Note the observable `set(x = 0, y = 0, z = 0)` defaults EVERY argument, so
199
211
  `set(10, 20)` zeroes z exactly as the plain vector does.
@@ -283,11 +295,13 @@ math.clamp(v, lo, hi);
283
295
  math.lerp(a, b, t); // scalars
284
296
  math.damp(a, b, lambda, dt); // frame-rate independent
285
297
  math.degToRad(deg); math.radToDeg(rad);
286
- math.random(lo, hi); // integer
298
+ math.random(lo, hi); // integer, hi EXCLUDED: random(1, 6) never gives 6
287
299
  math.randomFloat(lo, hi);
288
- math.weightedRandom(lo, hi); // integer, biased toward LO
289
- math.round(n, decimals);
300
+ math.weightedRandom(lo, hi); // integer, biased toward LO, hi excluded
301
+ math.round(n, decimals); // nearest; a tie goes up, as Math.round: -2.5 → -2
290
302
  math.pow(n); // n squared
303
+ math.lerpArray([1, 0.5, 0], t); // a value along evenly spaced samples, t in 0..1
304
+ math.isPowerOfTwo(n); math.nextPowerOfTwo(n);
291
305
  math.DEG_TO_RAD math.RAD_TO_DEG math.TAU math.ETA math.EPSILON
292
306
  ```
293
307
 
@@ -315,9 +329,13 @@ and `Math.random()` every frame jitters instead of drifting.
315
329
  | symptom | cause |
316
330
  |---|---|
317
331
  | z unchanged after scaling | `scale(s)` leaves z alone; pass `scale(s, s, s)` |
318
- | z became 0 for no reason | `set(x, y)` defaults z to 0 |
332
+ | z became 0 for no reason | `set(x, y)` defaults z to 0; move a renderable with `setPosition(x, y)` |
319
333
  | the original vector changed | the method mutates; `copy()` into something first |
334
+ | 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 |
335
+ | `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 |
336
+ | `math.isPowerOfTwo(0)` is true, or a fraction passes | before 20.13 it accepted zero, negative and fractional values; upgrade |
320
337
  | memory climbs in a loop | `clone()` or `negate()`/`floor()`/`ceil()` per frame |
338
+ | memory taken by dropped `clone()`s never comes back | before 20.9 the pool kept every instance it handed out; upgrade, and release what you clone |
321
339
  | two helper results are the same object | a shared scratch vector was returned |
322
340
  | `.z` is a type error | `Renderable.pos` is declared `Vector2d` (#1510) |
323
341
  | rotation eases oddly between directions | `lerp` on directions; use `slerp` |
@@ -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, stopStream, autoDestroyOnComplete, onComplete, sprite sheet particles, framewidth, randomFrame, cone, sphere, 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,51 @@ 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` (deprecated since 20.2, and does nothing), and a
68
+ `Container` as `referenceSpace` (it offers
69
+ `"local"` and `"world"`). `textureAdditive` is left out: `blendMode`
70
+ replaces it.
71
+ - **Changes at run time.** Calling `burstParticles()` when something
72
+ happens, or assigning `emitter.blendMode` while particles are alive, is
73
+ game code. Its "moving emitter" option previews the common case: an
74
+ emitter attached to something that moves.
75
+
31
76
  ## Sprite-sheet particles
32
77
 
33
78
  Use `framewidth` and `frameheight` (the same names as on `Sprite`) to divide
@@ -107,15 +152,33 @@ fountain and wrong for an explosion: additive sprites piled on one point clip
107
152
  all three channels there however well they are tinted, so a burst reads as one
108
153
  bright flash rather than as debris.
109
154
 
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:
155
+ Give it the size of whatever broke. The constructor centres the area on the
156
+ point it is given, so pass the centre of the source:
113
157
 
114
158
  ```js
115
159
  const area = radius * 2;
116
- const emitter = new ParticleEmitter(x - area / 2, y - area / 2, {
117
- width: area, height: area,
118
- });
160
+ const emitter = new ParticleEmitter(x, y, { width: area, height: area });
161
+ ```
162
+
163
+ After that, `pos` is the area's top-left corner, as on any renderable. To move
164
+ the emitter to a new source later, offset by half, or the burst sits down and
165
+ to the right of it:
166
+
167
+ ```js
168
+ emitter.pos.set(x - area / 2, y - area / 2); // re-aimed at (x, y)
169
+ emitter.burstParticles();
170
+ ```
171
+
172
+ Under a `Camera3d`, `depth` (20.13) gives the area its third side: each
173
+ particle is born at a random depth within it, centered on the emitter's own
174
+ depth, unlike `width` and `height`. Without it every particle starts on one
175
+ plane facing the camera, so rain or dust seen from the side is a flat sheet. A
176
+ 2D camera does not show depth, though with an order-dependent blend the
177
+ particles are still sorted by it. It is the spawn box's size, not the
178
+ emitter's `depth` property, which is where the emitter sits along Z.
179
+
180
+ ```js
181
+ new ParticleEmitter(x, y, { width: 600, height: 20, depth: 400 }); // a volume of rain
119
182
  ```
120
183
 
121
184
  ## Particles in 3D
@@ -196,7 +259,9 @@ debris.
196
259
 
197
260
  For one-shot effects, `autoDestroyOnComplete: true` removes the emitter once its
198
261
  particles have finished. The `onComplete` callback fires at that same moment
199
- either way.
262
+ either way. A stream finishes only once it stops emitting: one left on the
263
+ default `duration` (`Infinity`) never completes until you call `stopStream()`,
264
+ so give a stream that should clean up after itself a `duration`.
200
265
 
201
266
  For repeated effects (muzzle flashes, impacts) keep **one** long-lived emitter
202
267
  and re-aim it per shot, rather than allocating an emitter per event.
@@ -321,6 +386,7 @@ answer when you want discrete puffs rather than a continuous ribbon.
321
386
  |---|---|
322
387
  | trail looks flat under a `Camera3d` | `Trail` is 2D; use `Trail3d` |
323
388
  | 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`) |
389
+ | 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
390
  | `Trail3d` draws nothing, with a console warning | it needs a `Camera3d`; the app's `cameraClass` is still the 2D default |
325
391
  | `trail.tint` does nothing | `Trail` cannot be tinted at all; `Trail3d` can |
326
392
  | assigning `maxPoints` throws | `Trail3d` sizes its geometry once; pass `length` to the constructor |
@@ -329,13 +395,15 @@ answer when you want discrete puffs rather than a continuous ribbon.
329
395
  | the cloud follows a moving emitter | default `referenceSpace: "local"` — use `"world"` for trails |
330
396
  | particles do not travel far enough | settings tuned pre-20.2, when drawn travel was doubled |
331
397
  | one-shot emitters accumulate | missing `autoDestroyOnComplete: true` |
398
+ | a streaming emitter with `autoDestroyOnComplete` never goes away | the stream runs on the default `duration` of `Infinity`: give it a `duration`, or call `stopStream()` |
332
399
  | every particle of an emitter is the same colour all its life | `tint` is one value for the emitter: use `tintOverLife` for a colour that changes as it ages |
333
400
  | a `tintOverLife` colour's transparency has no effect | the list sets the colour only; opacity is the particle's fade |
334
401
  | `tintOverLife` colours look muddy | the texture is coloured; author it white so the tints come through as given |
335
402
  | frame rate drops with many effects | an emitter per event instead of one re-aimed emitter |
336
403
  | a 3D burst is invisible under a `Camera3d` | before 20.8 a particle was culled at twice its depth; update |
337
404
  | 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 |
405
+ | 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) |
406
+ | 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
407
  | the middle of a burst goes white | additive sprites overlapping — `textureAdditive: false`, or spread them |
340
408
  | debris chunks look like squares | the default sprite is a filled square; pass your own `image` |
341
409
  | a ring tilts with the camera instead of staying on a surface | the launch axis is in WORLD axes, not the camera basis |