melonjs 20.7.0 → 20.9.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 (332) hide show
  1. package/README.md +7 -3
  2. package/build/application/application.d.ts +66 -8
  3. package/build/application/application.d.ts.map +1 -1
  4. package/build/application/defaultApplicationSettings.d.ts +6 -0
  5. package/build/application/defaultApplicationSettings.d.ts.map +1 -1
  6. package/build/application/header.d.ts.map +1 -1
  7. package/build/application/resize.d.ts +25 -0
  8. package/build/application/resize.d.ts.map +1 -1
  9. package/build/application/scaleMethods.d.ts +13 -3
  10. package/build/application/scaleMethods.d.ts.map +1 -1
  11. package/build/application/settings.d.ts +144 -5
  12. package/build/application/settings.d.ts.map +1 -1
  13. package/build/audio/audio.d.ts +29 -5
  14. package/build/audio/audio.d.ts.map +1 -1
  15. package/build/audio/autopause.d.ts +36 -0
  16. package/build/audio/autopause.d.ts.map +1 -0
  17. package/build/audio/backend/engine.d.ts +10 -1
  18. package/build/audio/backend/engine.d.ts.map +1 -1
  19. package/build/audio/backend/sound.d.ts +0 -1
  20. package/build/audio/backend/sound.d.ts.map +1 -1
  21. package/build/audio/backend/spatial.d.ts.map +1 -1
  22. package/build/audio/context.d.ts +35 -0
  23. package/build/audio/context.d.ts.map +1 -0
  24. package/build/audio/playback.d.ts.map +1 -1
  25. package/build/audio/procedural.d.ts.map +1 -1
  26. package/build/audio/spatial.d.ts +87 -0
  27. package/build/audio/spatial.d.ts.map +1 -0
  28. package/build/audio/state.d.ts +1 -0
  29. package/build/audio/state.d.ts.map +1 -1
  30. package/build/audio/types.d.ts +46 -6
  31. package/build/audio/types.d.ts.map +1 -1
  32. package/build/camera/camera2d.d.ts.map +1 -1
  33. package/build/camera/camera3d.d.ts +95 -8
  34. package/build/camera/camera3d.d.ts.map +1 -1
  35. package/build/camera/effects/fade_effect.d.ts.map +1 -1
  36. package/build/camera/effects/mask_effect.d.ts.map +1 -1
  37. package/build/camera/effects/shake_effect.d.ts.map +1 -1
  38. package/build/geometries/box3d.d.ts +25 -7
  39. package/build/geometries/box3d.d.ts.map +1 -1
  40. package/build/geometries/ellipse.d.ts +15 -1
  41. package/build/geometries/ellipse.d.ts.map +1 -1
  42. package/build/geometries/line.d.ts +1 -1
  43. package/build/geometries/line.d.ts.map +1 -1
  44. package/build/geometries/point.d.ts +1 -1
  45. package/build/geometries/point.d.ts.map +1 -1
  46. package/build/geometries/polygon.d.ts +15 -1
  47. package/build/geometries/polygon.d.ts.map +1 -1
  48. package/build/geometries/rectangle.d.ts +1 -1
  49. package/build/geometries/rectangle.d.ts.map +1 -1
  50. package/build/geometries/roundrect.d.ts +1 -1
  51. package/build/geometries/roundrect.d.ts.map +1 -1
  52. package/build/geometries/sphere.d.ts +36 -7
  53. package/build/geometries/sphere.d.ts.map +1 -1
  54. package/build/index.d.ts +16 -3
  55. package/build/index.d.ts.map +1 -1
  56. package/build/index.js +38172 -33633
  57. package/build/index.js.map +4 -4
  58. package/build/input/gamepad.d.ts +129 -3
  59. package/build/input/gamepad.d.ts.map +1 -1
  60. package/build/input/pointerevent.d.ts +8 -0
  61. package/build/input/pointerevent.d.ts.map +1 -1
  62. package/build/level/gltf/GLTFModel.d.ts +20 -33
  63. package/build/level/gltf/GLTFModel.d.ts.map +1 -1
  64. package/build/level/gltf/GLTFScene.d.ts +40 -1
  65. package/build/level/gltf/GLTFScene.d.ts.map +1 -1
  66. package/build/level/level.d.ts +35 -0
  67. package/build/level/level.d.ts.map +1 -1
  68. package/build/level/tiled/TMXLayer.d.ts +5 -2
  69. package/build/level/tiled/TMXLayer.d.ts.map +1 -1
  70. package/build/level/tiled/TMXObject.d.ts +0 -1
  71. package/build/level/tiled/TMXObjectFactory.d.ts +3 -2
  72. package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
  73. package/build/level/tiled/TMXTile.d.ts +2 -1
  74. package/build/level/tiled/TMXTile.d.ts.map +1 -1
  75. package/build/level/tiled/TMXTileMap.d.ts +7 -4
  76. package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
  77. package/build/level/tiled/TMXTileset.d.ts +1 -1
  78. package/build/level/tiled/TMXTileset.d.ts.map +1 -1
  79. package/build/level/tiled/TMXTilesetGroup.d.ts +5 -0
  80. package/build/level/tiled/TMXTilesetGroup.d.ts.map +1 -1
  81. package/build/level/tiled/factories/shape.d.ts +0 -1
  82. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +6 -5
  83. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
  84. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +5 -0
  85. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
  86. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +5 -7
  87. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
  88. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +5 -0
  89. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
  90. package/build/level/tiled/renderer/TMXRenderer.d.ts +19 -4
  91. package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
  92. package/build/level/tiled/renderer/autodetect.d.ts +0 -4
  93. package/build/level/tiled/renderer/autodetect.d.ts.map +1 -1
  94. package/build/loader/cache.d.ts +220 -0
  95. package/build/loader/cache.d.ts.map +1 -1
  96. package/build/loader/loader.d.ts +20 -340
  97. package/build/loader/loader.d.ts.map +1 -1
  98. package/build/loader/loadingscreen.d.ts +1 -2
  99. package/build/loader/loadingscreen.d.ts.map +1 -1
  100. package/build/loader/parsers/gltf.d.ts +115 -0
  101. package/build/loader/parsers/gltf.d.ts.map +1 -1
  102. package/build/loader/parsers/shader.d.ts +0 -2
  103. package/build/math/color.d.ts +28 -5
  104. package/build/math/color.d.ts.map +1 -1
  105. package/build/math/matrix2d.d.ts +1 -1
  106. package/build/math/matrix2d.d.ts.map +1 -1
  107. package/build/math/matrix3d.d.ts +1 -1
  108. package/build/math/matrix3d.d.ts.map +1 -1
  109. package/build/math/observableVector2d.d.ts +14 -2
  110. package/build/math/observableVector2d.d.ts.map +1 -1
  111. package/build/math/observableVector3d.d.ts +15 -1
  112. package/build/math/observableVector3d.d.ts.map +1 -1
  113. package/build/math/vector2d.d.ts +31 -1
  114. package/build/math/vector2d.d.ts.map +1 -1
  115. package/build/math/vector3d.d.ts +38 -1
  116. package/build/math/vector3d.d.ts.map +1 -1
  117. package/build/math/vertex.d.ts.map +1 -1
  118. package/build/particles/emitter.d.ts.map +1 -1
  119. package/build/particles/particle.d.ts +29 -2
  120. package/build/particles/particle.d.ts.map +1 -1
  121. package/build/particles/settings.d.ts +104 -0
  122. package/build/particles/settings.d.ts.map +1 -1
  123. package/build/physics/adapter.d.ts +9 -1
  124. package/build/physics/adapter.d.ts.map +1 -1
  125. package/build/physics/bounds.d.ts +1 -1
  126. package/build/physics/bounds.d.ts.map +1 -1
  127. package/build/physics/broadphase/octree.d.ts +24 -0
  128. package/build/physics/broadphase/octree.d.ts.map +1 -1
  129. package/build/physics/broadphase/quadtree.d.ts +31 -0
  130. package/build/physics/broadphase/quadtree.d.ts.map +1 -1
  131. package/build/physics/builtin/body.d.ts +17 -2
  132. package/build/physics/builtin/body.d.ts.map +1 -1
  133. package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
  134. package/build/physics/builtin/detector.d.ts +0 -1
  135. package/build/physics/builtin/detector.d.ts.map +1 -1
  136. package/build/physics/builtin/sat.d.ts +0 -7
  137. package/build/physics/builtin/sat3d.d.ts +46 -0
  138. package/build/physics/builtin/sat3d.d.ts.map +1 -1
  139. package/build/physics/response.d.ts +45 -62
  140. package/build/physics/response.d.ts.map +1 -1
  141. package/build/pool.d.ts +4 -0
  142. package/build/pool.d.ts.map +1 -1
  143. package/build/renderable/collectable.d.ts +6 -1
  144. package/build/renderable/collectable.d.ts.map +1 -1
  145. package/build/renderable/colorlayer.d.ts +17 -4
  146. package/build/renderable/colorlayer.d.ts.map +1 -1
  147. package/build/renderable/container.d.ts +23 -17
  148. package/build/renderable/container.d.ts.map +1 -1
  149. package/build/renderable/entity/entity.d.ts +12 -16
  150. package/build/renderable/entity/entity.d.ts.map +1 -1
  151. package/build/renderable/frameAnimation.d.ts +1 -1
  152. package/build/renderable/groundshadow.d.ts +1 -0
  153. package/build/renderable/groundshadow.d.ts.map +1 -1
  154. package/build/renderable/imagelayer.d.ts +12 -5
  155. package/build/renderable/imagelayer.d.ts.map +1 -1
  156. package/build/renderable/instanced_mesh.d.ts +56 -7
  157. package/build/renderable/instanced_mesh.d.ts.map +1 -1
  158. package/build/renderable/mesh.d.ts +369 -4
  159. package/build/renderable/mesh.d.ts.map +1 -1
  160. package/build/renderable/nineslicesprite.d.ts +3 -13
  161. package/build/renderable/nineslicesprite.d.ts.map +1 -1
  162. package/build/renderable/renderable.d.ts +129 -28
  163. package/build/renderable/renderable.d.ts.map +1 -1
  164. package/build/renderable/sprite.d.ts +7 -20
  165. package/build/renderable/sprite.d.ts.map +1 -1
  166. package/build/renderable/sprite3d.d.ts +31 -16
  167. package/build/renderable/sprite3d.d.ts.map +1 -1
  168. package/build/renderable/text/bitmaptext.d.ts +76 -5
  169. package/build/renderable/text/bitmaptext.d.ts.map +1 -1
  170. package/build/renderable/text/bitmaptextdata.d.ts +1 -5
  171. package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
  172. package/build/renderable/text/glypheffect.d.ts +44 -0
  173. package/build/renderable/text/glypheffect.d.ts.map +1 -0
  174. package/build/renderable/text/text.d.ts +22 -3
  175. package/build/renderable/text/text.d.ts.map +1 -1
  176. package/build/renderable/text/textmetrics.d.ts +2 -0
  177. package/build/renderable/text/textmetrics.d.ts.map +1 -1
  178. package/build/renderable/trail.d.ts +0 -1
  179. package/build/renderable/trail.d.ts.map +1 -1
  180. package/build/renderable/trail3d.d.ts +174 -0
  181. package/build/renderable/trail3d.d.ts.map +1 -0
  182. package/build/renderable/trigger.d.ts +28 -5
  183. package/build/renderable/trigger.d.ts.map +1 -1
  184. package/build/renderable/ui/panelpaint.d.ts +5 -0
  185. package/build/renderable/ui/panelpaint.d.ts.map +1 -0
  186. package/build/renderable/ui/progressbar.d.ts +192 -0
  187. package/build/renderable/ui/progressbar.d.ts.map +1 -0
  188. package/build/renderable/ui/uibaseelement.d.ts +48 -3
  189. package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
  190. package/build/renderable/ui/uipanel.d.ts +245 -0
  191. package/build/renderable/ui/uipanel.d.ts.map +1 -0
  192. package/build/renderable/ui/uispriteelement.d.ts +19 -3
  193. package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
  194. package/build/renderable/ui/uitextbutton.d.ts +30 -1
  195. package/build/renderable/ui/uitextbutton.d.ts.map +1 -1
  196. package/build/state/state.d.ts.map +1 -1
  197. package/build/system/bootstrap.d.ts.map +1 -1
  198. package/build/system/legacy_pool.d.ts +24 -4
  199. package/build/system/legacy_pool.d.ts.map +1 -1
  200. package/build/system/pool.d.ts +15 -8
  201. package/build/system/pool.d.ts.map +1 -1
  202. package/build/tweens/tween.d.ts +50 -3
  203. package/build/tweens/tween.d.ts.map +1 -1
  204. package/build/video/canvas/canvas_renderer.d.ts +34 -23
  205. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  206. package/build/video/effects/blendEffect.d.ts +0 -1
  207. package/build/video/effects/bloom.d.ts +72 -0
  208. package/build/video/effects/bloom.d.ts.map +1 -0
  209. package/build/video/effects/blur.d.ts +3 -2
  210. package/build/video/effects/blur.d.ts.map +1 -1
  211. package/build/video/effects/chromaticAberration.d.ts +3 -2
  212. package/build/video/effects/chromaticAberration.d.ts.map +1 -1
  213. package/build/video/effects/colorMatrix.d.ts +3 -2
  214. package/build/video/effects/colorMatrix.d.ts.map +1 -1
  215. package/build/video/effects/desaturate.d.ts +10 -2
  216. package/build/video/effects/desaturate.d.ts.map +1 -1
  217. package/build/video/effects/dissolve.d.ts +3 -2
  218. package/build/video/effects/dissolve.d.ts.map +1 -1
  219. package/build/video/effects/dropShadow.d.ts +3 -2
  220. package/build/video/effects/dropShadow.d.ts.map +1 -1
  221. package/build/video/effects/flash.d.ts +3 -2
  222. package/build/video/effects/flash.d.ts.map +1 -1
  223. package/build/video/effects/glow.d.ts +3 -2
  224. package/build/video/effects/glow.d.ts.map +1 -1
  225. package/build/video/effects/hologram.d.ts +3 -2
  226. package/build/video/effects/hologram.d.ts.map +1 -1
  227. package/build/video/effects/invert.d.ts +10 -2
  228. package/build/video/effects/invert.d.ts.map +1 -1
  229. package/build/video/effects/outline.d.ts +3 -2
  230. package/build/video/effects/outline.d.ts.map +1 -1
  231. package/build/video/effects/pixelate.d.ts +3 -2
  232. package/build/video/effects/pixelate.d.ts.map +1 -1
  233. package/build/video/effects/radialGradient.d.ts +8 -3
  234. package/build/video/effects/radialGradient.d.ts.map +1 -1
  235. package/build/video/effects/scanline.d.ts +3 -2
  236. package/build/video/effects/scanline.d.ts.map +1 -1
  237. package/build/video/effects/sepia.d.ts +10 -2
  238. package/build/video/effects/sepia.d.ts.map +1 -1
  239. package/build/video/effects/shadereffect.d.ts +10 -4
  240. package/build/video/effects/shadereffect.d.ts.map +1 -1
  241. package/build/video/effects/shine.d.ts +3 -2
  242. package/build/video/effects/shine.d.ts.map +1 -1
  243. package/build/video/effects/tintPulse.d.ts +3 -7
  244. package/build/video/effects/tintPulse.d.ts.map +1 -1
  245. package/build/video/effects/tonemap.d.ts +121 -0
  246. package/build/video/effects/tonemap.d.ts.map +1 -0
  247. package/build/video/effects/vignette.d.ts +3 -2
  248. package/build/video/effects/vignette.d.ts.map +1 -1
  249. package/build/video/effects/wave.d.ts +3 -7
  250. package/build/video/effects/wave.d.ts.map +1 -1
  251. package/build/video/gpu/batcher.d.ts +8 -2
  252. package/build/video/gpu/batcher.d.ts.map +1 -1
  253. package/build/video/gpu/vertexformat.d.ts +26 -0
  254. package/build/video/gpu/vertexformat.d.ts.map +1 -1
  255. package/build/video/gradient.d.ts +1 -1
  256. package/build/video/gradient.d.ts.map +1 -1
  257. package/build/video/renderer.d.ts +111 -7
  258. package/build/video/renderer.d.ts.map +1 -1
  259. package/build/video/renderstate.d.ts +2 -0
  260. package/build/video/renderstate.d.ts.map +1 -1
  261. package/build/video/rendertarget/canvasrendertarget.d.ts +3 -4
  262. package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
  263. package/build/video/rendertarget/render_target_pool.d.ts +0 -1
  264. package/build/video/rendertarget/rendertarget.d.ts +12 -5
  265. package/build/video/rendertarget/rendertarget.d.ts.map +1 -1
  266. package/build/video/rendertarget/webglrendertarget.d.ts +0 -1
  267. package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -1
  268. package/build/video/rendertarget/webgpurendertarget.d.ts +24 -14
  269. package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
  270. package/build/video/texture/atlas.d.ts +2 -1
  271. package/build/video/texture/atlas.d.ts.map +1 -1
  272. package/build/video/texture/cache.d.ts +1 -3
  273. package/build/video/texture/frametexture.d.ts +0 -1
  274. package/build/video/texture/frametexture.d.ts.map +1 -1
  275. package/build/video/texture/noise_texture2d.d.ts +1 -0
  276. package/build/video/texture/noise_texture2d.d.ts.map +1 -1
  277. package/build/video/texture/texture2d.d.ts +6 -0
  278. package/build/video/texture/texture2d.d.ts.map +1 -1
  279. package/build/video/utils/autodetect.d.ts +0 -2
  280. package/build/video/webgl/batchers/batcher.d.ts +0 -9
  281. package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
  282. package/build/video/webgl/batchers/lit_quad_batcher.d.ts +2 -0
  283. package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
  284. package/build/video/webgl/batchers/material_batcher.d.ts +4 -2
  285. package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
  286. package/build/video/webgl/batchers/mesh_batcher.d.ts +0 -3
  287. package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
  288. package/build/video/webgl/batchers/quad_batcher.d.ts +4 -0
  289. package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
  290. package/build/video/webgl/buffer/index.d.ts +0 -1
  291. package/build/video/webgl/buffer/retained_geometry.d.ts +0 -1
  292. package/build/video/webgl/glshader.d.ts +5 -3
  293. package/build/video/webgl/glshader.d.ts.map +1 -1
  294. package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts +0 -4
  295. package/build/video/webgl/texture/store.d.ts +0 -1
  296. package/build/video/webgl/webgl_renderer.d.ts +63 -24
  297. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  298. package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -3
  299. package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
  300. package/build/video/webgpu/batchers/quad_batcher.d.ts +0 -2
  301. package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
  302. package/build/video/webgpu/batchers/webgpu_batcher.d.ts +0 -2
  303. package/build/video/webgpu/batchers/webgpu_batcher.d.ts.map +1 -1
  304. package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
  305. package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts +0 -2
  306. package/build/video/webgpu/texture/frametexture.d.ts +0 -2
  307. package/build/video/webgpu/texture/frametexture.d.ts.map +1 -1
  308. package/build/video/webgpu/texture/store.d.ts +0 -1
  309. package/build/video/webgpu/webgpu_renderer.d.ts +7 -75
  310. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  311. package/package.json +2 -2
  312. package/skills/AGENTS.md +1 -1
  313. package/skills/melonjs/SKILL.md +2 -0
  314. package/skills/melonjs-3d/SKILL.md +454 -22
  315. package/skills/melonjs-3d-assets/SKILL.md +111 -10
  316. package/skills/melonjs-audio/SKILL.md +229 -8
  317. package/skills/melonjs-camera-and-drawing/SKILL.md +127 -1
  318. package/skills/melonjs-display-and-scaling/SKILL.md +273 -0
  319. package/skills/melonjs-effects-and-shaders/SKILL.md +341 -29
  320. package/skills/melonjs-events/SKILL.md +8 -6
  321. package/skills/melonjs-input/SKILL.md +147 -7
  322. package/skills/melonjs-math/SKILL.md +313 -0
  323. package/skills/melonjs-particles-and-trails/SKILL.md +189 -13
  324. package/skills/melonjs-performance/SKILL.md +131 -23
  325. package/skills/melonjs-physics/SKILL.md +103 -28
  326. package/skills/melonjs-renderables/SKILL.md +8 -2
  327. package/skills/melonjs-scenes-and-state/SKILL.md +134 -3
  328. package/skills/melonjs-sprites-and-animation/SKILL.md +9 -4
  329. package/skills/melonjs-tilemaps/SKILL.md +40 -12
  330. package/skills/melonjs-ui-and-text/SKILL.md +546 -7
  331. package/build/level/gltf/srgb.d.ts +0 -20
  332. package/build/level/gltf/srgb.d.ts.map +0 -1
