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
@@ -34,25 +34,25 @@ dependencies.
34
34
  | [melonjs-scenes-and-state](../melonjs-scenes-and-state/SKILL.md) | `Stage` subclasses, the state manager, transitions, pausing, the update loop, timers, tweens, saved data. |
35
35
  | [melonjs-renderables](../melonjs-renderables/SKILL.md) | Subclassing `Renderable`, custom `draw()`, draw order, anchors, `floating`, post effects on an object. |
36
36
  | [melonjs-sprites-and-animation](../melonjs-sprites-and-animation/SKILL.md) | `Sprite`, frame animation, texture atlases (TexturePacker / Aseprite / ShoeBox), tint, flip, pooling. |
37
- | [melonjs-input](../melonjs-input/SKILL.md) | Keyboard actions, pointer and touch handling, drag, gamepad, virtual controls. |
38
- | [melonjs-physics](../melonjs-physics/SKILL.md) | Collision, bodies, movement, spatial queries, and the planck / matter adapters. |
37
+ | [melonjs-input](../melonjs-input/SKILL.md) | Keyboard actions, pointer and touch handling, drag, gamepad, virtual controls (fixed or floating sticks and buttons, tap to act). |
38
+ | [melonjs-physics](../melonjs-physics/SKILL.md) | Collision, bodies, movement, spatial queries, and the planck / matter / Rapier adapters. |
39
39
  | [melonjs-math](../melonjs-math/SKILL.md) | Vectors and matrices: what mutates, what allocates, and interpolating with lerp, slerp, damp or moveTowards. |
40
- | [melonjs-tilemaps](../melonjs-tilemaps/SKILL.md) | Tiled maps — TMX/TSX loading, spawning entities from objects, collision layers, isometric maps. |
40
+ | [melonjs-tilemaps](../melonjs-tilemaps/SKILL.md) | Tiled maps — TMX/TSX loading, spawning entities from objects, collision layers, isometric maps, reading and changing tiles at run time (a tile layer as the game board). |
41
41
  | [melonjs-audio](../melonjs-audio/SKILL.md) | Sound effects, music, audio sprites, spatial audio, procedural tone and noise. |
42
42
  | [melonjs-effects-and-shaders](../melonjs-effects-and-shaders/SKILL.md) | Post effects, custom GLSL/WGSL shaders, blend modes, colour grading, screen capture. |
43
43
  | [melonjs-3d](../melonjs-3d/SKILL.md) | Anything 3D or 2.5D — `Camera3d`, meshes, instancing, `Sprite3d` billboards, `Light3d`, distance fog, glTF scenes. |
44
44
  | [melonjs-3d-assets](../melonjs-3d-assets/SKILL.md) | Loading glTF/GLB or OBJ models — materials, imported lights, instancing, and what is not supported. |
45
45
  | [melonjs-camera-and-drawing](../melonjs-camera-and-drawing/SKILL.md) | Camera follow, bounds, shake and fade, coordinate conversion, and immediate-mode shape drawing. |
46
- | [melonjs-ui-and-text](../melonjs-ui-and-text/SKILL.md) | HUDs, buttons, menus, drag-and-drop, `Text` and `BitmapText`, web fonts, panels. |
46
+ | [melonjs-ui-and-text](../melonjs-ui-and-text/SKILL.md) | HUDs, menus and options screens — buttons, checkboxes, sliders, progress bars, laid-out and scrolling panels — drag-and-drop, `Text` and `BitmapText`, web fonts. |
47
47
  | [melonjs-particles-and-trails](../melonjs-particles-and-trails/SKILL.md) | `ParticleEmitter`, burst versus stream, `referenceSpace`, and the `Trail` renderable. |
48
48
  | [melonjs-lighting](../melonjs-lighting/SKILL.md) | 2D dynamic lighting — `Light2d`, stage ambient, normal-mapped sprites. |
49
- | [melonjs-deployment](../melonjs-deployment/SKILL.md) | Scaffolding a project, building, browser support and transpiling, native and mini-game platforms. |
50
- | [melonjs-loading-assets](../melonjs-loading-assets/SKILL.md) | Resource descriptors, the asset type list, base URLs, cross-origin, progress events, unloading. |
49
+ | [melonjs-deployment](../melonjs-deployment/SKILL.md) | Scaffolding a project, building, browser support and transpiling, native and mini-game platforms, running with no DOM (nx.js on the Nintendo Switch, Web Workers). |
50
+ | [melonjs-loading-assets](../melonjs-loading-assets/SKILL.md) | Resource descriptors, the asset type list, a multi-level game's assets as bundles (`common` + one per level), loading and freeing a level's assets, base URLs, cross-origin, progress events. |
51
51
  | [melonjs-renderer-backends](../melonjs-renderer-backends/SKILL.md) | A feature works on one machine but not another; choosing or pinning a backend; degrading gracefully. |
52
52
  | [melonjs-display-and-scaling](../melonjs-display-and-scaling/SKILL.md) | Blurry, soft or cropped output; the design resolution, how the canvas fills its page, and pixel-perfect integer scaling. |
53
53
  | [melonjs-events](../melonjs-events/SKILL.md) | The event bus, per-frame and lifecycle events, and subscribing without leaking. |
54
54
  | [melonjs-performance](../melonjs-performance/SKILL.md) | Dropped frames, draw-call counts, pooling, batching, culling, baking, profiling. |
55
- | [melonjs-plugins](../melonjs-plugins/SKILL.md) | Registering the debug, Spine or Tiled-inflate plugins, physics adapters, or writing your own plugin. |
55
+ | [melonjs-plugins](../melonjs-plugins/SKILL.md) | Registering the debug, Spine, Tiled-inflate, gamepad (touch controls) or Capacitor plugins, installing a physics adapter, or writing your own plugin. |
56
56
 
57
57
 
58
58
  ## Escape hatch: the full API index
