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-deployment
3
- description: "Use this skill when packaging, shipping or scaffolding a melonJS game — starting a project with create-melonjs, building for the web, wrapping as a native app with Capacitor or Cordova, publishing to WeChat Mini Games or Facebook Instant Games, browser support and transpiling. Triggers on: create-melonjs, scaffold, new project, build, deploy, publish, Capacitor, Cordova, native app, iOS, Android, WeChat, Instant Games, browser support, transpile, ES2022, bundle size, tree-shaking."
3
+ description: "Use this skill when packaging, shipping or scaffolding a melonJS game — starting a project with create-melonjs, building for the web, wrapping as a native app with Capacitor or Cordova, publishing to WeChat Mini Games or Facebook Instant Games, running with no DOM (nx.js on the Nintendo Switch, a Web Worker), browser support and transpiling. Triggers on: create-melonjs, scaffold, new project, build, deploy, publish, Capacitor, Cordova, native app, iOS, Android, WeChat, Instant Games, nx.js, Nintendo Switch, homebrew, no DOM, Web Worker, OffscreenCanvas, browser support, transpile, ES2022, bundle size, tree-shaking."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -15,9 +15,11 @@ npm create melonjs@latest my-game
15
15
  ```
16
16
 
17
17
  The project name is required — without it the command prints its usage and
18
- exits. It downloads the melonJS TypeScript boilerplate (Vite, TypeScript and the
19
- debug plugin already wired in), which saves you the top-level-await and
20
- asset-path issues that a hand-rolled config usually hits first.
18
+ exits — and it names a new directory: an existing one is refused. It downloads
19
+ the melonJS TypeScript boilerplate (Vite, TypeScript and the debug plugin
20
+ already wired in), which saves you the top-level-await and asset-path issues
21
+ that a hand-rolled config usually hits first. Then `npm install` and
22
+ `npm run dev` in that directory.
21
23
 
22
24
  ## Adding melonJS to an existing project
23
25
 
@@ -69,14 +71,22 @@ Two caveats worth knowing:
69
71
 
70
72
  ## Native apps
71
73
 
72
- **Capacitor** is the current recommendation for iOS and Android — see
73
- `@melonjs/capacitor-plugin`. **Cordova** also works and is documented, but is the
74
- older route.
75
-
76
- The thing that breaks first in both is asset loading: the app is served from
77
- `file://`, where `fetch()` cannot read local files. melonJS's loader falls back
78
- to XHR for that scheme, so engine-loaded assets work — but any `fetch()` in
79
- *your* game code will fail there.
74
+ **Capacitor** is the current recommendation for iOS and Android. A melonJS game
75
+ runs in its WebView unchanged; `@melonjs/capacitor-plugin` adds the native
76
+ lifecycle: the game pauses when the app goes to the background and resumes when
77
+ it comes back (but stays paused if the player had paused it, from 1.0.1), the
78
+ hardware back button reaches the current stage (`bindStageBack`), and
79
+ orientation lock and the splash screen are one call each. The boilerplate's
80
+ Vite build writes to `dist/`, Capacitor's default `webDir`. See
81
+ `melonjs-plugins` for its options. **Cordova** also works and is documented,
82
+ but is the older route.
83
+
84
+ The thing that breaks first under Cordova is asset loading: the app is served
85
+ from `file://`, where `fetch()` cannot read local files. melonJS's loader falls
86
+ back to XHR for that scheme, so engine-loaded assets work — but any `fetch()` in
87
+ *your* game code will fail there. Capacitor serves the app from a local web
88
+ server instead (`https://localhost` on Android, `capacitor://localhost` on
89
+ iOS), so `fetch()` works there.
80
90
 
81
91
  ## Mini-game platforms
82
92
 
@@ -85,7 +95,67 @@ wiki. Note that `device.platform.isWeixin` detects WeChat's **in-app browser**,
85
95
  which is a normal WebView. WeChat *Mini Games* are a different runtime with no
86
96
  DOM, and need platform-specific adaptation rather than just detection — the one
87
97
  hook the engine offers is that the renderer adopts a `globalThis.canvas` when