@@ -33,13 +33,34 @@ or it renders flat. See `melonjs-3d` for the camera.
33
33
  |---|---|---|
34
34
  | `scale` | `1` | pixels per glTF unit, applied to the whole scene. Blender's metre-scale export usually needs 20–100. |
35
35
  | `container` | `game.world` | where the nodes are added |
36
- | `onLoaded` | `app.onLevelLoaded` | called with the **level id**, not the scene |
36
+ | `onLoaded` | `game.onLevelLoaded` | called with the **level id**, not the scene |
37
37
  | `async` | `false` | return a promise that settles once the scene is in the world, instead of a boolean |
38
38
  | `rightHanded` | `true` | see below |
39
39
  | `lights` | `true` | instantiate authored `KHR_lights_punctual` lights as `Light3d` world children |
40
40
  | `lightIntensityScale` | — | keep authored intensity ratios instead of normalising every light to 1 |
41
+
42
+ Those `game.` defaults are the engine's, not a recommendation. `game` is
43
+ whichever `Application` finished `init()` last, so **pass `container`
44
+ explicitly** from a `Stage`, where the right one is in your hand:
45
+
46
+ ```js
47
+ level.load("sceneId", { container: this.app.world, scale: 40 });
48
+ ```
41
49
  | `castGroundShadow` | inherits the app setting (**on**) | blob shadows for this scene's meshes |