@@ -97,7 +97,9 @@ Whatever the task, these produce code that runs and is wrong:
97
97
  it after does not resort (except under `Camera3d`).
98
98
  3. **`isKinematic` defaults to `true`**, which silently excludes a renderable
99
99
  from pointer events and from the physics broadphase. Plain `Renderable` and
100
- `Sprite` subclasses need `this.isKinematic = false`.
100
+ `Sprite` subclasses with no body need `this.isKinematic = false`; adding a
101
+ body sets it for you, on every adapter (matter-adapter before 1.6 and
102
+ planck-adapter before 1.7 did not).
101
103
 
102
104
  ## Companion packages
103
105
 
@@ -109,10 +111,11 @@ Tiled inflate plugin are *required* for their use cases, not merely convenient.
109
111
  |---|---|
110
112
  | [`@melonjs/planck-adapter`](https://www.npmjs.com/package/@melonjs/planck-adapter) | You want full rigid-body dynamics — stacking, joints, realistic restitution. Pass it as the Application's `physic` setting; the portable `raycast` and `queryAABB` calls work unchanged across adapters (`querySphere` and `raycast3d` are built-in-adapter only). |
111
113
  | [`@melonjs/matter-adapter`](https://www.npmjs.com/package/@melonjs/matter-adapter) | Same, backed by matter-js. Choose one adapter per game. |
114
+ | [`@melonjs/rapier2d-adapter`](https://www.npmjs.com/package/@melonjs/rapier2d-adapter) | Same again, backed by Rapier compiled to WebAssembly: many bodies, per-body continuous collision, a character controller. About 3 MB loaded on demand, so make it with `await RapierAdapter.create()`. |
112
115
  | [`@melonjs/tiled-inflate-plugin`](https://www.npmjs.com/package/@melonjs/tiled-inflate-plugin) | **Required** to load gzip-, zlib- or zstd-compressed Tiled maps. Without it, parsing such a map throws `No inflate function set`. |
113
116
  | [`@melonjs/spine-plugin`](https://www.npmjs.com/package/@melonjs/spine-plugin) | Skeletal animation authored in Spine, as a melonJS renderable. |
114
117
  | [`@melonjs/debug-plugin`](https://www.npmjs.com/package/@melonjs/debug-plugin) | An in-game debug panel — FPS, draw counts, collision-shape and bounds overlays. Register it after `app.init()`. |
115
- | [`@melonjs/gamepad-plugin`](https://www.npmjs.com/package/@melonjs/gamepad-plugin) | On-screen touch controls for phones and tablets: sticks and buttons reported as a gamepad, so the game's `bindGamepad` bindings drive it. Use it rather than writing virtual controls. |
118
+ | [`@melonjs/gamepad-plugin`](https://www.npmjs.com/package/@melonjs/gamepad-plugin) | On-screen touch controls for phones and tablets: sticks and buttons reported as a gamepad, so the game's `bindGamepad` bindings drive it; fixed or floating under the thumb, hidden until touched, tap to act. Use it rather than writing virtual controls. |
116
119
  | [`@melonjs/capacitor-plugin`](https://www.npmjs.com/package/@melonjs/capacitor-plugin) | Wrapping a game as a native iOS/Android app: it bridges Capacitor's lifecycle — pause/resume, the hardware back button, orientation lock, splash screen. |
117
120
  | [`create-melonjs`](https://www.npmjs.com/package/create-melonjs) | Scaffolding a new project — start here rather than assembling a build by hand. |
118
121
 
@@ -136,7 +139,7 @@ const app = new Application(800, 600, {
136
139
  parent: "screen",
137
140
  // gravity is in pixels/s²; pixelsPerMeter converts it to the adapter's
138
141
  // metres. Both are shown at their defaults; subSteps defaults to 1
139
- physic: new PlanckAdapter({
142
+ physic: await PlanckAdapter.create({
140
143
  gravity: { x: 0, y: 320 },
141
144
  pixelsPerMeter: 32,
142
145
  subSteps: 2,
@@ -145,15 +148,36 @@ const app = new Application(800, 600, {
145
148
  await app.init();
146
149
  ```
147
150
 
151
+ Every adapter is made the same way, with `await X.create(options)`. A running
152
+ game switches adapter with `app.world.setAdapter(await X.create(options))`,
153
+ which rebuilds every body from its `bodyDef`; `world.adapter` itself is
154
+ read-only. `melonjs-physics` says which adapter suits which game.
155
+
148
156
  `melonjs-plugins` covers the registry, the version gate and writing your own.
149
157
 
150
158
  ## Prefer built-in features
151
159
 
152
160
  melonJS covers a lot that is easy to reimplement badly. Before hand-rolling,
153
- check for: `ParticleEmitter` and `Trail`, `Tween` with easing, the built-in
154
- `ShaderEffect` presets (vignette, scanline, chromatic aberration, glow, dissolve
155
- and more), `Light2d` / `Light3d`, `UIBaseElement` / `UISpriteElement` /
156
- `UITextButton`, `NineSliceSprite`, `TextureAtlas` (TexturePacker, ShoeBox,
157
- Aseprite), `BitmapText`, `CanvasRenderTarget` for bake-once drawing,
158
- `NoiseTexture2d`, `viewport.shake` / `fadeIn` / `fadeOut`, and `save` for
159
- localStorage-backed persistence.
161
+ check for:
162
+
163
+ - **Effects and motion:** `ParticleEmitter`, `Trail` / `Trail3d`, `Tween` with
164
+ easing, the built-in `ShaderEffect` presets (vignette, scanline, chromatic
165
+ aberration, glow, outline, dissolve, bloom and more), `Light2d` / `Light3d`,
166
+ `viewport.shake` / `fadeIn` / `fadeOut`.
167
+ - **UI:** `UIPanel` (laid out, scrolling, skinnable), `UITextButton`,
168
+ `UICheckbox` (a radio button with a `group`), `UISlider`, `ProgressBar`,
169
+ `UISpriteElement`, keyboard and gamepad navigation between widgets once one
170
+ has `focus()`, `NineSliceSprite`, `Draggable` / `DropTarget`, `BitmapText`.
171
+ - **Art:** `TextureAtlas` (TexturePacker, ShoeBox, Aseprite),
172
+ `CanvasRenderTarget` for bake-once drawing, `NoiseTexture2d`.
173
+ - **Game flow:** `state.transition()` (`"fade"`, `"mask"` or `"pixelate"`)
174
+ between screens, `state.pause()` / `state.resume()` behind a pause menu,
175
+ `save` for localStorage-backed persistence, `createPool` / `getPool` for
176
+ recycling.
177
+ - **Levels:** Tiled tile layers read and changed with `cellAt` / `setTile`,
178
+ and asset bundles (`loader.preload({ common: [...], level1: [...] })`, then
179
+ `loader.unload("level1")` / `loader.load("level1")`) to free and reload a
180
+ level's assets.
181
+ - **Physics and controls:** a rigid-body adapter (planck, matter or Rapier,
182
+ switched at run time with `world.setAdapter()`) rather than a hand-written
183
+ solver, and `@melonjs/gamepad-plugin` rather than hand-made touch sticks.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-20-migration
3
- description: "Use this skill when writing melonJS code that may be based on pre-20.x knowledge, when migrating a game from melonJS 19.x or earlier, or when an API you expected does not exist. Covers the mandatory async Application.init(), the WebGL 2 baseline and WebGPU backend, renderable post effects replacing the shader property, and the deprecated exports still present. Triggers on: migration, upgrade, melonJS 19, me.game, me.video.init, renderable.shader, Compositor, CanvasTexture, me.Math, deprecated."
3
+ description: "Use this skill when writing melonJS code that may be based on pre-20.x knowledge, when migrating a game from melonJS 19.x or earlier, or when an API you expected does not exist. Covers the mandatory async Application.init(), the WebGL 2 baseline and WebGPU backend, renderable post effects replacing the shader property, the deprecated exports still present, and what changed again during 20.x. Triggers on: migration, upgrade, melonJS 19, me.game, me.video.init, video.renderer, renderable.shader, Compositor, CanvasTexture, me.Math, pool.pull, pool.register, world.adapter, uvOffset, deprecated."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -43,7 +43,13 @@ none, and anything passed is ignored (settings come from the constructor).
43
43
  **`video.init(...)` no longer exists.** The `video` module now exports only the
44
44
  four renderer constants (`AUTO`, `CANVAS`, `WEBGL`, `WEBGPU`) — there is no
45
45
  `init` on it to call. Any code shaped like `me.video.init(800, 600, {...})` is
46
- from a removed API. Build an `Application` and await `init()`.
46
+ from a removed API. Build an `Application` and await `init()`. Its siblings
47
+ went with it: `video.renderer`, `video.createCanvas()` and `video.getParent()`
48
+ are now `app.renderer`, `app.renderer.createCanvas()` and
49
+ `app.getParentElement()`.
50
+
51
+ `init()` runs once: a second call is ignored with a console warning, and after
52
+ `app.destroy()` it throws. A destroyed `Application` is final; build a new one.
47
53
 
48
54
  Note that `import * as me from "melonjs"` still works, so `me.Sprite` style is
49
55
  fine. It is the *globals bootstrap* that is gone, not the namespace import.
@@ -77,6 +83,16 @@ Consequences:
77
83
  on either GPU backend.
78
84
  - Features needing a programmable pipeline are inert on Canvas and warn once
79
85
  rather than throwing.
86
+ - Test capabilities, not the backend: `renderer.shaderLanguage` and
87
+ `renderer.supportsDepthBuffer` replace checks on `renderer.gl` or
88
+ `instanceof WebGLRenderer`, which answer "no" on WebGPU.
89
+ `renderer.WebGLVersion` is always `2` (deprecated), and the `preferWebGL1`
90
+ setting and the `#webgl1` URL flag are gone.
91
+ - A custom WebGL batcher extends `WebGLBatcher` (`Batcher` is now the
92
+ backend-neutral base), and a custom shader on a `Mesh` receives model-space
93
+ geometry: position with
94
+ `uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertex, 1.0)` and
95
+ tint with `uTint`. Sprite and post-effect shaders are unchanged.
80
96
 
81
97
  ## 4. Deprecated exports still present
82
98
 
@@ -93,8 +109,14 @@ These resolve but should not be used in new code:
93
109
  | `renderable.shader = …` | 19.2.0 | `addPostEffect()` |
94
110
  | `Entity` | 18.1.0 | `Sprite` / `Renderable` + a `Body` |
95
111
  | `loader.onload` / `onProgress` / `onError` | 18.2.0, **removed 20.3** | the `LOADER_*` events, or `preload(assets, onloadcb)` |
96
- | `response.overlap` / `overlapN` / `overlapV` | — | `response.depth` / `response.normal` |
112
+ | `response.overlap` / `overlapN` / `overlapV` | 19.5.0 | `response.depth` / `response.normal` |
97
113
  | `setLineWidth()` | 17.3.0 | the `lineWidth` property |
114
+ | `pool.register` / `pool.pull` / `pool.push` | 18.0.0 (a console notice since 20.9) | `createPool()` / `getPool()`; `registerTiledObjectClass()` for a class a Tiled map names |
115
+ | `body.fromJSON()`, a shape list given to `addShape()` | 20.7.0 (the `Body` constructor form throws) | `bodyDef = { type, shapes: "<json asset>", id }` |
116
+ | `body.ignoreGravity` | 19.5.0 | `gravityScale = 0`, or `body.setGravityScale(0)` |
117
+ | `app.updateAverageDelta` | 20.0.0 | `app.lastUpdateDelta` |
118
+ | `loader.crossOrigin = …` / `loader.withCredentials = …` | 20.4.0 (assigning throws) | `loader.setOptions({ crossOrigin, withCredentials })` |
119
+ | `UITextButton#bitmapText` | 20.13.0 | `label` |
98
120
 
99
121
  Two of these deserve emphasis:
100
122
 
@@ -128,6 +150,25 @@ feature is used. Transpile if you need older browsers, and note that transpiling
128
150
  handles *syntax* only: built-in methods need separate polyfills, which a
129
151
  bundler's `target` setting will never add.
130
152
 
153
+ ## 8. Changed again during 20.x
154
+
155
+ Code written against an early 20.x release can still trip on these:
156
+
157
+ - **`world.adapter` is read-only** (20.14): assigning it throws a
158
+ `TypeError`. `app.world.setAdapter(adapter)` switches adapters, rebuilding
159
+ every body from its `bodyDef`.
160
+ - **`renderer.uvOffset` and the `textureSeamFix` setting are removed** (20.11).
161
+ Code that added `uvOffset` to a size should drop it.
162
+ - **The engine's classes are no longer in the legacy pool** (20.9):
163
+ `pool.pull("Sprite")`, `pool.pull("Text")` and the other built-in names
164
+ throw `Cannot instantiate object of type`. Call the constructor, or
165
+ `getPool("text")`, `getPool("tween")`, `getPool("particle")`,
166
+ `getPool("colorLayer")` to recycle. `pool.register(name, Class, true)` no
167
+ longer returns an instance to the pool on removal either; `createPool` does.
168
+ - **A container sorted on `"x"` or `"y"` re-sorts its children as they move**
169
+ (20.11, `autoSort` on by default). Turn `autoSort` off to keep an order set
170
+ by hand. A `"z"` sort is unchanged.
171
+
131
172
  ## The shipped examples are not all current
132
173
 
133
174
  The examples in this repository are the best reference for how melonJS is
@@ -139,10 +180,11 @@ from them:
139
180
  right; the comment is wrong.
140
181
  - Several examples assign `viewport.shader = …` / `sprite.shader = …`, which is
141
182
  deprecated. `addPostEffect()` is current.
142
- - Five files set `this.z = Number.POSITIVE_INFINITY` on a HUD. That property
143
- does not exist; those HUDs draw on top only because they are added last.
144
- - `platformer/entities/player.ts` reads `response.overlapV`, the deprecated
145
- field. Use `response.depth` / `response.normal`.
183
+ - Three files (in `platformer-matter/` and `pool-matter/`) set
184
+ `this.z = Number.POSITIVE_INFINITY` on a HUD or a virtual joypad. That
185
+ property does not exist; they draw on top only because they are added last.
186
+ - `platformer/entities/player.ts` and `enemies.ts` read `response.overlapV`,
187
+ the deprecated field. Use `response.depth` / `response.normal`.
146
188
  - Particle speeds tuned before 20.2 look wrong now: the particle transform fix
147
189
  changed how far a burst travels, and emitters were retuned upward to match.
148
190
 
@@ -153,6 +195,7 @@ from them:
153
195
  3. Is it using `renderable.shader =` instead of `addPostEffect()`?
154
196
  4. Is it importing a deprecated symbol from the table above?
155
197
  5. Is it referencing a global `game`?
198
+ 6. Is it relying on something section 8 lists as changed during 20.x?
156
199
 
157
200
  ## Related skills
158
201
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-3d
3
- description: "Use this skill for anything 3D or 2.5D in melonJS — Camera3d, Mesh, InstancedMesh, Sprite3d billboards, Light3d, ground shadows, glTF/GLB scenes, and depth sorting. Covers the Y-down/+Z-forward convention that is the inverse of OpenGL, the cameraClass opt-in, clip planes, and what does not work on the Canvas fallback. Triggers on: Camera3d, Mesh, InstancedMesh, Sprite3d, Trail3d, ribbon, exhaust trail, Light3d, billboard, glTF, glb, 3D, 2.5D, depth, cameraClass, fov, setClipPlanes, setFog, fog, distance fog, height fog, heightFalloff, transparent, transparency, alpha, blendMode, fade, castGroundShadow, lit, setBasis, getBasis, lookAt, up vector, gimbal, slerp, great circle, turn a direction."
3
+ description: "Use this skill for anything 3D or 2.5D in melonJS — Camera3d, Mesh, InstancedMesh, Sprite3d billboards, Light3d, ground shadows, glTF/GLB scenes, and depth sorting. Covers the Y-down/+Z-forward convention that is the inverse of OpenGL, the cameraClass opt-in, clip planes, and what does not work on the Canvas fallback. Triggers on: Camera3d, Mesh, InstancedMesh, Sprite3d, Trail3d, ribbon, exhaust trail, Light3d, billboard, glTF, glb, 3D, 2.5D, depth, cameraClass, fov, setClipPlanes, setFog, fog, distance fog, height fog, heightFalloff, transparent, transparency, alpha, blendMode, fade, castGroundShadow, shadowGroundPoint, lit, emissive, specular, shininess, normalMap, alphaCutoff, depthTest, vertexColors, worldToScreen, followOffset, setBasis, getBasis, lookAt, up vector, gimbal, slerp, great circle, turn a direction."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -69,7 +69,8 @@ await app.init();
69
69
 
70
70
  That flips the world's sort to `"depth"`, switches the broadphase to a 3D index,
71
71
  and renders sprites and meshes with perspective projection. `Camera3d` extends
72
- `Camera2d`, so follow, fade, shake and post effects all still work.
72
+ `Camera2d`, so follow, fade, shake and post effects all still work (follow with
73
+ a difference, see the camera traps).
73
74
 
74
75
  ### Keep the camera, do not fish it back out
75
76
 
@@ -193,8 +194,15 @@ class GameStage extends Stage {
193
194
  sense to `lookAt`'s, and one negation apart.
194
195
  - **`worldToLocal` does not project.** It is a 2D camera's offset subtraction.
195
196
  To pin a label or marker to a point in the scene use
196
- `camera.worldToScreen(x, y, z)`, which applies the projection and returns
197
+ `camera.worldToScreen(point, out?)`: it takes a `Vector3d`, applies the
198
+ projection and returns a `Vector2d` in the camera's pixels (y down), or
197
199
  `null` behind the camera. See `melonjs-camera-and-drawing`.
200
+ - **`follow()` tracks exactly.** Under a `Camera3d` the camera sits at the
201
+ target's position plus `followOffset` (world space, set with
202
+ `camera.setFollowOffset(x, y, z)`) every frame. The 2D follow's axis,
203
+ deadzone and damping are not applied. For a damped chase camera, leave the
204
+ target unset and move the camera yourself (`math.damp`, or `slerp` below for
205
+ its facing).
198
206
 
199
207
  ## Where an up vector earns its keep
200
208
 
@@ -299,6 +307,11 @@ index** — a real world depth in a 3D scene, and never the one you wanted. Pass
299
307
  it (`world.addChild(mesh, z)`), or set `mesh.depth` afterwards. The glTF
300
308
  importer turns `autoDepth` off on the container it loads into for this reason.
301
309
 
310
+ A `Container`'s depth carries to its children when they are drawn, as it
311
+ already did for culling and the sort: a group at depth 400 draws its sprites
312
+ and meshes 400 units further away. One more reason to give containers an
313
+ explicit z.
314
+
302
315
  **`floating` does not opt out of this sort.** It skips the camera *transform*,
303
316
  not the depth *order*. A floating child is ordered by `|pos.z|` alone — its
304
317
  `pos.x/y` are screen pixels, and the camera does not move relative to it — so
@@ -327,8 +340,9 @@ Meshes render in two phases. The **opaque pass** writes depth in sort order; the
327
340
  depth. A draw lands in the second whenever its alpha is fractional.
328
341
 
329
342
  That default matters because the opaque path writes premultiplied colour with
330
- blending off, so before this a faded mesh came out **darkened toward black**
331
- rather than see-through — the background contributed nothing.
343
+ blending off, so a faded mesh that stays there (as every one did before 20.4)
344
+ comes out **darkened toward black** rather than see-through — the background
345
+ contributes nothing.
332
346
 
333
347
  **`transparent: true`** when the transparency is in the TEXTURE rather than the
334
348
  opacity — a soft-edged glow, smoke, a glTF material with `alphaMode: "BLEND"`.
@@ -376,8 +390,9 @@ const halo = new Sprite3d(0, 0, {
376
390
  });
377
391
  ```
378
392
 
379
- Honoured in the transparent pass on both GPU backends; the Canvas renderer has
380
- no depth buffer and ignores it.
393
+ Honoured in the **transparent pass only**, on both GPU backends: a mesh drawn
394
+ in the opaque pass ignores it, so pair it with `transparent: true` as above.
395
+ The Canvas renderer has no depth buffer and ignores it.
381
396
 
382
397
  Blending uses the renderable's existing `blendMode`, so a glow is one property.
383
398
  The advanced modes (`"overlay"`, `"difference"`, and the rest that need a
@@ -482,6 +497,8 @@ the built-in ones:
482
497
  uniform vec3 uFogColor; // straight (unpremultiplied) fog colour
483
498
  uniform vec4 uFogParams; // x = mode (0 off / 1 linear / 2 exp2),
484
499
  // y = near, z = 1/(far - near), w = density
500
+ uniform vec4 uFogHeight; // height falloff: xyz = falloff x world-up axis,
501
+ // in VIEW space; w = pre-baked altitude term
485
502
  ```
486
503
 
487
504
  Your vertex stage computes the distance itself — `length((uViewMatrix *
@@ -490,6 +507,12 @@ camera turns — and the blend must scale the fog colour by the fragment's own
490
507
  alpha, `mix(uFogColor * a, rgb, f)`, because `vColor` arrives premultiplied.
491
508
  Mixing toward the unscaled colour haloes every alpha-cutout edge.
492
509
 
510
+ `uFogHeight` is only needed to honour `heightFalloff`, and is fed only to a
511
+ program that also declares `uFogParams`. The engine's own shaders multiply the
512
+ distance by `uFogHeight.w * (exp(k) - 1.0) / k`, with `k = dot(uFogHeight.xyz,
513
+ viewPos)` and the factor taken as `uFogHeight.w` when `k` is near zero. Leave it
514
+ out and your mesh fogs uniformly at every altitude.
515
+
493
516
  The flip side is the reason fog costs nothing when unused: with no camera fog,
494
517
  the mesh programs are compiled without any of it, on both backends. It is not a
495
518
  branch that is skipped at runtime — the code is not there.
@@ -510,7 +533,32 @@ const cube = new Mesh(x, y, {
510
533
  `Camera3d` it opts out of the anchor offset entirely, so `anchorPoint` is inert
511
534
  there — place the origin where you want the pivot at authoring time, or nest the
512
535
  mesh under a transformed parent. The anchor is only honoured on the legacy
513
- 2D-camera path.
536
+ 2D-camera path. `Sprite3d` is the exception: it bakes its `anchorPoint` into
537
+ the quad's vertices, so a `"bottom"` anchor works under either camera.
538
+
539
+ ### Material settings
540
+
541
+ What a material file can state is also a setting, so a hand-built mesh gets the
542
+ same surface. All but `baseColorFactor` are GPU-only; the Canvas renderer
543
+ ignores them.
544
+
545
+ | setting | what it does | needs |
546
+ |---|---|---|
547
+ | `emissive: [r, g, b]` | self-illumination added on top, 0..1 (above 1 for an HDR glow) | nothing, lit or unlit |
548
+ | `specular: [r, g, b]` + `shininess` | a highlight; `shininess` 0, the default, turns it off however bright `specular` is | `lit`, a `Light3d`, normals |
549
+ | `normalMap` | tangent-space relief, a loader key or image (see Lighting below) | `lit` and a `Camera3d` |
550
+ | `alphaCutoff` | discard texels below this alpha: foliage, fences | nothing |
551
+ | `alphaMap` | per-texel opacity, multiplied in before the cutoff | a non-zero `alphaCutoff` |
552
+ | `baseColorFactor` | a LINEAR colour, encoded into `tint` (see `melonjs-3d-assets`) | nothing |
553
+
554
+ `emissive`, `specular` and `shininess` can be changed after construction too;
555
+ `setBaseColorFactor()` is the method form of the last row.
556
+
557
+ A `ShaderEffect` can shade a mesh as well: `mesh.addPostEffect(fx)` splices it
558
+ into the mesh shader as a colour hook on both GPU backends, so the mesh keeps its
559
+ placement, cutout, lighting and fog. A few builtins are unavailable there, and an
560
+ `InstancedMesh` cannot host one; both warn once. See
561
+ `melonjs-effects-and-shaders`.
514
562
 
515
563
  ### InstancedMesh, and when it is the wrong tool
516
564
 
@@ -578,9 +626,9 @@ vertices. Want smooth: share them.
578
626
  An explicit `settings.normals` always wins, and an unlit mesh gets none — there
579
627
  would be nothing to read them.
580
628
 
581
- **A lit mesh with no normals used to render fullbright**, which looks like the
582
- lighting is broken rather than absent. If an older scene suddenly picks up
583
- shading, that is why.
629
+ **A lit mesh with no normals used to render fullbright** (before 20.4), which
630
+ looks like the lighting is broken rather than absent. If an older scene suddenly
631
+ picks up shading, that is why.
584
632
 
585
633
  ## Colouring a mesh
586
634
 
@@ -591,7 +639,7 @@ There are four levels, and picking the wrong one is the usual reason a colour
591
639
  |---|---|---|
592
640
  | whole object | `mesh.setTint("#ff8800")`, or `mesh.tint.setColor(r, g, b)` | flash on hit, team colour, fading one object |
593
641
  | per vertex | `settings.vertexColors`, or `mesh.setVertexColor(i, color)` | a gradient *within* one mesh — distance haze, a darker crease |
594
- | per material | `textureGroups`, from a multi-material OBJ + MTL | a model whose parts differ, in one draw call |
642
+ | per material | a multi-material OBJ + MTL | a model whose parts differ: each `Kd` is baked into the vertex colours, and only a different `map_Kd` costs a draw (one per `textureGroups` entry) |
595
643
  | per instance | `new InstancedMesh(…, { instanceColors: true })` then `setInstanceColor(i, color)` | a thousand copies that differ |
596
644
 
597
645
  **`tint` is per object.** That is the trap: build a terrain as one big mesh and
@@ -629,10 +677,11 @@ On a lit mesh the colour multiplies the **lit** result, so it behaves as albedo
629
677
  rather than as an emissive override — a vertex colour will not make an unlit
630
678
  face bright.
631
679
 
632
- `alpha` hides a mesh as it hides anything else: at 0 the draw is skipped. There
633
- is no *partial* mesh transparency though — the mesh path renders opaque, so a
634
- mesh at `alpha = 0.5` draws fully opaque rather than half see-through. Fade a
635
- mesh out and it will stay solid until it vanishes.
680
+ `alpha` hides a mesh as it hides anything else: below 1/255 the draw is skipped.
681
+ A fractional `alpha` fades it, through the transparent pass described under
682
+ Transparency above, which needs a `Camera3d` on a GPU backend. Without that pass
683
+ (a 2D camera on a GPU backend, or `transparent: false`) the mesh path does not
684
+ blend, and a faded mesh darkens instead of turning see-through.
636
685
 
637
686
  ## Moving a mesh in 3D
638
687
 
@@ -649,6 +698,12 @@ rotates about that axis. Both write to `currentTransform`, a **`Matrix3d`**,
649
698
  which `Mesh` folds into its model matrix — so orienting a mesh costs a matrix
650
699
  update, not new geometry.
651
700
 
701
+ Under a `Camera3d` on a GPU backend the geometry is uploaded once and redrawn
702
+ from the GPU, so moving, turning, scaling or re-tinting needs no signal. Editing the geometry
703
+ itself does: after writing into `originalVertices`, the UVs, normals or
704
+ indices, set `mesh.needsUpdate = true`, or the change shows under a 2D camera and
705
+ not under the 3D one.
706
+
652
707
  This matters most for the case it is least obvious for: a horizontal plane.
653
708
  `Sprite3d` with `billboard: false` is a quad in the sprite plane — upright —
654
709
  and the way to lay one flat (water, a road, a shadow decal) is to rotate it a
@@ -657,13 +712,14 @@ geometry instead is tiling: a `Sprite3d` bakes its UVs 0..1 from its atlas
657
712
  frame, so a surface that needs `textureRepeat` across many world units still
658
713
  wants an explicit `vertices`/`uvs` mesh.
659
714
 
660
- ## The mesh pass is opaque
715
+ ## The opaque pass does not blend
661
716
 
662
717
  Worth stating plainly, because the symptom does not look like a blending
663
- problem: **the mesh pass disables blending**. A soft-edged texture — a glow, a
664
- sun, a halo, anything with a gradient alpha — composites as a hard-edged disc
665
- with a grey rim, which reads as a broken texture rather than as a missing
666
- feature.
718
+ problem: **the opaque mesh pass disables blending**, and a draw goes there
719
+ unless its alpha is fractional or it says `transparent: true`. A soft-edged
720
+ texture — a glow, a sun, a halo, anything with a gradient alpha — drawn at full
721
+ opacity composites as a hard-edged disc with a grey rim, which reads as a broken
722
+ texture rather than as a missing feature.
667
723
 
668
724
  `transparent: true` routes it through the transparent pass, and `blendMode:
669
725
  "additive"` on top of that makes a light source add to the sky instead of
@@ -741,11 +797,13 @@ world.addChild(new Light3d({ type: "spot", position: torch.pos, range: 400 }));
741
797
  ```
742
798
 
743
799
  `direction` and `position` take an `[x, y, z]` array **or** a `Vector3d`, and
744
- `color` takes a `Color`, a CSS string or an `[r, g, b]` array — so a value the
745
- game already holds can go straight in. Each is read, not retained: move your
746
- vector afterwards and the light stays where it was.
800
+ `color` takes a `Color`, a CSS string or an `[r, g, b]` array in **0..1** (the
801
+ glTF convention, not 0..255) — so a value the game already holds can go straight
802
+ in. The vectors are read, not retained: move your vector afterwards and the
803
+ light stays where it was. A `Color` is kept as given, so mutating it recolours
804
+ the light.
747
805
 
748
- Two things still bite, both silently:
806
+ Three things still bite, all silently:
749
807
 
750
808
  - `new Light3d(0, 0, {…})` — the constructor takes **options alone**. JavaScript
751
809
  drops the extra arguments, so `options` becomes the number `0` and every
@@ -766,6 +824,10 @@ Two things still bite, both silently:
766
824
 
767
825
  `position` follows the same convention: a lamp above the floor has a
768
826
  **smaller** y than the floor.
827
+ - **`position` and `direction` are in world coordinates, wherever the light
828
+ sits in the tree.** A `Light3d` added under a container does not move or turn
829
+ with it, and its own `pos` is never read. To carry a lamp with a car or a
830
+ torch, write the object's world position into `light.position` each frame.
769
831
 
770
832
  Use **both halves**: with a key light but no ambient, the shadow side of a mesh
771
833
  goes black. With no `Light3d` in the world at all, a `lit: true` mesh falls back
@@ -776,6 +838,10 @@ Other defaults worth knowing: `intensity` 1, `color` white, `range` 1000 (point
776
838
  and spot, a stylised quadratic falloff, not inverse-square),
777
839
  `innerConeAngle` 0 and `outerConeAngle` π/4 (spot).
778
840
 
841
+ Up to **32** directional, point and spot lights shade a mesh; ambient lights are
842
+ summed into one fill and do not count. Lights past the 32nd are dropped without
843
+ a warning.
844
+
779
845
  **`Light2d` is 2D-only** and produces visible artifacts under perspective
780
846
  projection. Do not combine it with `Camera3d`.
781
847
 
@@ -858,7 +924,10 @@ the fade clamps to full strength and the blob never fades at all; and a point
858
924
  on the wrong side of the caster reads as a large height, so the blob
859
925
  disappears outright rather than thinning to a ring the way a backwards
860
926
  `shadowGroundY` does. It is also the one shadow setting that THROWS rather
861
- than clamping or hiding: a non-finite component is rejected on the spot.
927
+ than clamping or hiding: a non-finite component is rejected on the spot. A
928
+ two-component point (a `Vector2d`, a 2-element array) does not throw: its z
929
+ silently becomes 0. The point is copied, so moving the vector you passed moves
930
+ nothing; assign it again.
862
931
 
863
932
  The blob is an ellipse sized to the caster's own footprint and placed at the
864
933
  caster's x/z, or at `shadowGroundPoint` when one is set. So a tall or narrow object (a character, a tree, a pickup) shows
@@ -1028,7 +1097,9 @@ level.load("diorama", {
1028
1097
  An **animated** asset instantiates as a `GLTFModel`, which is a game object as
1029
1098
  much as a scene: construct it straight from the parsed descriptor, and place it
1030
1099
  with `pos` / `depth` / `rotate` / `scale` like any other renderable — the
1031
- placement drives the whole rig and composes with the playing clip.
1100
+ placement drives the whole rig and composes with the playing clip, and still
1101
+ applies while the clip is paused, finished or was never played. `scale()`
1102
+ multiplies the `scale` option it was built with.
1032
1103
 
1033
1104
  ```js
1034
1105
  const model = new GLTFModel(loader.getGLTF("boat"), { scale: 40, lit: false });
@@ -1048,14 +1119,20 @@ animation (and the skinning that is out of scope), and OBJ/MTL.
1048
1119
 
1049
1120
  ## Requires a GPU backend
1050
1121
 
1051
- The whole 3D tier needs WebGPU or WebGL 2. The Canvas renderer has no depth
1052
- buffer, no perspective path and no `drawMesh`, so a `Camera3d` scene does not
1053
- render correctly there — usually a black canvas. `Light3d` and shader effects
1054
- are inert too. A `Mesh` under a *2D* camera still renders, CPU-projected by
1055
- painter's algorithm and unlit.
1122
+ The 3D tier is built for WebGPU or WebGL 2: target one of them. The Canvas
1123
+ renderer has no depth buffer and no perspective path, so a `Camera3d` scene
1124
+ does not render there — usually a black canvas. `Light3d` and shader effects
1125
+ are inert too.
1126
+
1127
+ A `Mesh` under a *2D* camera does render on Canvas, but only as a last resort:
1128
+ textured, CPU-projected, sorted back to front a triangle at a time (painter's
1129
+ algorithm) and unlit. Overlapping parts can show slivers of the wrong
1130
+ triangle, large textured faces are mapped without perspective correction, and
1131
+ it slows down as the triangle count grows. It keeps a 2D-camera game from going
1132
+ blank on a device with no GPU backend; do not design for it or tune on it.
1056
1133
 
1057
- `Application` does warn: constructing with a `cameraClass` whose
1058
- `defaultSortOn` is `"depth"` on a renderer with no depth buffer logs a
1134
+ `Application` does warn: `await app.init()` with a `cameraClass` whose
1135
+ `defaultSortOn` is `"depth"`, on a renderer with no depth buffer, logs a
1059
1136
  `console.warn`. But `video.AUTO` falls back silently to Canvas, so the strong
1060
1137
  gate is to ask for a GPU backend by name — `renderer: video.WEBGL` (or
1061
1138
  `video.WEBGPU`) makes `await app.init()` reject instead of misrendering:
@@ -1070,23 +1147,42 @@ await app.init();
1070
1147
  ```
1071
1148
 
1072
1149
  To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
1073
- `init()`.
1150
+ `init()`: a `Camera3d` game should refuse Canvas there with a message, while a
1151
+ game whose meshes sit under a 2D camera can let it through as the fallback.
1152
+
1153
+ ```js
1154
+ const app = new Application(1024, 768, { parent: "screen", renderer: video.AUTO });
1155
+ await app.init();
1156
+ if (!app.renderer.supportsDepthBuffer) {
1157
+ // the Canvas fallback: no GPU backend on this device. Meshes under the
1158
+ // 2D camera still draw (CPU-projected, unlit); keep the scene small, or
1159
+ // tell the player the game needs WebGL 2 or WebGPU.
1160
+ }
1161
+ ```
1074
1162
 
1075
1163
  ## Symptom → cause
1076
1164
 
1077
1165
  | symptom | cause |
1078
1166
  |---|---|
1079
1167
  | scene lit from underneath | `direction` Y sign — this is Y-down, so a sun overhead is **+Y** |
1080
- | a `lit` mesh renders fullbright | it had no normals — supply them, or let the engine generate them |
1168
+ | `model.scale(2)` throws "not a function" on a `GLTFModel` (before 20.14) | the `scale` option hid the method: update |
1169
+ | a `GLTFModel` ignores `pos` / `rotate` once its clip is paused or finished (before 20.14) | it was only re-posed while a clip played: update |
1170
+ | a sprite or mesh in a container draws too near under `Camera3d`, and pops in and out at the wrong time (before 20.14) | the container's depth was used to cull and sort its children but not to draw them: update |
1171
+ | a lit mesh inside a moved or turned container is lit as if the container were at the origin (before 20.14) | lights were not taken into the container's frame: update. Under a *scaled* container, a point light's range is measured in the container's units |
1172
+ | objects at the screen edge pop in a frame late while the camera turns, or a camera cut shows one empty frame (before 20.14) | culling used the camera as it was at the last `camera.update()`: update |
1173
+ | a `lit` mesh renders fullbright | no `Light3d` in the world, or a 2D camera (which warns once). Before 20.4 a mesh with no normals did it too; they are generated now |
1081
1174
  | a gradient across one mesh is impossible | `tint` is per object — use `vertexColors` / `setVertexColor` |
1082
- | a mesh stays solid as you fade it out | meshes render opaque; only `alpha` 0 (hidden) and 1 differ |
1083
1175
  | a glow is cut along a hard straight line, worst near a round object's limb | a flat billboard being depth tested against the geometry behind it — `depthTest: false` |
1084
- | vertex colour applies under a 2D camera but not `Camera3d` | wrote the array directly without setting `needsUpdate` |
1176
+ | `depthTest: false` changes nothing | the mesh draws in the opaque pass, which ignores it — add `transparent: true` |
1177
+ | `specular` is set but no highlight appears | `shininess` is 0 (the default), or the mesh is not `lit` |
1178
+ | `setInstanceColor` does nothing | the set was built without `instanceColors: true`; the slot does not exist |
1179
+ | a vertex colour or a geometry edit applies under a 2D camera but not `Camera3d` | wrote the array directly without setting `needsUpdate` |
1085
1180
  | `Mesh: vertexColors has N entries, expected M` | one colour per *vertex*, not per triangle or per index |
1086
1181
  | nothing renders, or a backdrop covers everything | wrong depth sign — "far" is *larger* z when looking along +Z |
1087
1182
  | distant objects vanish or warp | scene exceeds the default far plane; `setClipPlanes` |
1088
1183
  | distant surfaces z-fight | `near` too small for the scene scale |
1089
1184
  | black canvas under `Camera3d` | Canvas renderer (no depth buffer) — check the `console.warn` |
1185
+ | a 2D-camera mesh shows slivers where it overlaps itself, or slows with many triangles | the Canvas fallback, a last resort: ask for `video.WEBGL` or `video.WEBGPU` |
1090
1186
  | everything flat and unlit | `lit: true` with no `Light3d` in the world (falls back to fullbright), or a mesh under a 2D camera |
1091
1187
  | a faded mesh goes dark instead of see-through | `transparent: false` on it, or a 2D camera — the transparent pass needs a `Camera3d` |
1092
1188
  | a soft-edged glow has hard edges | `alphaCutoff` discarded the soft texels; lower it |
@@ -1114,9 +1210,13 @@ To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
1114
1210
  | `setBasis` throws "forward must have a non-zero length" | `forward` is all zeros, the one case that names no direction; a short one is fine, so a surface frame that shrank as it drifted still works |
1115
1211
  | roll is ignored when the target is straight up or down | no rotation about the view axis is determined there, so `lookAt` keeps the roll it had and `setBasis` falls back to the `right` axis |
1116
1212
  | an object jumps to the camera plane | `Vector3d.set(x, y)` / `camera.pos.set(x, y)` zeroed its z |
1213
+ | a follow camera ignores its deadzone and damping | `Camera3d` follows exactly, at the target plus `followOffset`; damp it yourself with no target set |
1214
+ | a `Light3d` parented to a moving object stays behind | `position` is in world coordinates and its container is not applied; copy the world position into it each frame |
1117
1215
  | lighting looks wrong under a 3D camera | `Light2d` used instead of `Light3d` |
1118
1216
 
1119
1217
  ## Related skills
1120
1218
 
1219
+ - `melonjs-3d-assets` — glTF/GLB and OBJ/MTL loading, imported materials and lights
1220
+ - `melonjs-effects-and-shaders` — a `ShaderEffect` hosted on a mesh, and custom mesh shaders
1121
1221
  - `melonjs-renderables` — the 2D z-ordering rules, anchors, custom draw
1122
1222
  - `melonjs-getting-started` — Application settings and the renderer fallback