88
- the runtime's adapter provides one, instead of creating its own.
98
+ the runtime's adapter provides one, instead of creating its own. What the engine
99
+ does without a DOM is described in the next section.
100
+
101
+ ## Runtimes with no DOM: nx.js, Web Workers
102
+
103
+ melonJS (from 20.14) runs where there is no `document`: in a Web Worker, and in
104
+ runtimes that implement the web APIs a game uses but no page around it, such as
105
+ [nx.js](https://github.com/TooTallNate/nx.js), JavaScript homebrew for the
106
+ Nintendo Switch. There is no `<canvas>` to create there, so hand the
107
+ Application the canvas to draw to. On nx.js that is the global `screen`, the
108
+ 1280x720 display:
109
+
110
+ ```js
111
+ import { Application, video } from "melonjs";
112
+
113
+ const app = new Application(1280, 720, {
114
+ canvas: screen, // nx.js's display; an OffscreenCanvas in a worker
115
+ renderer: video.AUTO, // WebGL 2 where the runtime has it, else Canvas
116
+ scale: 1,
117
+ });
118
+ await app.init();
119
+ ```
120
+
121
+ What changes without a DOM:
122
+
123
+ - **Design at the canvas's size.** There is no page to fit, so
124
+ `scale: "auto"` measures nothing and the game is drawn at the scale it was
125
+ given. On the Switch that is 1280x720 (the console scales it when docked).
126
+ - `app.parentElement` is `undefined`: the canvas is the display and is not
127
+ attached to anything. Fullscreen and pointer lock do nothing.
128
+ - **Assets** load through `fetch` and `createImageBitmap` as in a browser; on
129
+ nx.js a relative path resolves inside the app's RomFS. Tiled maps must be
130
+ **JSON** (`.tmj` / `.tsj`): XML needs `DOMParser` (see `melonjs-tilemaps`).
131
+ The `video` and `script` asset types need a DOM and fail.
132
+ - **Input**: touch arrives as `TouchEvent`s on the canvas, and melonJS reports
133
+ them as pointer events as usual. There is no mouse. On nx.js the Joy-Con are
134
+ standard gamepads (`melonjs-input`), and keyboard events only come from a
135
+ USB keyboard.
136
+ - **Audio** needs no gesture to unlock. nx.js cannot say which formats it
137
+ decodes (its `Audio` has no `canPlayType`), so melonJS tries the first
138
+ extension given to `audio.init()`: list one FFmpeg decodes, such as
139
+ `audio.init("ogg,mp3")`. nx.js's Web Audio has no `PannerNode`,
140
+ `OscillatorNode`, `BiquadFilterNode` or effect nodes, so plain playback,
141
+ volume and `stereo()` work, but 3D `position()` / `panner()`, `tone()` /
142
+ `noise()` and the reverb / echo / compressor effects throw there (see
143
+ `melonjs-audio`).
144
+ - **Fonts**: nx.js's `FontFace` only takes the font's bytes, so a `fontface`
145
+ asset (a URL) fails there. Load the bytes yourself and add the face before
146
+ using it in a `Text`. The face is ready once made, and its `load()` throws
147
+ on nx.js, so do not call it:
148
+ `fonts.add(new FontFace("Pixel", await (await fetch("font.ttf")).arrayBuffer()))`.
149
+ - **Save data**: melonJS checks `localStorage` when it loads. On nx.js, if the
150
+ app declares user save data (`userAccountSaveDataSize` in its NACP), that
151
+ first read is when the Switch asks the player to pick a user profile. An app
152
+ that saves nothing should leave that size at 0, so no picker appears.
153
+ - A Web Worker has no `Image`, `Audio` or `AudioContext` of its own: draw
154
+ there, and play sound from the page.
155
+
156
+ The engine's own test, `tests/nxjs-compatibility.spec.js`, runs a game in a
157
+ Web Worker made to look like nx.js. It is the reference for what is expected
158
+ to work, not a test on Switch hardware.
89
159
 
90
160
  ## Size
91
161
 
@@ -103,11 +173,19 @@ are not decoded into memory.
103
173
  |---|---|
104
174
  | blank page, syntax error in the console | browser older than the ES2022 target |
105
175
  | transpiling changes nothing | build config excludes `node_modules` |
106
- | assets 404 in a packaged native app | `fetch()` in game code — `file://` needs XHR |
176
+ | assets 404 in a packaged Cordova app | `fetch()` in game code — `file://` needs XHR |
107
177
  | a built-in method is missing after transpiling | transpiling is syntax-only; polyfills are separate |
108
178
  | bundle much larger than expected | tree-shaking recovers little — subsystems are interconnected |
179
+ | a native app paused from its menu is running again after coming back from the background | capacitor-plugin < 1.0.1 resumed on every return; upgrade |
180
+ | `Cannot read properties of undefined (reading 'body')` from `new Application` in nx.js or a Web Worker | melonJS older than 20.14, which needed a DOM; update |
181
+ | WebGL 2 "could not be created" on nx.js | another context was taken on `screen` first: it holds one context kind, so let the Application be the first to call `getContext` on it |
182
+ | the game is drawn small, or cropped, on nx.js | its resolution is not the screen's 1280x720; with no page, `scale: "auto"` does not fit it |
183
+ | an XML Tiled map fails with "needs DOMParser" | no DOM to parse XML with: export the map and its tilesets as JSON (`.tmj` / `.tsj`) |
184
+ | a `fontface` asset fails on nx.js | nx.js's `FontFace` takes bytes, not a URL: fetch the file and add the face to `fonts` yourself |
185
+ | spatial sound, `tone()` or `noise()` throws "Method not implemented" on nx.js | the node it needs is missing from nx.js's Web Audio; use `stereo()` and recorded sounds |
109
186
 
110
187
  ## Related skills
111
188
 
112
189
  - `melonjs` — the companion packages, including the Capacitor plugin
190
+ - `melonjs-plugins` — registering the Capacitor plugin, and its helpers
113
191
  - `melonjs-getting-started` — the Application bootstrap the template produces
@@ -39,8 +39,11 @@ the usual arrangement: author once, scale to whatever you are given.
39
39
 
40
40
  ## `scale` and `scaleMethod`
41
41
 
42
- `scale: "auto"` turns on automatic scaling; a number is a fixed multiplier and
43
- `scaleMethod` is then ignored.
42
+ `scale: "auto"` turns on automatic scaling, and so does naming any
43
+ `scaleMethod` other than `"manual"`, even with no `scale` given:
44
+ `{ scaleMethod: "flex" }` alone scales to its container. A number is a fixed
45
+ multiplier, used only while `scaleMethod` is `"manual"` (the default); with
46
+ automatic scaling on, the number is ignored.
44
47
 
45
48
  `scaleMethod` decides how the available space is used:
46
49
 
@@ -54,7 +57,8 @@ the usual arrangement: author once, scale to whatever you are given.
54
57
  both axes.
55
58
  - **`"stretch"`** scales each axis independently, so the aspect ratio is not
56
59
  preserved and circles become ovals.
57
- - **`"manual"`** does nothing automatic.
60
+ - **`"manual"`**, the default, does nothing automatic with a numeric `scale`.
61
+ With `scale: "auto"` it scales as `"fit"` does.
58
62
 
59
63
  The distinction that matters: **`fit` scales a fixed frame; everything else
60
64
  changes how much of the world is visible.** A game whose layout assumes a
@@ -381,3 +385,4 @@ const app = new Application(1024, 576, {
381
385
  | `scaleRounding: "integer"` changes nothing | it applies to `scaleMethod: "fit"` only; the others resize the renderer |
382
386
  | text is soft but sprites are crisp | the glyphs were anti-aliased when `Text` baked them; use `BitmapText` (see below) |
383
387
  | the game shows more world on a bigger window | that is `fill-*` or `flex-*` doing their job; use `"fit"` to keep a fixed frame |
388
+ | a numeric `scale` (say `2`) is ignored | a `scaleMethod` other than `"manual"` turns automatic scaling on, which ignores the number |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-effects-and-shaders
3
- description: "Use this skill for post-processing effects, custom shaders, blend modes and colour grading in melonJS. Covers the built-in ShaderEffect presets, addPostEffect on renderables and cameras, writing a custom dual-language GLSL/WGSL effect, the screen_texture builtins, and why effects silently do nothing on the Canvas fallback. Triggers on: Tween, easing, animate an effect, ShaderEffect, addPostEffect, removePostEffect, getPostEffect, postEffectNeedsCapture, VignetteEffect, GlowEffect, BlurEffect, PixelateEffect, ScanlineEffect, shader, GLSL, WGSL, uniform, setUniform, setTexture, setTime, blendMode, colorMatrix, screen_texture, toFrameTexture, post effect, filter."
3
+ description: "Use this skill for post-processing effects, custom shaders, blend modes and colour grading in melonJS. Covers the built-in ShaderEffect presets, addPostEffect on renderables and cameras, writing a custom dual-language GLSL/WGSL effect, the screen_texture builtins, and why effects silently do nothing on the Canvas fallback. Triggers on: Tween, easing, animate an effect, ShaderEffect, addPostEffect, removePostEffect, getPostEffect, postEffectNeedsCapture, VignetteEffect, GlowEffect, BlurEffect, PixelateEffect, ScanlineEffect, OutlineEffect, BloomEffect, ToneMappingEffect, SwayEffect, SquishEffect, hdr, tone mapping, shader, GLSL, WGSL, uniform, setUniform, setTexture, setTime, getShader, prewarm, blendMode, colorMatrix, screen_texture, screen_uv, noise_uv, toFrameTexture, pixelate transition, post effect, filter."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -88,8 +88,9 @@ class Bar extends Renderable {
88
88
  }
89
89
  ```
90
90
 
91
- `ProgressBar` and `Trail` set it for you, and a sprite needs nothing: the flag
92
- defaults to `false` and only ever changes the single-effect case.
91
+ `ProgressBar`, `UIPanel` and `Trail` set it for you, and a sprite needs
92
+ nothing: the flag defaults to `false` and only ever changes the single-effect
93
+ case.
93
94
 
94
95
  ## Toggle with `enabled`, do not remove
95
96
 
@@ -229,7 +230,8 @@ squish.setBulge(1 - scaleY);
229
230
  ```
230
231
 
231
232
  Apply that from the sprite's own `update()`, not from a tween's `onUpdate`. A
232
- physics adapter that mirrors a body's angle (Matter does) rewrites
233
+ physics adapter that mirrors a body's angle (matter, planck and Rapier do, and so does
234
+ the built-in once a body has an angle or angular velocity) rewrites
233
235
  `currentTransform` on every step, before the renderables update, so a scale set
234
236
  anywhere else is gone by the time the sprite is drawn. Let the tween animate a
235
237
  plain `{ scaleY }` object and read it in `update()`.
@@ -457,8 +459,10 @@ renderer.setToneMapping("aces", { exposure: 0.4, white: 6 });
457
459
  renderer.setToneMapping("none"); // and off again
458
460
  ```
459
461
 
460
- `"none"` by default. The renderer keeps the curve LAST on the camera, so
461
- whatever you put in front of it is mapped along with everything else. Changing
462
+ `"none"` by default, and set at boot through the `toneMapping`,
463
+ `toneMappingExposure` and `toneMappingWhite` application settings. The
464
+ renderer keeps the curve LAST on the camera, so whatever you put in front of
465
+ it is mapped along with everything else. Changing
462
466
  the mode rebuilds one shader (the curve is baked); `exposure` and `white` are
463
467
  uniform writes, so a settings screen can drive them live. `white` is the value
464
468
  that comes out as display white — the knob a "brightness" or "peak" slider
@@ -483,6 +487,13 @@ It is still *implemented* as one: a non-identity matrix makes the camera append
483
487
  an internal `ColorMatrixEffect` to its post-effect chain for that frame, so it
484
488
  needs a GPU backend like everything else here.
485
489
 
490
+ The `"pixelate"` state transition (`state.transition("pixelate", …)`, or a
491
+ `PixelateTransitionEffect` added with `addCameraEffect`) is another such guest:
492
+ while it runs it puts a `PixelateEffect` FIRST in the camera's chain, so a
493
+ vignette or scanlines you added stay sharp over the blocks, and it takes the
494
+ effect off again when it is done. Being a shader, it does nothing on Canvas,
495
+ where `state.transition` changes the state directly instead.
496
+
486
497
  ## Custom effects are dual-language
487
498
 
488
499
  A custom `ShaderEffect` supplies a fragment **body**, not a whole program. To run
@@ -582,8 +593,8 @@ string in your own module:
582
593
 
583
594
  ```js
584
595
  // with the level's other assets, not in the stage that uses them.
585
- // `preload()` rather than `load()` — preload is what warms the shaders it
586
- // brought in, and it can be called again per level, not just at boot
596
+ // `preload()` or a bundle's `load(id)`, not `load(asset)`: those two warm
597
+ // the shaders they bring in, and run again per level, not just at boot
587
598
  await loader.preload([
588
599
  { name: "ripples", type: "shader", src: {
589
600
  glsl: "assets/level2/ripples.glsl",
@@ -596,6 +607,14 @@ await loader.preload([
596
607
  `src` takes the dual-language pair, so one asset carries both realizations and
597
608
  the two backends cannot drift apart. `data` takes inline source the same way.
598
609
 
610
+ A level's bundle (20.14: `loader.load("level2", [...])` declares and loads
611
+ one, warming like `preload` but with no loading screen) is also the shader's
612
+ lifetime. Unloading the last bundle that lists it, or `loader.unload()` of the
613
+ shader asset itself, destroys the shared instance whatever still draws with
614
+ it, and those renderables then draw without the effect. An effect that
615
+ outlives a level (a HUD's, a camera's) belongs in a bundle that outlives it
616
+ too; see `melonjs-loading-assets`.
617
+
599
618
  With `prewarm` on (the default since 20.6), `loader.preload()` warms on its way
600
619
  through — automatically, on every call — and compiles the GPU-side objects for
601
620
  every shader it holds — on WebGPU that is the
@@ -632,7 +651,9 @@ variants are covered by the loading-screen warm-up and by the per-renderable one
632
651
  For refraction, distortion and anything that samples what is already drawn:
633
652
 
634
653
  - **`screen_texture`** — a sampler kept filled with everything drawn so far
635
- (GLSL: annotate the sampler `uniform sampler2D tex : screen_texture;`)
654
+ (GLSL: annotate the sampler `uniform sampler2D tex : screen_texture;`, or
655
+ `: screen_texture(repeat)` to sample it wrapping; `repeat-x` and
656
+ `repeat-y` work too)
636
657
  - **`screen_uv`** — this fragment's 0..1 position in that capture
637
658
  - **`noise_uv`** — 0..1 across the sprite regardless of its atlas frame, and
638
659
  across the renderable when it has several effects (it does not slide with
@@ -650,7 +671,11 @@ Two places where a straight port really does differ:
650
671
  into a `Texture2d` you hand to `setTexture` — the supported replacement for
651
672
  `readPixels` tricks. The GL capture is bottom-up (GLSL bodies sample it with
652
673
  `1.0 - uv.y`) and opaque RGB; the WebGPU capture is top-down (the WGSL twin
653
- must *not* flip) and preserves alpha.
674
+ must *not* flip) and preserves alpha. It holds what was drawn when it was
675
+ called, so call it right before the draw that samples it. By default it
676
+ refills one shared, renderer-owned texture; `toFrameTexture({ target: null })`
677
+ hands back a capture of your own, for two alive at once, which you
678
+ `destroy()` when done.
654
679
  - **Vertical UV offsets** — a drop shadow, a directional smear. The WebGL pooled
655
680
  multi-effect path composites through bottom-up FBOs, so declare a `uUVYDir`
656
681
  uniform (initialise it to `1.0`) and multiply vertical offsets by it; the
@@ -724,10 +749,14 @@ capture plus one composite **per draw**: `difference`, `overlay`, `hard-light`,
724
749
  accents; expensive for hundreds of objects. The Canvas renderer reaches all
725
750
  eight through `globalCompositeOperation` instead, at no extra cost.
726
751
 
727
- Anything else collapses to `"normal"`, and so do 3D meshes and `Gradient` fills
728
- whatever you ask for — the latter with a one-time console warning.
729
- `renderer.setBlendMode(mode)` returns the mode it actually applied, so comparing
730
- that against your request detects the fallback.
752
+ Anything else collapses to `"normal"`. `renderer.setBlendMode(mode)` returns
753
+ the mode it actually applied, so comparing that against your request detects
754
+ an unknown name, and `none` on Canvas.
755
+
756
+ Two kinds of draw cannot take the eight composited modes on the GPU backends:
757
+ a 3D mesh and a `Gradient` fill draw them as `"normal"`, with a one-time
758
+ console warning naming which. `setBlendMode` cannot see that one, since it
759
+ happens at the draw, and still reports the mode you asked for.
731
760
 
732
761
  ## The Canvas fallback
733
762
 
@@ -758,6 +787,7 @@ same question after construction.
758
787
 
759
788
  | symptom | cause |
760
789
  |---|---|
790
+ | a camera's post effect hides another camera drawn before it | before 20.13 a later camera's pass replaced the whole frame; update. Since then each camera's effects apply to what it draws, over the cameras before it |
761
791
  | effect does nothing, warning in console | Canvas fallback — no programmable pipeline |
762
792
  | effect does nothing on some machines only | GLSL-only shader, `video.AUTO` chose WebGPU |
763
793
  | a white or solid box where the effect should be | carrier renderable drawn while the effect is disabled |
@@ -766,9 +796,11 @@ same question after construction.
766
796
  | ported shader renders upside down | a hand-bound `toFrameTexture()` capture — GL is bottom-up, WebGPU top-down |
767
797
  | shadow/smear offset flips on some draws | vertical UV offset not multiplied by `uUVYDir` |
768
798
  | frame rate collapses with many blended sprites | an *advanced* mode (overlay, darken, …) — each draw is a capture plus a composite |
799
+ | `overlay` (or another composited mode) does nothing on a mesh or a gradient fill, with a console warning | those two draws take the eight composited modes as `"normal"` on the GPU backends |
800
+ | an effect from `loader.getShader()` stops drawing after a level change | its asset, or the last bundle listing it, was unloaded, which destroys the shared instance |
769
801
  | many sprites with the same effect still cost a draw each | an effect instance per sprite, or a uniform set to a different value per sprite — share one instance and group sprites by value |
770
802
  | animated shader never moves | `setTime` fed milliseconds, or the shader declares no `uTime` |
771
- | squash and stretch only bulges, the height never changes, under Matter | the adapter rewrites `currentTransform` every step; set the scale from the sprite's `update()` |
803
+ | squash and stretch only bulges, the height never changes, under matter, planck or Rapier (or a rotating built-in body) | the adapter rewrites `currentTransform` every step; set the scale from the sprite's `update()` |
772
804
  | `SquishEffect` squishes the whole screen instead of the sprite | it is chained with another effect, or on the camera; give the sprite it alone |
773
805
  | syntax error in a file whose shader you just edited, pointing at a line of GLSL | a backtick in the shader body ended the template literal — most often one wrapped around an identifier in a comment |
774
806
 
@@ -55,7 +55,7 @@ game events in a `.ts` project.
55
55
  | event | when |
56
56
  |---|---|
57
57
  | `GAME_BEFORE_UPDATE` / `GAME_UPDATE` / `GAME_AFTER_UPDATE` | around the world update — `GAME_UPDATE` is **not** emitted while paused |
58
- | `GAME_BEFORE_DRAW` / `GAME_AFTER_DRAW` | around the draw pass |
58
+ | `GAME_BEFORE_DRAW` / `GAME_AFTER_DRAW` | around the draw pass, on frames that redraw: one where no update reported a change is not drawn |
59
59
  | `WORLD_STEP` | after the physics step |
60
60
  | `TICK` | the raw engine tick |
61
61
 
@@ -67,7 +67,7 @@ game events in a `.ts` project.
67
67
  | `STATE_CHANGE`, `STATE_PAUSE`, `STATE_RESUME`, `STATE_STOP` | state machine |
68
68
  | `STAGE_RESET` | a stage was reset |
69
69
  | `LEVEL_LOADED` | a Tiled level finished loading |
70
- | `LOADER_PROGRESS`, `LOADER_COMPLETE`, `LOADER_ERROR` | asset loading |
70
+ | `LOADER_PROGRESS`, `LOADER_COMPLETE`, `LOADER_ERROR` | asset loading: every `loader.preload()`, and a bundle loaded with `loader.load(id)` (20.14); not one asset loaded with `loader.load(descriptor)` |
71
71
  | `BLUR` / `FOCUS` | the page lost or regained focus |
72
72
  | `CANVAS_ONRESIZE`, `VIEWPORT_ONRESIZE` | relayout hooks |
73
73
  | `POINTERMOVE` | global pointer movement |
@@ -87,7 +87,7 @@ right times and there is nothing to unsubscribe:
87
87
  | callback | when |
88
88
  |---|---|
89
89
  | `onActivateEvent()` | `addChild`-ed into a container that is already attached to the world root (a `Container` forwards it to its children) |
90
- | `onDeactivateEvent()` | removed from its container — including the automatic world clear on a state change |
90
+ | `onDeactivateEvent()` | removed from its container — including the automatic world clear on a state change. Once per `onActivateEvent`: the children of a removed container, at any depth, are told once |
91
91
  | `onDestroyEvent()` | `destroy()` ran; called with no arguments from the container-removal path |
92
92
  | `onCollisionStart` / `onCollisionActive` / `onCollisionEnd` | contacts |
93
93
 
@@ -153,7 +153,10 @@ Use `removeChild()`, or flag the work and drain it from `Stage.update`.
153
153
  | crash during a stage switch | callback ran mid-teardown — guard with `state.isCurrent` |
154
154
  | crash after a collision | `removeChildNow()` inside a contact callback — use `removeChild()` |
155
155
  | progress callbacks never fire | assigned `loader.onProgress` (removed in 20.3) instead of subscribing to `LOADER_PROGRESS` |
156
+ | a `LOADER_COMPLETE` handler (a `state.change` to the first screen) runs again mid-game | a later `preload()`, or a level's bundle loaded with `loader.load(id)`, emits it too; subscribe with `event.once`, or use `preload()`'s promise or callback |
156
157
  | `onActivateEvent` never runs | the child was added to a container not yet attached to the world |
158
+ | `onDeactivateEvent` runs twice when a group is removed (a count goes negative, a light or listener is released twice) | the group's `onDeactivateEvent` and its `destroy()` both reached the children, before 20.13.0 — now once per `onActivateEvent` |
159
+ | `onDeactivateEvent` never runs on a child of a group built off-world and destroyed | it never had `onActivateEvent` either: the pair is only sent to children that entered the world |
157
160
 
158
161
  ## Related skills
159
162
 
@@ -137,7 +137,6 @@ with the formats you ship before any audio asset is preloaded.
137
137
  ```js
138
138
  import { audio, loader } from "melonjs";
139
139
 
140
- loader.setOptions({ crossOrigin: "anonymous" }); // needed for remote assets
141
140
  audio.init("mp3,ogg"); // BEFORE preloading audio
142
141
  await loader.preload([
143
142
  { name: "tileset", type: "image", src: "data/img/tileset.png" },
@@ -145,6 +144,11 @@ await loader.preload([
145
144
  ]);
146
145
  ```
147
146
 
147
+ A flat list suits a single-screen game. A game with several levels exports a
148
+ record of bundles instead — `{ common: [...], level1: [...], level2: [...] }`,
149
+ passed to the same `loader.preload()` — so each level's assets can later be
150
+ freed and loaded again by id; see `melonjs-loading-assets`.
151
+
148
152
  Two ordering rules that produce confusing failures when missed:
149
153
 
150
154
  - **`audio.init()` must run before any audio asset is preloaded**, and names the
@@ -173,7 +177,7 @@ placement.
173
177
  | scene content appears once and never resets | scene built in the constructor instead of `onResetEvent` |
174
178
  | everything offset by half a sprite | top-left math against the centred `anchorPoint` default |
175
179
  | a `Tween` or `ParticleEmitter` does nothing | `.start()` / `streamParticles()` never called |
176
- | cross-origin textures fail to load | missing `loader.setOptions({ crossOrigin: "anonymous" })` |
180
+ | an asset on another origin fails to load, the error naming it | that server sends no `Access-Control-Allow-Origin` header (see melonjs-loading-assets) |
177
181
 
178
182
  ## Related skills
179
183
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-input
3
- description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, virtual gamepad, touch controls, joystick, gamepad-plugin, KEYDOWN, KEYUP, press any key, splash screen, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
3
+ description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, bindPointer, mouse button, vibrateGamepad, rumble, activeGamepad, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, virtual gamepad, touch controls, joystick, gamepad-plugin, floating stick, floating button, floating joystick, two thumbs, tap to fire, hideAtRest, mobile controls, KEYDOWN, KEYUP, press any key, splash screen, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -54,12 +54,37 @@ narrows it; it also repeats while the key is held, so guard anything that must
54
54
  happen once. Keep polling for movement and for a menu cursor, where one step
55
55
  per press and a key held across a state change are the things that matter.
56
56
 
57
+ When the window loses the focus or the page is hidden, every key and
58
+ `bindPointer` button still held is released, since its real release goes to
59
+ another window: polling sees it up when the player comes back, and a key still
60
+ held then is pressed again by its next `keydown`. No `KEYUP` is sent for it,
61
+ so code moved by `KEYDOWN` / `KEYUP` rather than by polling should also listen
62
+ to `event.BLUR`. A gamepad's presses are left to the gamepad, which is read
63
+ every frame.
64
+
57
65
  The full signature is `bindKey(keyCode, action, lock?, preventDefault?)`;
58
66
  `preventDefault` defaults to `input.preventDefault` (itself `true`). Pass the
59
67
  fourth argument per binding, or change the global default with
60
68
  `input.setPreventDefault(false)` — **assigning `input.preventDefault = false`
61
69
  does not work**, it is a read-only module binding.
62
70
 
71
+ ### A mouse button as a key
72
+
73
+ `bindPointer` makes a mouse button press a bound key, so a click fires the same
74
+ action and `isKeyPressed` sees it:
75
+
76
+ ```js
77
+ input.bindKey(input.KEY.X, "fire");
78
+ input.bindPointer(input.KEY.X); // the left button
79
+ input.bindPointer(input.pointer.RIGHT, input.KEY.X); // or another button
80
+ input.unbindPointer(input.pointer.RIGHT); // LEFT when omitted
81
+ ```
82
+
83
+ As with `bindGamepad`, the key needs its `bindKey` first, or `bindPointer`
84
+ throws `no action defined for keycode N`. Turning the wheel neither presses
85
+ nor releases a bound button's key (before 20.14 it released the left
86
+ button's).
87
+
63
88
  ## Pointer events need `isKinematic = false`
64
89
 
65
90
  **This is the trap.** `Renderable.isKinematic` defaults to `true`, and both the
@@ -82,9 +107,11 @@ class Button extends Renderable {
82
107
  ```
83
108
 
84
109
  `Container`, `Camera2d`, `Draggable`, `DropTarget` and the `UI*` elements set it
85
- to `false` for you, and so does attaching a physics body (`Body`'s constructor
86
- sets `ancestor.isKinematic = false`) — so plain `Renderable` and `Sprite`
87
- subclasses with no body are the ones that catch people.
110
+ to `false` for you, and so does giving it a physics body (a `bodyDef` on any
111
+ adapter, or a `Body`) — so plain `Renderable` and `Sprite` subclasses with no
112
+ body are the ones that catch people. Before melonJS 20.14, matter-adapter
113
+ 1.6.0 and planck-adapter 1.7.0, a `bodyDef` under matter or planck left
114
+ `isKinematic` at `true`, so set it yourself there.
88
115
 
89
116
  Register in `onActivateEvent` and release in `onDeactivateEvent`, not in the
90
117
  constructor — pooled objects are reused and would otherwise accumulate handlers.
@@ -103,6 +130,11 @@ throw `invalid event type`. melonJS maps the canonical name onto whichever
103
130
  mouse/touch events the device actually supports, so you always register the
104
131
  pointer name.
105
132
 
133
+ A region can listen to `"pointerup"` alone, to act on release: it gets the
134
+ release of a press made on it. With several fingers on one region, each finger
135
+ that pressed it gets its own `pointerup` / `pointercancel`; `pointerenter` and
136
+ `pointerleave` still follow one pointer per region.
137
+
106
138
  ## Which object gets the event
107
139
 
108
140
  The dispatcher collects the regions near the pointer from the broadphase,
@@ -182,9 +214,10 @@ globalThis.addEventListener("pointerup", onRelease);
182
214
 
183
215
  For plain drag-and-drop, `Draggable` / `DropTarget` already wire all of this up
184
216
  (`pointerdown` / `pointerup` / `pointercancel` regions plus a global
185
- `POINTERMOVE` listener) and emit `event.DRAGSTART` / `event.DRAGEND`. `DRAGEND`
186
- still originates from a region handler, so it only fires when the release lands
187
- on the draggable itself.
217
+ `POINTERMOVE` listener) and emit `event.DRAGSTART` / `event.DRAGEND`, and
218
+ follow the pointer under any camera, scrolled or zoomed. `DRAGEND` still
219
+ originates from a region handler, so it only fires when the release lands on
220
+ the draggable itself.
188
221
 
189
222
  ## Gamepad
190
223
 
@@ -252,10 +285,10 @@ input.hasGamepadVibration(index); // for an options-screen toggle
252
285
 
253
286
  Safe to call blind: no pad, no motors, or no Gamepad API at all resolves
254
287
  `false` and does nothing, so game code never branches on support. Magnitudes
255
- default to **full** — the underlying web API defaults every parameter to zero,
256
- which makes a bare `playEffect` a silent no-op. A second call preempts the
257
- first on the same pad, so a rapid series of hits reads as a rapid series of
258
- hits rather than as a queue. For a SUSTAINED rumble issue one effect with a
288
+ default to **full** and `duration` to 200 ms — the underlying web API defaults
289
+ every parameter to zero, which makes a bare `playEffect` a silent no-op. A
290
+ second call preempts the first on the same pad, so a rapid series of hits
291
+ reads as a rapid series of hits rather than as a queue. For a SUSTAINED rumble issue one effect with a
259
292
  long `duration` instead of calling every frame.
260
293
 
261
294
  Hot-plugging needs no polling of your own — the engine listens for the browser's
@@ -270,12 +303,15 @@ event.on(event.GAMEPAD_DISCONNECTED, (gamepad) => { /* pause, show a prompt */ }
270
303
 
271
304
  Bindings survive a disconnect: they are attached to the index, not to the
272
305
  device, so a pad unplugged and plugged back into the same slot keeps working.
306
+ A pad unplugged with a button or the stick held lets go of the keys bound to
307
+ them, as releasing them would.
273
308
 
274
309
  ## Virtual controls
275
310
 
276
- For on-screen touch controls, use `@melonjs/gamepad-plugin` rather than
277
- writing them: sticks and buttons drawn over the game and reported as a pad in
278
- a gamepad slot, so the `bindGamepad` bindings above answer to them unchanged.
311
+ For on-screen touch controls, use `@melonjs/gamepad-plugin` (it needs melonJS
312
+ 20.12 or later) rather than writing them: sticks and buttons drawn over the
313
+ game and reported as a pad in a gamepad slot, so the `bindGamepad` bindings
314
+ above answer to them unchanged.
279
315
 
280
316
  ```js
281
317
  import { GamepadPlugin } from "@melonjs/gamepad-plugin";
@@ -293,6 +329,45 @@ its own, a touch starting on the controls does not reach the game, and they
293
329
  hide while a real pad is in use. `plugin.cache.gamepad.visible` says whether
294
330
  they show, to word a prompt ("tap" or a key name).
295
331
 
332
+ Sticks and buttons are fixed in a corner by default. **`floating: true`**
333
+ puts one under the finger wherever it lands inside its `area` (`"half"`, its
334
+ own half of the screen, by default; `"screen"`; or `{ x, y, width, height }`),
335
+ and **`hideAtRest: true`** draws it only while held. The usual mobile
336
+ two-thumb layout, nothing on screen until touched (1.1):
337
+
338
+ ```js
339
+ plugin.register(GamepadPlugin, "gamepad", {
340
+ sticks: [{ floating: true, hideAtRest: true }], // left half
341
+ buttons: [{ floating: true, hideAtRest: true, label: "A" }], // right half
342
+ });
343
+ ```
344
+
345
+ Or one thumb for both: a stick over the whole screen whose quick tap (lifted
346
+ within `tap.time`, 250 ms, without leaning to `dpadThreshold`) presses a key
347
+ or button for one frame, and no button at all:
348
+
349
+ ```js
350
+ plugin.register(GamepadPlugin, "gamepad", {
351
+ sticks: [{ floating: true, area: "screen", hideAtRest: true,
352
+ tap: { key: input.KEY.SPACE } }],
353
+ buttons: 0,
354
+ });
355
+ ```
356
+
357
+ Where areas overlap, a finger takes the first free control there: fixed
358
+ buttons, fixed sticks, floating buttons, floating sticks. So a floating button
359
+ on its `"half"` carves the right half out of a `"screen"` stick, and several
360
+ floating buttons sharing an area split it by distance to where each rests.
361
+ Every press lasts at least one frame, so a 15 ms thumb tap still reaches
362
+ `isKeyPressed`, and Safari's pinch gesture is cancelled while the controls
363
+ show, so a second thumb works on iOS.
364
+
365
+ To match the game's artwork (1.2), pass an already-loaded `TextureAtlas` as
366
+ `skin` to `GamepadPlugin`, with `base` / `thumb` region names on sticks
367
+ and `region` / optional `pressed` names on buttons. Omitted parts keep
368
+ their shapes. Regions scale to the controls' sizes without changing hit areas;
369
+ labels, floating controls, opacity, hiding and dimming keep working.
370
+
296
371
  A single key can also be pressed directly, reaching the same actions:
297
372
 
298
373
  ```js
@@ -305,17 +380,28 @@ input.triggerKeyEvent(input.KEY.LEFT, false); // release
305
380
  | symptom | cause |
306
381
  |---|---|
307
382
  | pointer handler never fires | `isKinematic` left at its `true` default |
383
+ | touches never arrive where there is no DOM (nx.js, a Web Worker) | melonJS older than 20.14 listened for mouse events there; update. Touch is then read from `TouchEvent`s dispatched on the canvas given as `settings.canvas` |
308
384
  | handlers fire twice, or on dead objects | registered in the constructor instead of `onActivateEvent` |
309
385
  | camera drag jumps around | using world coordinates instead of `gameScreenX`/`gameScreenY` |
310
386
  | `gameX` holds screen coords where world coords were expected | read on `event.POINTERMOVE`, or on a floating region — use `gameWorldX`/`gameWorldY` |
311
387
  | drag stops when leaving the object | region events end at the region — use `event.POINTERMOVE` |
312
388
  | release never detected outside the region | no global pointer-up event — add a raw listener |
313
389
  | jump fires every frame while held | `bindKey` without its third `lock` argument |
390
+ | the hero keeps walking after an Alt-Tab, until the key is pressed again (before 20.14) | the key's release went to another window: update, held keys are now released on blur |
391
+ | a region with only a `"pointerup"` callback never gets it, or the first finger's release is lost with two on one region (before 20.14) | the release was only delivered to the last pointer to press the region: update |
392
+ | a key bound with `bindPointer` never fires once the mouse has entered the canvas (before 20.12) | the pointer's button bindings were replaced on each event: update |
393
+ | firing (a key bound with `bindPointer`) stops while the button is held and the wheel turns (before 20.14) | a wheel event released the main button's key: update |
394
+ | clicks land off target after browser zoom or a move to another screen (before 20.14) | the pointer was scaled by the live pixel ratio against a canvas sized with the old one: update |
395
+ | a `Draggable` jumps away when grabbed, and stays stuck dragging, once the camera has moved (before 20.14) | it mixed screen and world coordinates: update |
314
396
  | `invalid event type` thrown at registration | non-pointer name (`"click"`, `"mousedown"`, …) passed to `registerPointerEvent` |
315
- | `no action defined for keycode N` | `bindGamepad` called before `bindKey` for that keycode |
397
+ | `no action defined for keycode N` | `bindGamepad` or `bindPointer` called before `bindKey` for that keycode |
398
+ | floating stick ignores the right half of the screen | its `area` defaults to `"half"`, its own side — use `area: "screen"` |
399
+ | `area: "screen"` stick still ignores part of the screen | a floating button's area comes first there — the overlap order is fixed buttons, fixed sticks, floating buttons, floating sticks |
400
+ | a quick tap on a stick moves instead of acting | it leaned past `dpadThreshold` or outlasted `tap.time` before lifting |
316
401
  | stick has to be pushed well past its `threshold` before it registers | `threshold` is ADDED to the deadzone, not used instead of it |
317
402
  | one half of a stick does nothing | each half is a separate `bindGamepad`, chosen by the sign of `threshold` |
318
- | a widget under an overlapping panel still reacts | the panel consumes nothing — return `false` from its `onClick` and `onOver` |
403
+ | a key stays pressed after its pad is unplugged, or a stick bound on one side holds its key after coming back to the centre (before 20.12) | update: an unplugged pad lets go of what it held, and a stick lets go when it returns to rest, even just past the centre |
404
+ | a widget under an overlapping panel still lights up | the panel consumes no hover: return `false` from its `onOver` (presses already stop at UI elements; `UIPanel` with `blocking` does both) |
319
405
  | a widget stays highlighted after the pointer moves onto a panel over it | the panel consumes no `pointermove`, so nothing takes the pointer off the widget |
320
406
  | a child's big `z` does not put it in front of another container | `pos.z` is container-local — use `ancestor.moveToTop()` on the container |
321
407
  | every click in the game stops working | a handler on `app.viewport` returning `false`, which is asked first |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-lighting
3
- description: "Use this skill for 2D dynamic lighting in melonJS — Light2d, ambient light on a Stage, multiple lights, and per-pixel normal-mapped sprites authored in SpriteIlluminator. Covers specular highlights via shininess, the ambient-cutout model, which parts need a GPU backend, and why Light2d does not survive a Camera3d. Triggers on: Light2d, specular, shininess, highlight, glint, lighting, ambientLight, ambientLightingColor, normalMap, SpriteIlluminator, per-pixel lighting, drawLight, torch, glow, darkness."
3
+ description: "Use this skill for 2D dynamic lighting in melonJS — Light2d, ambient light on a Stage, multiple lights, and per-pixel normal-mapped sprites authored in SpriteIlluminator. Covers specular highlights via shininess, the ambient-cutout model, which parts need a GPU backend, and why Light2d does not survive a Camera3d. Triggers on: Light2d, softness, soft light, lightHeight, setRadii, specular, shininess, highlight, glint, lighting, ambientLight, ambientLightingColor, normalMap, SpriteIlluminator, per-pixel lighting, drawLight, torch, glow, darkness."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -31,6 +31,11 @@ intensity = 0.7)`, and `pos` is the light's **centre** — its `anchorPoint` is
31
31
  `(0.5, 0.5)`, and `centerX` / `centerY` are overridden to return `pos` directly.
32
32
  Give `radiusY` a different value for a stretched, elliptical light.
33
33
 
34
+ Resize a light with `light.setRadii(radiusX, radiusY)`, which resizes its
35
+ bounds too. Writing `radiusX` alone changes nothing you can see: the glow, the
36
+ hole in the darkness and the normal-map shading are all sized from the bounds,
37
+ on every backend.
38
+
34
39
  A `Light2d` added to the world **registers itself with the stage's ambient
35
40
  overlay on activate**, so you add it like any other renderable and it takes part
36
41
  in the lighting pass. It draws additively (`blendMode = "lighter"`); set
@@ -105,6 +110,11 @@ const hero = new Sprite(x, y, {
105
110
  canvas, `ImageBitmap`, a `Texture2d`). Assigning anything else — including an
106
111
  `HTMLVideoElement`, which would freeze on frame 0 — throws a `TypeError`.
107
112
 
113
+ How grazing the light is comes from `light.lightHeight`, its height above the
114
+ sprite plane in the same units as its radius (default `max(radiusX, radiusY) *
115
+ 0.075`). Lower it for light raking across the surface, which shows the relief
116
+ off; raise it for flatter, more even shading.
117
+
108
118
  Normal maps are typically authored in **SpriteIlluminator**. The sidecar pattern
109
119
  above — one `_n` image beside each diffuse image — is the simplest setup, but an
110
120
  atlas can carry one too: `new TextureAtlas(json, image, { normalMap })` pairs a
@@ -209,6 +219,8 @@ See the `melonjs-3d` skill.
209
219
  | sprite not shaded by a light, only glowed over | no `normalMap` on the sprite, or the Canvas backend is active |
210
220
  | normal-mapped sprite is pure black where unlit | `Stage.ambientLightingColor` left at black |
211
221
  | the 33rd light shades nothing | `MAX_LIGHTS = 32` for the normal-map pipeline |
222
+ | changing `radiusX` / `radiusY` does nothing | everything is sized from the bounds, which `setRadii()` updates and a bare field write does not |
223
+ | normal-mapped relief looks flat under a light | `lightHeight` is high relative to the radius; lower it for a grazing light |
212
224
  | `TypeError` assigning `normalMap` | not an image-like value (a video element is rejected outright) |
213
225
  | lighting misaligned under a 3D camera | the 2D light pass is not projection-aware — use `Light3d` |
214
226