42
50
  | `shadowGroundY` | each object's own base | world Y of the floor the blobs land on |
51
+ | `shadowOpacity` | `0.45` | how dark the blobs are |
52
+ | `shadowScale` | `1` | blob footprint multiplier; `0` or less hides them |
53
+ | `shadowOffset` | `0` | slide them along the light, in blob radii; needs `shadowGroundY`, which is the only way a scene load can name its floor |
54
+ | `shadowStretch` | `1` | lengthen them along it, clamped to 3 |
55
+ | `shadowDirectionX` / `shadowDirectionZ` | `0` | the ground direction they are cast along |
56
+ | `shadowGroundNormal` | world up | up normal of the floor, for a scene that is not level |
57
+ | `shadowLight` | — | a `Light3d` to take the direction from, re-read every draw |
58
+
59
+ `shadowGroundPoint` is **not** on that list, and a scene load drops it without
60
+ a warning. One floor POINT shared by every part of a scene would stack the
61
+ whole thing's blobs on a single spot, which is nonsense where one floor
62
+ HEIGHT is perfectly sensible. Ground that is not flat is a per-object answer:
63
+ set it on the parts you want once the load has finished.
43
64
 
44
65
  `onLoaded` receives the level id — it is a "done" signal, not a handle on the
45
66
  scene. You need it, or `async`: with the game loop running, `level.load` stops
@@ -79,10 +100,11 @@ world.addChild(new Trigger(x, y, {
79
100
  }));
80
101
  ```
81
102
 
82
- `scale`, `rightHanded`, `lights`, `lightIntensityScale`, `castGroundShadow` and
83
- `shadowGroundY` all travel, and so do `container`, `onLoaded`, `flatten` and
84
- `setViewportBounds`. That list is **hardcoded** in `Trigger` — a `level.load`
85
- option outside it is dropped without a warning.
103
+ Every `level.load` option above travels, including the whole ground-shadow set,
104
+ and so do `container`, `onLoaded`, `flatten` and `setViewportBounds`. The list is
105
+ **derived** from `level.loadOptions` rather than restated, so an option added to
106
+ a level format arrives here for free. It used to be hand-written and had fallen
107
+ three releases behind the glTF path (#1649).
86
108
 
87
109
  **`async` is deliberately excluded.** A trigger with a `color` + `duration`
88
110
  sets it itself, because it needs the promise to sequence its fade and reveal; a
@@ -129,6 +151,74 @@ asset fails to load. Export uncompressed.
129
151
  `"auto"`) drives sampling; glTF materials that declare their own sampler carry
130
152
  it through. Pixel-art models want `"nearest"`.
131
153
 
154
+ ### `baseColorFactor` is LINEAR; a `tint` is sRGB
155
+ **Pass the factor as a setting and the encoding is done for you.** `Mesh`
156
+ takes `baseColorFactor` — `[r, g, b]` in 0..1, linear, which is the form the
157
+ loader hands back — so a mesh built from a node passes the node's own field
158
+ straight through:
159
+
160
+ ```js
161
+ const node = loader.getGLTF("palm").nodes[0];
162
+ const mesh = new Mesh(x, y, {
163
+ vertices: node.vertices, normals: node.normals,
164
+ uvs: node.uvs, indices: node.indices, normalize: false,
165
+ baseColorFactor: node.baseColorFactor, // encoded, not scaled
166
+ });
167
+ ```
168
+
169
+ The setting's type admits `undefined`, because the loader declares the field
170
+ optional and that line has to compile under `exactOptionalPropertyTypes`.
171
+ `Sprite3d` forwards it too, and `mesh.setBaseColorFactor(f)` applies one after
172
+ construction. For an ordinary sRGB colour use `setTint` instead — the rest of
173
+ this section is why the two are different.
174
+
175
+
176
+ The loader handles this for you. It only bites when you build meshes yourself
177
+ from `loader.getGLTF(name).nodes` — which is what you do for per-node control,
178
+ since there is no public per-node `Mesh` factory — and reach for the obvious
179
+ `* 255`:
180
+
181
+ ```js
182
+ // WRONG — every untextured material renders far too dark
183
+ mesh.tint.setColor(f[0] * 255, f[1] * 255, f[2] * 255, 1);
184
+ ```
185
+
186
+ glTF defines `pbrMetallicRoughness.baseColorFactor` in **linear** space (spec
187
+ 3.9.2), while a melonJS `tint` is 8-bit **sRGB** — the same space as a CSS
188
+ colour or a texel out of a PNG. They are not the same number, and the gap is
189
+ widest exactly where art tends to live:
190
+
191
+ | linear | `* 255` | correct |
192
+ |---|---|---|
193
+ | 0.05 | 13 | 63 |
194
+ | 0.20 | 51 | 124 |
195
+ | 0.42 | 107 | 173 |
196
+ | 0.72 | 184 | 221 |
197
+ | 1.00 | 255 | 255 |
198
+
199
+ `Color.setLinear` does the encoding for you — it is the linear-space sibling of
200
+ `setFloat`, which takes the same `0..1` range but treats it as already-sRGB:
201
+
202
+ ```js
203
+ mesh.tint.setLinear(...node.baseColorFactor); // encodes; what you want
204
+ mesh.tint.setFloat(...node.baseColorFactor); // does NOT encode; too dark
205
+ ```
206
+
207
+ It clamps, so the slightly out-of-range factors some exporters emit cannot turn
208
+ into `NaN`, and it keeps the encoded value as a float rather than rounding
209
+ through 8 bits.
210
+
211
+ Measured on a scene whose every material went through `* 255`: mean frame luma
212
+ 85.5 against 147.8 once encoded, with each channel sitting 60 to 73 counts low.
213
+
214
+ **Fix it early.** It is nearly invisible as a bug, because the scene still
215
+ looks coherent — just moody — so lighting and bloom thresholds get tuned
216
+ against the wrong values. Correcting the encoding afterwards blows the frame
217
+ out and forces a lighting retune, which is a much bigger job than the encode.
218
+
219
+ This applies only to factors coming out of glTF. Colours **you** author for the
220
+ screen, a HUD tint or a glow, are already sRGB: `* 255` is right for those.
221
+
132
222
  ## Lights
133
223
 
134
224
  Authored `KHR_lights_punctual` lights — sun, point, spot — become `Light3d`
@@ -223,11 +313,21 @@ Omit `shadowGroundY` and each blob sits at its own object's base at full
223
313
  strength, which is right for props already resting on the ground. Set it when
224
314
  things jump or fly, so the shadow stays on the floor and shrinks with height.
225
315
 
226
- A blob is centred on its caster's x/z and is never offset by light direction, so
227
- a wide flat-bottomed prop hides its own shadow under itself. Raising
228
- `shadowGroundY` to force one into view floats the blob up over the object as a
229
- dark ring rather than sliding it clear — the setting is for things that leave
230
- the ground, not a visibility knob.
316
+ A blob sits on its caster's x/z by default, so a wide flat-bottomed prop hides
317
+ its own shadow under itself. The knob for that is `shadowScale`, which widens
318
+ the blob until its edge shows past the prop, or `shadowOffset` plus a direction,
319
+ which slides it clear. **Not** `shadowGroundY`: raising that floats the blob up
320
+ over the object as a dark ring instead of sliding it out, because it is for
321
+ things that leave the ground, not a visibility control.
322
+
323
+ For a light that is not overhead, name one: `shadowLight` takes the direction
324
+ from a `Light3d` and is re-read every draw, so a day or night cycle carries
325
+ every shadow in the scene with it. Nothing is inferred — a scene with no named
326
+ light and no `shadowDirectionX`/`Z` gets no direction, because a scene with
327
+ several lights has no non-arbitrary answer. `shadowStretch` lengthens the blob
328
+ along that direction and fades it as it pulls; `shadowGroundNormal` lays it on
329
+ a floor that is not level. All of these apply to an `InstancedMesh` too, as one
330
+ value for the whole set.
231
331
 
232
332
  ## OBJ/MTL
233
333
 
@@ -423,6 +523,7 @@ need a prefix.
423
523
  | one part of a rig renders rigid while its curves exist | it was re-parented mid-pose, baking the inverse into `matrix_parent_inverse` |
424
524
  | a merged mesh lights wrong along one axis | it inherited the active object's non-uniform scale on join — apply transforms |
425
525
  | an authored palette comes out uniformly dark, but white is correct | linear values written to a strip that is saved verbatim |
526
+ | every untextured glTF material renders too dark, white unaffected | `baseColorFactor` is linear and a `tint` is sRGB — `tint.setLinear(...)`, not `* 255` or `setFloat` |
426
527
  | `getGLTF(name).nodes[0]` geometry lands in the wrong place | `nodes` is per primitive, each with its own `world`; merge to one primitive and export at the origin |
427
528
  | a prop casts no visible shadow | wide and flat-bottomed — the blob is under it; `shadowGroundY` haloes it rather than revealing it |
428
529
  | shadows only show on casters near the camera | `shadowGroundY` is on the wrong side — Y-down means the floor is a **greater** y, so a `pos.y - lift` puts the blob inside the caster and the depth test leaves only a hairline ring |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-audio
3
- description: "Use this skill for sound and music in melonJS — loading audio, playing effects, background tracks, audio sprites, volume and muting, spatial audio, and procedural tone/noise generation. Covers the mandatory audio.init format list, the directory-not-file src convention, playTrack versus play, and browser autoplay unlocking. Triggers on: audio, sound, audio.init, audio.play, playTrack, stopTrack, audio.load, sfx, music, volume, mute, sprite audio, stereo, panner, audio.tone, audio.noise, getAudioContext, autoplay."
3
+ description: "Use this skill for sound and music in melonJS — loading audio, playing effects, background tracks, audio sprites, volume and muting, spatial audio, procedural tone/noise generation, and effects such as reverb or a compressor on the whole mix. Covers the mandatory audio.init format list, the directory-not-file src convention, playTrack versus play, the fixed listener and pixel-scale distances of positional sound, and browser autoplay unlocking. Triggers on: audio, sound, audio.init, audio.play, playTrack, stopTrack, audio.load, sfx, music, volume, mute, fade, rate, pitch, sprite audio, stereo, panner, position, listener, refDistance, reverb, echo, delay, compressor, distortion, filter, audio effect, audio.tone, audio.noise, getAudioContext, getMasterGain, hasAudio, hasFormat, autoplay, no sound, sound stopped working, context suspended, silent after idle, clips silent but tones play."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -29,6 +29,10 @@ await loader.preload([
29
29
  gives you a 404. A `data:audio/...` URL is the one exception: it is used
30
30
  verbatim, prefix and extension skipped.
31
31
 
32
+ `audio.hasAudio()` says whether the browser can play audio at all, and
33
+ `audio.hasFormat("ogg")` whether it can play one codec — useful for choosing
34
+ the `init` list or hiding a sound toggle on a device with no audio.
35
+
32
36
  Order matters — melonJS tries the listed formats left to right, so put the
33
37
  preferred one first. Two formats (`"webm,mp3"` or `"mp3,ogg"`) is the usual
34
38
  belt-and-braces for codec coverage. Accepted tokens: `mp3`, `mpeg`, `opus`,
@@ -51,7 +55,33 @@ loops, and it records the name as the current track — which is what
51
55
  `app.pauseOnBlur` / `resumeOnFocus` / `stopOnBlur` act on, via
52
56
  `state.pause(true)` → `pauseTrack()`. `stopTrack()`, `pauseTrack()`,
53
57
  `resumeTrack()` and `getCurrentTrack()` all read that same single slot, so there
54
- is only ever one current track. Stop it in `onDestroyEvent`.
58
+ is only ever one current *track name*.
59
+
60
+ **`playTrack` stops the track it replaces** (20.9.0 and later; before that it
61
+ only assigned the slot, and the previous bed kept looping underneath where
62
+ `stopTrack()` could no longer reach it). So a scene just states its own music
63
+ and nothing cancels anything:
64
+
65
+ ```js
66
+ class PlayScreen extends Stage {
67
+ onResetEvent() { audio.playTrack("battle", 0.6); }
68
+ }
69
+ ```
70
+
71
+ Calling it with the track already playing **restarts** it, the same as `play()`
72
+ on any other clip. A scene that wants the bed to carry on across a screen
73
+ change should not call it again, rather than rely on it doing nothing.
74
+
75
+ `stopTrack()` is for where nothing follows: a scene whose successor has no
76
+ music of its own, or a game going quiet. `state.change` destroys the outgoing
77
+ stage **before** it resets the incoming one, so a `stopTrack()` in
78
+ `onDestroyEvent` and a `playTrack()` in the next `onResetEvent` are in that
79
+ order and never cancel each other.
80
+
81
+ To end a track on an event rather than on a screen change, say a run ending
82
+ while the player stays on the same screen, `audio.fade(name, from, 0, ms)`
83
+ takes it down gently. A fade to zero is silence, not a stop, so the voice is
84
+ still live until something stops it or replaces it.
55
85
 
56
86
  Every per-clip function throws `audio clip <name> does not exist` if the clip was
57
87
  never loaded, and `play` additionally throws when a named sprite region is
@@ -72,8 +102,9 @@ audio.play("sfx", { sprite: "jump" });
72
102
 
73
103
  The optional third element marks a region as looping. `play()`'s second argument
74
104
  takes either the original `loop` boolean or a `PlayOptions` object
75
- (`sprite`, `loop`, `onend`, `volume`) — both forms work. Object fields win over
76
- the positional `onend` / `volume` arguments when both are supplied.
105
+ (`sprite`, `loop`, `onend`, `volume`, plus the spatial `follow`, `at` and
106
+ `stopWithTarget`) — both forms work. Object fields win over the positional
107
+ `onend` / `volume` arguments when both are supplied.
77
108
 
78
109
  ## Clip state
79
110
 
@@ -88,7 +119,9 @@ audio.state("theme"); // "unloaded" | "loading" | "loaded"
88
119
  `stop()`, `pause()` and `resume()` take an optional instance `id` too; omit it
89
120
  and the whole group is affected. `audio.stop()` with no arguments at all stops
90
121
  every sound. `audio.seek(name)` reads the position, `audio.seek(name, s, id?)`
91
- writes it; `audio.rate` and `audio.fade(name, from, to, ms, id?)` round it out.
122
+ writes it; `audio.rate(name, r, id?)` changes speed and pitch together
123
+ (`0.5`..`4.0`, `1` is normal) and `audio.fade(name, from, to, ms, id?)` ramps
124
+ the volume (`0`..`1`, over milliseconds).
92
125
 
93
126
  ## Volume and muting
94
127
 
@@ -105,6 +138,14 @@ audio.muted(); // global mute state
105
138
  `audio.unload(name)` / `audio.unloadAll()` free the decoded buffers; `unload`
106
139
  also clears the current-track pointer if it named that clip.
107
140
 
141
+ ### Losing focus mutes everything
142
+
143
+ When the window loses focus the engine mutes all audio, and unmutes it on
144
+ focus. That covers sound effects, procedural `tone` / `noise` and the mixer
145
+ effects, not just the music track that `state.pause()` pauses. Set
146
+ `pauseOnBlur: false` in the application settings if a clip has to keep playing
147
+ in the background.
148
+
108
149
  ## Spatial audio
109
150
 
110
151
  ```js
@@ -120,10 +161,113 @@ Called with just the clip name, each of the four returns the current value —
120
161
  `stereo` gives `0` and `position` gives `[0, 0, 0]` before they have ever been
121
162
  set.
122
163
 
164
+ ### Let the engine place the sound: `follow` and `at`
165
+
166
+ The shortest path to positional audio is a play option. Both take **world
167
+ pixels**, the same numbers as `pos.x` / `pos.y`, and the engine converts to
168
+ audio space for you:
169
+
170
+ ```js
171
+ // tracks the renderable every frame, and stops when it is destroyed
172
+ audio.play("ufo", { follow: this, stopWithTarget: true });
173
+
174
+ // pinned to a fixed world point
175
+ audio.play("portal", { at: { x: 1200, y: 480 } });
176
+ ```
177
+
178
+ `follow` and `at` are mutually exclusive: passing both throws, rather than
179
+ quietly picking one. `follow` re-reads the target's **absolute** position each
180
+ frame, so a renderable nested in a moving container is placed correctly. `stopWithTarget` is what you
181
+ want for a sound that belongs to an entity: without it a looping clip on a
182
+ destroyed renderable keeps playing from wherever it last was. `audio.unfollow(id)`
183
+ detaches a sound and leaves it playing where it is.
184
+
185
+ ### Move the listener, don't do the subtraction yourself
186
+
187
+ ```js
188
+ audio.setListener(player); // tracked every frame
189
+ audio.listener(x, y, z); // or place it by hand
190
+ audio.listener(); // → [x, y, z] in world pixels
191
+ audio.listenerOrientation(fx, fy, fz, ux, uy, uz);
192
+ audio.setListener(null); // stop tracking
193
+ ```
194
+
195
+ With a listener in the world, source positions are **absolute world
196
+ coordinates**, not offsets. The older idiom — passing `source.pos.x -
197
+ player.pos.x` every frame with the ear stuck at the origin — still works, but
198
+ only if you leave the listener at `(0, 0, 0)`; mixing the two double-counts the
199
+ player's position.
200
+
201
+ `setListener` places the ear at the target's absolute position after the world
202
+ updates. A plain renderable contributes position only, so a side-scroller keeps
203
+ the default "facing into the screen" and gets left-right panning plus distance
204
+ falloff from the one call.
205
+
206
+ A `Camera3d` target also contributes its **orientation**, taken from its own
207
+ basis, so turning the camera swings the stereo image with it. That is the whole
208
+ of what a posed 3D scene needs:
209
+
210
+ ```js
211
+ audio.setListener(app.viewport); // in a 3D scene this is a Camera3d
212
+ audio.play("drone", { follow: saucer }); // scene coordinates, directly
213
+ ```
214
+
215
+ **Y is down.** Every one of these calls takes and returns melonJS world
216
+ coordinates, where y grows downward; the Y-up flip Web Audio wants happens
217
+ inside the engine. Pass `pos.y` as it is.
218
+
219
+ ### The defaults are tuned for pixels
220
+
221
+ `audio.setSpatialDefaults()` / `getSpatialDefaults()` set the panner attributes
222
+ a sound placed through `follow` or `at` starts from, and the shipped values
223
+ assume a 2D game measured in pixels:
224
+
225
+ | attribute | default |
226
+ |---|---|
227
+ | `refDistance` | `240` |
228
+ | `maxDistance` | `10000` |
229
+ | `panningModel` | `"equalpower"` |
230
+ | `distanceModel` | `"inverse"` |
231
+
232
+ With those, a placed sound is at full volume out to 240 px and then halves
233
+ each time the distance doubles: about half at 480, a quarter at 960, roughly
234
+ an eighth at 2000. `maxDistance` is where it stops getting quieter.
235
+
236
+ **They apply to placed sounds only.** A plain `audio.play()` followed by a
237
+ bare `audio.position()` keeps WebAudio's own defaults, where `refDistance` is
238
+ `1` METRE and a source 100 px out is already near silent. That path is
239
+ unchanged, which is what keeps old code working.
240
+
241
+ Widen or tighten the field once, globally:
242
+
243
+ ```js
244
+ audio.setSpatialDefaults({ refDistance: 400, maxDistance: 20000 });
245
+ ```
246
+
247
+ `audio.panner(name, attrs, id?)` still overrides per clip or per voice, and it
248
+ merges over the current values rather than replacing them, so writing one field
249
+ no longer resets the rest. Called without an `id` it writes the **group**
250
+ defaults, which is what a clip-wide tuning call looks like.
251
+
252
+ For a 3D scene where front-to-back matters, `panningModel: "HRTF"` is worth the
253
+ cost; for a 2D one it mostly muddies the stereo image.
254
+
255
+ ### `stereo` and `position`: last writer wins
256
+
257
+ Both drive the same per-instance panner node, and the engine swaps the node to
258
+ match whichever you called **last**: `stereo()` after `position()` gives you
259
+ plain left-right panning, and `position()` after `stereo()` gives you 3D
260
+ falloff. Neither silently does nothing any more, and the order you call them in
261
+ no longer matters.
262
+
263
+ Still pick one per voice and stay with it. A 2D game usually wants `stereo()`
264
+ alone; reach for `position()` (or `follow` / `at`) when distance falloff
265
+ matters.
266
+
123
267
  **Spatial audio is WebAudio-only.** On a clip loaded with `stream: true` (or
124
268
  `html5: true`) `stereo` / `position` / `orientation` return early and do
125
269
  nothing — which is exactly the case for the long music tracks people reach for
126
- first.
270
+ first. `follow` and `at` are equally inert on a streamed clip.
127
271
 
128
272
  ## Procedural sound
129
273
 
@@ -167,6 +311,42 @@ custom Web Audio work; both return `null` when audio is unavailable, so guard.
167
311
  Connect a custom graph to the master gain rather than `ctx.destination` if you
168
312
  want it to respect `setVolume` / `muteAll`.
169
313
 
314
+ ## Effects on all game audio: reverb, echo, compressor
315
+
316
+ There is no built-in reverb, delay, distortion or compressor, and no per-clip
317
+ filter on file playback. There does not need to be one for the common case:
318
+ every buffered clip and every `tone` / `noise` passes through the master gain
319
+ on its way to the speakers, so an effect inserted **after** it applies to the
320
+ whole mix.
321
+
322
+ ```js
323
+ const ctx = audio.getAudioContext(); // creates the context if needed
324
+ const master = audio.getMasterGain();
325
+ if (ctx && master) { // null when audio is unavailable
326
+ const comp = ctx.createDynamicsCompressor();
327
+ master.disconnect(); // master -> speakers becomes
328
+ master.connect(comp); // master -> compressor -> speakers
329
+ comp.connect(ctx.destination);
330
+ }
331
+ ```
332
+
333
+ Any Web Audio node or chain works the same way: a `ConvolverNode` with an
334
+ impulse response for reverb, a `DelayNode` with a feedback gain for echo, a
335
+ `BiquadFilterNode` for an underwater low-pass, a `WaveShaperNode` for
336
+ distortion. Do it once, at startup; the master gain lives as long as the
337
+ context.
338
+
339
+ - **`setVolume` / `muteAll` still work**, because they act on the master gain,
340
+ which is now upstream of the effect.
341
+ - **Streamed clips are not affected.** A `stream: true` (`html5: true`) clip
342
+ plays through an `<audio>` element and never reaches the master gain — which
343
+ is usually the music. Preload the music buffered if it must go through the
344
+ effect.
345
+ - **It is all or nothing.** The per-instance nodes are not public, so an effect
346
+ on one clip only (muffling one sound behind a wall) is not possible through
347
+ this route; `noise` has its own `filter` for generated sounds.
348
+ - To remove it, reverse the wiring: `master.disconnect(); master.connect(ctx.destination);`.
349
+
170
350
  ## Autoplay: sound is silent until the first gesture
171
351
 
172
352
  Browsers create the audio context in the `suspended` state and keep it there
@@ -174,8 +354,36 @@ until a user interaction. Nothing errors — audio simply does not play, which
174
354
  reads as a bug. melonJS installs one-shot `touchstart` / `touchend` / `click` /
175
355
  `keydown` unlock listeners on `document`, so the first real gesture anywhere
176
356
  unlocks it and fires the clip's `on.unlock` callback. Design for it: start music
177
- on the first click or key press, not on load. `tone` / `noise` additionally call
178
- `ctx.resume()` themselves, best-effort, on every invocation.
357
+ on the first click or key press, not on load. The usual way to guarantee one is
358
+ a gate screen — a splash, a "press any key", or a health/photosensitivity notice
359
+ — whose dismissal *is* the gesture, so the menu behind it can start its music
360
+ unconditionally. Note the unlock listens for `keydown` too, so a keyboard-only
361
+ player is covered.
362
+
363
+ ### The context can stop again, long after it was unlocked
364
+
365
+ Unlocking is only the first half. A context that has been running can be
366
+ stopped later: melonJS suspends it itself after thirty idle seconds to save
367
+ power, and browsers stop it for their own reasons, a backgrounded tab among
368
+ them. Recovery is automatic and you do not have to write it: a user gesture
369
+ or the tab coming back to the front revives it, and a deliberate idle
370
+ suspension is left alone so the power saving still works.
371
+
372
+ **The symptom worth memorising**, because it points straight at the cause:
373
+ if `tone` and `noise` are audible while loaded clips are silent, the context
374
+ is healthy and the engine's playback state is wrong. The generators only need
375
+ a live context, where a clip goes through the engine's state machine and can
376
+ be parked waiting for a resume. It is never "the context died".
377
+
378
+ Both routes ask the same owner to resume, so hearing a tone tells you the
379
+ hardware is back; it tells you nothing about whether the clips waiting on the
380
+ engine's state have been let go.
381
+
382
+ That asymmetry was a real Safari-only bug: thirty idle seconds before the
383
+ first click left the engine waiting on a suspend the browser never confirmed,
384
+ so every clip queued for the life of the page while generated sound played
385
+ normally. Fixed in 20.9, and the lesson generalises: a clip that will not play
386
+ is a state question, not a hardware one.
179
387
 
180
388
  Playback also starts **asynchronously**: `play()` returns an id while the
181
389
  instance is still starting, so `playing()` can legitimately be `false` on the
@@ -209,6 +417,10 @@ same switch under its backend name.) `on` accepts the full lifecycle set:
209
417
  `play`, `pause`, `stop`, `end`, `fade`, `seek`, `rate`, `volume`, `mute`,
210
418
  `unlock`.
211
419
 
420
+ Outside a preload, `audio.load(asset, onload?, onerror?)` takes the same
421
+ descriptor and loads one clip at runtime — for a level's sounds fetched when
422
+ the level starts. The loader calls the same function, so the same rules apply.
423
+
212
424
  Re-preloading a manifest that already contains a loaded clip is a no-op —
213
425
  `audio.unload(name)` first if you genuinely want to reload it.
214
426
 
@@ -241,12 +453,21 @@ either way — blank screen, empty console.
241
453
  | nothing plays until the user clicks | browser autoplay policy — expected, design around it |
242
454
  | `playing()` is false right after `play()` | playback starts asynchronously |
243
455
  | music keeps playing after leaving a scene | used `play` instead of `playTrack`, or no `stopTrack` in teardown |
456
+ | two music tracks playing at once, and `stopTrack()` only silences one | melonJS before 20.9.0: `playTrack` did not stop the previous track, so the scene that started it had to `stopTrack()` on the way out |
457
+ | the music restarts when returning to a screen | `playTrack` with the track already playing restarts it; don't call it again if the bed should carry on |
244
458
  | `stereo` / `position` do nothing | the clip was loaded with `stream: true` — spatial is WebAudio-only |
245
459
  | one format works, another does not | that codec is not in the `audio.init()` list |
246
460
  | audio behind a login fails, or the preload hangs | `loader.setOptions({ withCredentials: true })` — the same switch every asset type uses |
247
461
  | a streamed clip ignores `withCredentials` | `stream: true` plays through an `<audio>` element; preload it buffered instead |
248
462
  | `audio.tone(440, {...})` does nothing / errors | `tone` takes a single options object with a required `duration` |
249
463
  | `tone` / `noise` are silent with no error | no WebAudio context — `getAudioContext()` returned `null` |
464
+ | a positioned sound is silent, or only audible right next to the player | placed by hand with `audio.position()`, which keeps WebAudio's metre defaults (`refDistance: 1`) — use `follow` / `at`, or set `refDistance` in pixels with `audio.panner()` |
465
+ | a positioned sound does not follow the player | nothing is placing it — `audio.play(name, { follow: entity })`, and `audio.setListener(player)` once |
466
+ | a followed sound pans to the wrong side as the player moves | the listener was moved **and** positions are still relative to the player — with a listener in the world, pass absolute world coordinates |
467
+ | a looping sound outlives the entity that owns it | `follow` without `stopWithTarget: true` |
468
+ | a voice panned with `stereo()` loses its 3D placement, or the reverse | one panner node per voice and last writer wins — pick one per voice and stay with it |
469
+ | a reverb / compressor on the master gain does not affect the music | the music is `stream: true`, which bypasses the master gain — preload it buffered |
470
+ | sound effects keep playing after the window loses focus | `pauseOnBlur` and `stopOnBlur` are both off, so the engine is deliberately leaving the game running in the background |
250
471
 
251
472
  ## Related skills
252
473
 
@@ -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 drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, and baking with CanvasRenderTarget. Triggers on: Camera2d, viewport, follow, setBounds, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d."
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 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, setBounds, 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."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -24,6 +24,44 @@ frame-rate independent since 19.7. `AXIS.HORIZONTAL` / `AXIS.VERTICAL` /
24
24
 
25
25
  Call it from the followed entity's `onActivateEvent`, using `this.parentApp`.
26
26
 
27
+ **For anything that is NOT the camera, the same smoothing is `math.damp`.**
28
+ `follow()` is frame-rate independent for you; a value you smooth yourself is
29
+ not, and `lerp(current, target, alpha)` once per frame moves further on a slow
30
+ frame than on a fast one, so the feel changes with the frame rate:
31
+
32
+ ```js
33
+ import { math } from "melonjs";
34
+
35
+ // dt is milliseconds, damp wants seconds
36
+ this.speed = math.damp(this.speed, targetSpeed, 5, dt / 1000);
37
+ this.turret.angle = math.damp(this.turret.angle, wanted, 8, dt / 1000);
38
+ ```
39
+
40
+ `damp(current, target, lambda, dt)`. Higher `lambda` converges faster; it is
41
+ the rate, not a per-frame fraction.
42
+
43
+ **For a DIRECTION rather than a scalar, the tool is `slerp`**, on `Vector2d`
44
+ and `Vector3d` alike. Two reasons, and the second is the one that bites:
45
+
46
+ ```js
47
+ // a turret, a ship, a camera: a facing that chases another facing
48
+ facing.slerp(toTarget, 1 - Math.exp((-6 * dt) / 1000));
49
+ ```
50
+
51
+ 1. Damping a facing component by component drags it through the inside of the
52
+ circle, so it slows at the ends, rushes the middle and comes out shorter
53
+ than it went in.
54
+ 2. **Damping a scalar heading goes the wrong way round the wrap.** From 170
55
+ degrees to -170 degrees is 20 degrees apart, and the numbers are 340 apart,
56
+ so the turret sweeps all the way round the back. `slerp` works on the
57
+ directions, takes the shorter arc by construction, and has no wrap to cross.
58
+
59
+ See `melonjs-3d` for the rest, including what happens between two exactly
60
+ opposite directions. Use it for camera pans you drive yourself,
61
+ turret tracking, zoom, input smoothing and any value that should chase another
62
+ one — it is the tool that is easy to miss and easy to hand-write slightly
63
+ wrong.
64
+
27
65
  ## Bounds are the thing people forget
28
66
 
29
67
  The camera's bounds default to the **renderer size**, so it cannot scroll until
@@ -53,6 +91,26 @@ The fade pair is named from the *colour's* point of view, not the scene's:
53
91
  (screen starts at `color` and clears). Both default to a 1000 ms duration and
54
92
  take an optional completion callback.
55
93
 
94
+ **`fadeIn` does not HOLD the colour.** The effect completes, the camera's own
95
+ update destroys it, and the screen snaps back — which is why the engine's
96
+ idiom does the work in the completion callback and then calls `fadeOut`. To
97
+ keep a filled screen across a state change, own the effect: a completed one
98
+ marked `isPersistent` is left alone, and taking it away again is yours to do.
99
+
100
+ ```js
101
+ const hide = camera.addCameraEffect(
102
+ new FadeEffect(camera, { color: "#000", duration: 400, direction: "in" }),
103
+ );
104
+ hide.isPersistent = true; // survives completion AND Camera2d#reset()
105
+ // ... later, once the new stage is up
106
+ camera.removeCameraEffect(hide); // which destroys it for you
107
+ ```
108
+
109
+ That is exactly what `state.transition()` is built on, so a hand-rolled
110
+ transition behaves the same way. Before 20.9 a persistent effect was destroyed
111
+ on completion like any other, which is why a transition used to flash the
112
+ scene it had just covered.
113
+
56
114
  `colorMatrix` (`brightness` / `contrast` / `saturate` / `hueRotate` / `sepia` /
57
115
  `invertColors`, all chainable) is the cheap way to grade a whole camera: the
58
116
  camera appends it as a final post-effect pass by itself whenever the matrix is
@@ -102,6 +160,24 @@ coordinate that puts your label on the wrong side of the screen. Skip it.
102
160
  Do not hand-roll this with `Matrix3d` — the perspective divide and the behind-
103
161
  camera case are exactly what gets it wrong.
104
162
 
163
+ ## Pointing a 3D camera
164
+
165
+ A 2D camera's only rotation is `roll`. A 3D one can be pointed at a target, and
166
+ optionally banked at the same time:
167
+
168
+ ```js
169
+ camera.lookAt(target); // pitch and yaw; roll untouched
170
+ camera.lookAt(target, up); // and bank, where `up` is what should
171
+ // appear UP ON SCREEN
172
+ camera.setBasis(right, up, forward); // or pose it from a basis outright
173
+ ```
174
+
175
+ `up` has no default, so the one-argument form behaves exactly as it always
176
+ did. Because this engine renders Y-down, a level horizon is `(0, -1, 0)`.
177
+ `setBasis` is the writable counterpart of `getBasis` and takes the camera's
178
+ own columns, whose `up` points down the screen: the opposite sense, one
179
+ negation apart. See `melonjs-3d` for the full account.
180
+
105
181
  ## Secondary cameras
106
182
 
107
183
  Cameras are first-class — a minimap is a second camera with `autoResize = false`
@@ -195,9 +271,57 @@ hand `.canvas` on for exactly that reason.
195
271
 
196
272
  This is the sanctioned performance idiom before reaching for a custom shader.
197
273
 
274
+ **For a solid fill you do not need a canvas at all.** `Renderer.getWhitePixel()`
275
+ is a shared, lazily-created 1x1 white canvas, and stretching it is how you get
276
+ a coloured rectangle, a bar, a flat screen overlay, or a blank carrier for an
277
+ effect that paints from scratch:
278
+
279
+ ```js
280
+ const bar = new Sprite(0, 0, { image: Renderer.getWhitePixel() });
281
+ bar.anchorPoint.set(0, 0);
282
+ bar.setTint("#39d0ff");
283
+ bar.scale(width, height); // a 1x1 texture, so scale IS the size in pixels
284
+ ```
285
+
286
+ It is also the right texture under a `ShaderEffect` whose body ignores the
287
+ incoming colour, and it keeps the quad batched with everything else using it.
288
+ When you DO need a real canvas, `Renderer.createCanvas(width, height)` is the
289
+ engine's allocator: it hands back an `OffscreenCanvas` where that is safe and
290
+ throws on a zero dimension, instead of `document.createElement("canvas")`.
291
+
198
292
  `renderer.toFrameTexture()` is the related tool for capturing the *current frame*
199
293
  into a texture for a shader to sample.
200
294
 
295
+ ## Reading pixels back
296
+
297
+ To grab what a render target holds, as pixels or as a file:
298
+
299
+ ```js
300
+ const pixels = await rt.toImageData(); // ImageData, any backend
301
+ const blob = await rt.toBlob(); // Blob, "image/png" by default
302
+ const url = await rt.toDataURL(); // data: URL string
303
+ const bitmap = await rt.toImageBitmap(); // ImageBitmap
304
+ ```
305
+
306
+ All four are promises, and that is not decoration. **WebGPU cannot read a
307
+ texture back synchronously** — it has to map a buffer, which only completes on
308
+ a later tick. So `toImageData()` is the portable readback and the other three
309
+ are built on it.
310
+
311
+ There is also a synchronous `getImageData()` on `CanvasRenderTarget` and
312
+ `WebGLRenderTarget`, because those two genuinely can. It is **not** part of the
313
+ `RenderTarget` contract and it **throws on a WebGPU target**. Reach for it only
314
+ in code that already knows which backend it is on; anything backend-agnostic
315
+ should await `toImageData()`.
316
+
317
+ ```js
318
+ // fine in canvas-only or WebGL-only code
319
+ const data = rt.getImageData(0, 0, w, h);
320
+
321
+ // the version that works wherever the game runs
322
+ const data = await rt.toImageData(0, 0, w, h);
323
+ ```
324
+
201
325
  ## Symptom → cause
202
326
 
203
327
  | symptom | cause |
@@ -212,7 +336,9 @@ into a texture for a shader to sample.
212
336
  | clipping in the wrong place inside a container | container clips in local coordinates, after its own translate |
213
337
  | `container.clipping = true` does nothing | container has no explicit size, so `width`/`height` are `Infinity` |
214
338
  | baked target draws nothing / throws | passed the `CanvasRenderTarget`, not its `.canvas` |
339
+ | `getImageData()` throws on one machine but not another | that one is on the WebGPU renderer, which cannot read back synchronously — `await toImageData()` instead |
215
340
  | frame rate drops with many static draws | bake into a `CanvasRenderTarget` instead |
341
+ | a 3D camera cannot be given an arbitrary up | `Camera3d#lookAt(target, up)` or `setBasis(right, up, forward)`; see `melonjs-3d` |
216
342
 
217
343
  ## Related skills
218
344