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-plugins
3
- description: "Use this skill when adding a melonJS plugin or adapter — the debug panel, Spine skeletal animation, compressed Tiled map support, on-screen touch controls, Capacitor packaging — or when writing your own plugin. Covers plugin.register and its version gate, plugin.cache, plugin.patch, and why physics adapters are not plugins. Triggers on: plugin, plugin.register, plugin.cache, plugin.get, plugin.patch, BasePlugin, debug-plugin, DebugPanelPlugin, SpinePlugin, TiledInflatePlugin, GamepadPlugin, gamepad-plugin, touch controls, virtual joystick, CapacitorPlugin, adapter, custom plugin, plugin version mismatch, already registered."
3
+ description: "Use this skill when adding a melonJS plugin or adapter — the debug panel, Spine skeletal animation, compressed Tiled map support, on-screen touch controls, Capacitor packaging — or when writing your own plugin. Covers plugin.register and its version gate, plugin.cache, plugin.patch, and why physics adapters are not plugins. Triggers on: plugin, plugin.register, plugin.cache, plugin.get, plugin.patch, BasePlugin, debug-plugin, DebugPanelPlugin, SpinePlugin, TiledInflatePlugin, GamepadPlugin, gamepad-plugin, touch controls, virtual joystick, floating joystick, floating button, CapacitorPlugin, adapter, custom plugin, plugin version mismatch, already registered."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -13,13 +13,13 @@ This trips people up, so it is worth stating first:
13
13
  | | how it is installed |
14
14
  |---|---|
15
15
  | **Plugins** | `plugin.register(Class, name?, ...args)` — after `await app.init()` |
16
- | **Physics adapters** | an `Application` **setting** (`physic:`), before `init()` |
16
+ | **Physics adapters** | an `Application` **setting** (`physic:`), before `init()`; or `app.world.setAdapter()` on a running game |
17
17
 
18
18
  ```js
19
19
  // the adapter is chosen at construction …
20
20
  const app = new Application(800, 600, {
21
21
  parent: "screen",
22
- physic: new PlanckAdapter({ gravity, pixelsPerMeter }),
22
+ physic: await PlanckAdapter.create({ gravity, pixelsPerMeter }),
23
23
  });
24
24
 
25
25
  // … the plugin is registered once the renderer exists
@@ -88,6 +88,19 @@ This is the answer for deforming animated characters — glTF import does not re
88
88
  vertex skinning. It also registers a Tiled object class, so Spine skeletons can
89
89
  be placed from a map.
90
90
 
91
+ ```js
92
+ import Spine, { SpinePlugin } from "@melonjs/spine-plugin";
93
+
94
+ await app.init();
95
+ plugin.register(SpinePlugin); // after init: it reads app.renderer
96
+ // preloaded as { name: "hero.atlas", type: "spine", src: … }, and the same for
97
+ // "hero.json" (a binary "hero.skel" also goes in jsonFile)
98
+ const hero = new Spine(100, 100, { atlasFile: "hero.atlas", jsonFile: "hero.json" });
99
+ ```
100
+
101
+ Register it after `await app.init()` like any plugin: its constructor builds
102
+ an asset manager on the application's renderer, which does not exist before.
103
+
91
104
  ### `@melonjs/gamepad-plugin`
92
105
 
93
106
  On-screen touch controls, reported as a gamepad: one or two sticks and any
@@ -105,7 +118,14 @@ plugin.register(GamepadPlugin, "gamepad", {
105
118
 
106
119
  Shown on touch screens by default (`enabled`), hidden while a real pad is in
107
120
  use. The left stick also presses the D-pad; a button can press a key instead
108
- of a pad button. See `melonjs-input` for the bindings.
121
+ of a pad button. Since 1.1 a stick or button can be `floating` (shows up
122
+ under the finger anywhere in its `area`), `hideAtRest` (drawn only while
123
+ held), and a stick can take a `tap` (a quick touch that does not steer presses
124
+ a key or button) — so the mobile layouts, two thumbs or one thumb with
125
+ tap-to-act, need no code of their own. Since 1.2 a `skin` texture atlas draws
126
+ them from the game's own art (`base` / `thumb` regions on a stick, `region` /
127
+ `pressed` on a button). It needs melonJS 20.12 or later. See `melonjs-input`
128
+ for the bindings, the layouts and the overlap rules.
109
129
 
110
130
  ### `@melonjs/capacitor-plugin`
111
131
 
@@ -121,16 +141,29 @@ It also exports helpers usable on their own: `onBackButton`, `bindStageBack`,
121
141
  `lockOrientation` / `unlockOrientation`, `hideSplash`. See `melonjs-deployment`
122
142
  for the wider packaging picture.
123
143
 
144
+ With `pauseOnBackground` (the default), going to the background pauses the
145
+ game and coming back resumes it, but only if this pause was the plugin's: a
146
+ game the player had paused stays paused.
147
+
124
148
  ## Physics adapters
125
149
 
126
- `@melonjs/planck-adapter` and `@melonjs/matter-adapter` swap the physics world
127
- wholesale. Pick **one** per game. The portable surface — the declarative
128
- `renderable.bodyDef`, `world.adapter.queryAABB(rect)` and
150
+ `@melonjs/planck-adapter`, `@melonjs/matter-adapter` and
151
+ `@melonjs/rapier2d-adapter` swap the physics world wholesale. Pick **one** per
152
+ game (`app.world.setAdapter()` can switch it later). The portable surface — the
153
+ declarative `renderable.bodyDef`, `world.adapter.queryAABB(rect)` and
129
154
  `world.adapter.raycast(from, to)` — works unchanged across the built-in world and
130
- both adapters, so entity code does not have to branch.
155
+ every adapter, so entity code does not have to branch. The exceptions are in
156
+ `melonjs-physics`: `Box3d`, `Sphere` and `Point` shapes are built-in only,
157
+ `type: "kinematic"` is planck and Rapier only, and the adapters' `queryAABB`
158
+ returns only renderables with a body. The Rapier adapter's WebAssembly loads
159
+ asynchronously, so it must be made with `await RapierAdapter.create()`.
160
+ `create()` is on every adapter from planck-adapter 1.7, matter-adapter 1.6 and
161
+ rapier2d-adapter 1.0 (an older planck or matter adapter is made with
162
+ `new X(options)`), and `world.setAdapter()` from melonJS 20.14.
131
163
 
132
164
  Two queries are **not** portable: `querySphere` and `raycast3d` exist only on the
133
- built-in adapter (planck and matter are 2D-only and deliberately omit them).
165
+ built-in adapter (planck, matter and Rapier are 2D-only and deliberately omit
166
+ them).
134
167
  `queryAABB` is the one region query every adapter must implement, so call the
135
168
  others optionally — `app.world.adapter.querySphere?.(center, radius)`. For rays
136
169
  there is a real flag: `app.world.adapter.capabilities.raycasts3d`.
@@ -195,11 +228,17 @@ or a `Stage` is simpler and easier to test.
195
228
  | `Plugin version mismatch` | the plugin's `version` floor is above the running engine |
196
229
  | `Plugin should extend the BasePlugin Class !` | the class does not extend `plugin.BasePlugin` |
197
230
  | the panel reports another app's numbers | `app` defaulted to `game`, the last Application to finish `init()` |
198
- | `world.adapter.querySphere is not a function` | built-in adapter only; planck and matter are 2D-only |
231
+ | `world.adapter.querySphere is not a function` | built-in adapter only; planck, matter and Rapier are 2D-only |
199
232
  | a Tiled map opens in Tiled but not melonJS | compressed layers, tiled-inflate plugin missing |
200
233
  | plugin setup never runs | setup written in an `init()` method — there is no such hook |
201
- | `plugin.register` for an adapter does nothing | adapters are a construction setting, not a plugin |
234
+ | `Plugin should extend the BasePlugin Class !` when registering an adapter | adapters are a construction setting (or `world.setAdapter()`), not a plugin |
235
+ | `PlanckAdapter.create is not a function` (or `MatterAdapter`) | planck-adapter < 1.7 or matter-adapter < 1.6; upgrade, or `new PlanckAdapter(options)` |
236
+ | `TypeError` reading `renderer` from `plugin.register(SpinePlugin)` | registered before `await app.init()`; the plugin's `app` is still unset |
202
237
  | physics behaves differently after adding an adapter | expected — units and force scales differ; re-tune |
238
+ | a Spine object slides off screen when the camera moves, or ignores its container (WebGL / WebGPU) | spine-plugin < 4.0.1 drew without the camera's and the parents' transform; upgrade |
239
+ | a Spine object ignores `setOpacity()` / `alpha` | spine-plugin < 4.0.1; upgrade |
240
+ | a game paused from its menu is running again after the app comes back from the background | capacitor-plugin < 1.0.1 resumed on every return; upgrade |
241
+ | touch controls miss the finger after browser zoom or a move to another screen | gamepad-plugin < 1.2.1; upgrade |
203
242
 
204
243
  ## Related skills
205
244
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-renderables
3
- description: "Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, and z-ordering. Triggers on: Tween, easing, animate, Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating."
3
+ description: "Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, z-ordering, y-sorting with sortOn and sortOffset, and Container clipping. Triggers on: Tween, easing, animate, Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, sortOn, sortOffset, autoSort, clipping, clipContains, anchorPoint, floating."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -67,14 +67,19 @@ class Button extends Renderable {
67
67
  The symptom is a handler that is registered, looks correct, and never fires.
68
68
 
69
69
  Note that `Container`, `Camera2d`, `Draggable`, `DropTarget` and the `UI*`
70
- elements set it to `false` for you, and so does the `Body` constructor on its
71
- owner (which covers `Entity` and anything you give a body to) — so a plain
72
- `Renderable` or `Sprite` subclass with no body is the case that catches people.
70
+ elements set it to `false` for you, and so does giving a renderable a body (a
71
+ `bodyDef`, on every physics adapter, or a `Body`, which covers `Entity`) — so a
72
+ plain `Renderable` or `Sprite` subclass with no body is the case that catches
73
+ people. Before melonJS 20.14, matter-adapter 1.6.0 and planck-adapter 1.7.0,
74
+ only the built-in adapter cleared it: under matter or planck a renderable with
75
+ a `bodyDef` stayed kinematic and missed pointer events.
73
76
 
74
77
  The same flag has a second, less obvious effect: it is also the opt-in to the
75
78
  world **broadphase**. The quadtree/octree insert skips any child with
76
79
  `isKinematic === true`, so body-less objects you want `adapter.queryAABB` /
77
- `querySphere` to find need `isKinematic = false` as well. The builtin
80
+ `querySphere` to find need `isKinematic = false` as well. That is the built-in
81
+ adapter; the `queryAABB` of matter, planck and Rapier only ever returns
82
+ renderables with a body. The builtin
78
83
  narrowphase likewise requires *both* partners to be non-kinematic.
79
84
 
80
85
  ## 3. Draw order: only `addChild(child, z)` works
@@ -291,6 +296,19 @@ other correct ending.
291
296
  `obj.alwaysUpdate === true`. An off-screen object with the default
292
297
  `alwaysUpdate = false` does not tick at all.
293
298
 
299
+ A `Container` is culled by its own bounds, which do not follow its children
300
+ unless it has a size of its own (`resize`) or `enableChildBoundsUpdate` is on.
301
+ With the flag, the bounds are the children's union, re-measured on every update
302
+ and, while the group is off screen, before each culling check — so a squad that
303
+ walks back into view is drawn again.
304
+
305
+ `clipping = true` crops a container's children to its own rectangle, so it
306
+ needs a size of its own: a sizeless container (the default) does not clip.
307
+ Since 20.13 the pointer skips a child where it is cropped away, and
308
+ `container.clipContains(x, y)` tells whether the container shows its children
309
+ at a point (screen pixels for a floating one). A clip inside a clip shows
310
+ their overlap, on WebGL and WebGPU as on Canvas.
311
+
294
312
  ## Anchors and floating
295
313
 
296
314
  - `anchorPoint` defaults to centre `(0.5, 0.5)` on `Renderable`, but `Container`
@@ -306,8 +324,10 @@ other correct ending.
306
324
  ## Prefer built-in features
307
325
 
308
326
  Before writing custom `draw()` code, check whether the engine already covers it —
309
- `ParticleEmitter`, `Tween`, the `ShaderEffect` presets, `UISpriteElement` and
310
- `UITextButton`, the physics adapters, and the Tiled tilemap renderers all exist.
327
+ `ParticleEmitter`, `Tween`, the `ShaderEffect` presets, the UI widgets
328
+ (`UITextButton`, `UISpriteElement`, `UIPanel`, `ProgressBar` and the rest, see
329
+ `melonjs-ui-and-text`), `NineSliceSprite`, the physics adapters, and the Tiled
330
+ tilemap renderers all exist.
311
331
  Hand-rolled equivalents miss the batching and the multi-backend support.
312
332
 
313
333
  ## Symptom → cause
@@ -322,14 +342,18 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
322
342
  | effect silently does nothing | running on the Canvas renderer |
323
343
  | object appears frozen while its state changes | `update()` not returning `true` |
324
344
  | off-screen object never ticks | `update()` is gated on `inViewport` — set `alwaysUpdate = true` |
345
+ | a group of moving children vanishes once they leave where they started | a sizeless container's bounds did not follow its children: give it a size, or `enableChildBoundsUpdate` (which only followed them from 20.13.0) |
325
346
  | custom draw offset by half the size | centred `anchorPoint` default not zeroed |
326
347
  | spatial query never finds an object | `isKinematic` left `true` — not in the broadphase |
327
348
  | `z` unexpectedly 0 after a `set` | `Vector3d.set(x, y)` defaults `z` to 0 — `setPosition(x, y)` on the renderable leaves the depth alone |
328
349
  | a settings option appears to do nothing | the class never reads that key — see section 7 |
329
350
  | `isRenderable = false` did not hide the object | it gates bounds, not drawing — use `alpha` |
351
+ | `clipping = true` crops nothing | the container has no size of its own: give it one (`resize`) |
352
+ | a child cropped out of a clipping container still takes clicks where it is hidden | before 20.13 the pointer ignored the clip: update |
330
353
 
331
354
  ## Related skills
332
355
  - `melonjs-scenes-and-state` — `Tween` and the easing families, for animating any of this
333
356
 
334
357
  - `melonjs-getting-started` — Application lifecycle and the scene graph
358
+ - `melonjs-ui-and-text` — the UI widgets, before drawing your own
335
359
  - `melonjs-20-migration` — the deprecated `shader` property and other pre-20 APIs
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-renderer-backends
3
- description: "Use this skill when a feature works on one machine and not another, when choosing or pinning a renderer, or when writing code that must degrade gracefully. Covers the WebGPU to WebGL 2 to Canvas fallback ladder, what each backend supports, capability detection, and the subsystems that warn once and then stop working on Canvas. Triggers on: video.AUTO, video.WEBGPU, video.WEBGL, video.CANVAS, renderer.type, supportsDepthBuffer, shaderLanguage, WebGPU, WebGL2, Canvas fallback, context loss, backend, works on my machine."
3
+ description: "Use this skill when a feature works on one machine and not another, when choosing or pinning a renderer, or when writing code that must degrade gracefully. Covers the WebGPU to WebGL 2 to Canvas fallback ladder, what each backend supports, capability detection, and the subsystems that warn once and then stop working on Canvas. Triggers on: video.AUTO, video.WEBGPU, video.WEBGL, video.CANVAS, renderer.type, supportsDepthBuffer, shaderLanguage, WebGPU, WebGL2, Canvas fallback, failIfMajorPerformanceCaveat, software rendering, supportsHDR, hdrOutput, context loss, backend, works on my machine."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -27,6 +27,14 @@ await app.init();
27
27
  WebGL 1 is not in the ladder — 20.x made WebGL 2 the baseline, and pinning
28
28
  `video.WEBGL` **rejects** on a WebGL-1-only device rather than degrading.
29
29
 
30
+ A GPU backend also counts as unavailable when the browser says it would run
31
+ far slower than native — a software rasterizer, a blocklisted driver. The
32
+ `failIfMajorPerformanceCaveat` setting (`true` by default, stricter than
33
+ WebGL's own default) refuses such a context, and the WebGPU backend refuses a
34
+ software adapter the same way: under `AUTO` that machine gets Canvas, and
35
+ under `video.WEBGL` or `video.WEBGPU` `init()` rejects. Set it `false` to accept
36
+ the slow context instead.
37
+
30
38
  ## What each backend can do
31
39
 
32
40
  | | WebGPU | WebGL 2 | Canvas |
@@ -36,21 +44,28 @@ WebGL 1 is not in the ladder — 20.x made WebGL 2 the baseline, and pinning
36
44
  | shaders and post effects | ✅ | ✅ | ❌ inert |
37
45
  | `Light2d` glow (`drawLight`) | ✅ | ✅ | ✅ cached radial gradient |
38
46
  | normal-map lighting, `Light3d` | ✅ | ✅ | ❌ inert |
39
- | `Mesh` | ✅ | ✅ | ⚠️ CPU-projected, no depth |
47
+ | `Mesh` | ✅ | ✅ | ⚠️ last resort: 2D camera only, CPU-projected, no depth, unlit |
40
48
  | `Camera3d` / depth sorting | ✅ | ✅ | ❌ warns, renders wrong |
41
49
  | `InstancedMesh` | ✅ | ✅ | ⚠️ one draw per instance |
42
50
  | GPU tilemap rendering | ✅ | ✅ | ❌ per-tile CPU path |
51
+ | `textureFilter: "linear-mipmap"` | ✅ | ✅ | ❌ no mipmaps |
52
+ | `hdr` (headroom through the effects) | ✅ | ✅ where the driver can render and filter half-float | ❌ |
53
+ | `hdrOutput` (HDR presentation) | ✅ with `hdr` on too | ❌ | ❌ |
54
+ | `state.transition("pixelate", …)` | ✅ | ✅ | ❌ the state changes with no transition |
43
55
 
44
- The Canvas column is the problem: none of it throws. Each of those paths warns
45
- once and then degrades or no-ops, so the game runs and looks wrong.
56
+ The Canvas column is the problem: none of it throws. Most of those paths warn
57
+ once and then degrade or no-op, and some (the pixelate transition, `hdr`) do
58
+ so without a word, so the game runs and looks wrong.
46
59
 
47
60
  Two Canvas rows are worth spelling out because they are easy to misread as total
48
61
  failures. A `Light2d` still *draws* — the Canvas renderer bakes a radial gradient
49
62
  per light and blits it — what disappears is per-pixel normal-map shading, which
50
63
  warns once (`Light2d normal-map lighting requires a GPU backend`), the first time
51
- a normal-mapped sprite is drawn under a light, and then renders sprites unlit. A `Mesh` still draws too, sorted with the painter's
52
- algorithm and no depth buffer, so convex models look right and anything
53
- self-overlapping does not. `"none"` (replace, alpha included) is the one blend
64
+ a normal-mapped sprite is drawn under a light, and then renders sprites unlit. A `Mesh` under a 2D camera still draws too, textured,
65
+ sorted with the painter's algorithm and no depth buffer, so convex models look
66
+ right and a self-overlapping one can draw a few triangles over the wrong ones.
67
+ That is a last resort so a game does not go blank, not a target: anything
68
+ built on meshes should run on WebGPU or WebGL 2. `"none"` (replace, alpha included) is the one blend
54
69
  mode Canvas cannot express — there is no `globalCompositeOperation` that
55
70
  disables blending for the drawn area alone — so it resolves to `"normal"`, and
56
71
  `setBlendMode` reports that back.
@@ -65,7 +80,9 @@ app.renderer.shaderLanguage; // "wgsl" | "glsl" | null (null on Canvas)
65
80
 
66
81
  There are narrower flags for the features that do not travel together:
67
82
  `supportsShaderTileLayers`, `supportsRetainedMesh` and `supportsInstancing` —
68
- all three `false` on Canvas, `true` on both GPU backends. Prefer a capability
83
+ all three `false` on Canvas, `true` on both GPU backends. `supportsHDR` and
84
+ `supportsHDROutput` say what the `hdr` and `hdrOutput` settings were actually
85
+ granted, which can be less than was asked. Prefer a capability
69
86
  flag over comparing `type` against a string; `type` is an identity check for
70
87
  code coupled to one backend's machinery.
71
88
 
@@ -160,12 +177,12 @@ or the `#webgpu` / `#webgl` URI fragment on a page you can open by hand.
160
177
  `OperationError: GPUQueue.copyExternalImageToTexture: External image state is
161
178
  not valid`, from a stack that reads `drawImage → addQuad → getBinding`. WebGL
162
179
  uploads the same source without complaint, so this is a class of bug that
163
- cannot appear on one backend and stops the game dead on the other. The usual
164
- source is a `Text` whose string is empty or whitespace-only — it measures to
165
- nothing, so its glyph canvas has no width. Give a placeholder label a real
166
- glyph and hide it with `alpha`/position instead of blanking its text.
167
- - **3D meshes do not honour blend modes.** The mesh pass draws with its own
168
- depth/blend state; asking for one of the advanced modes logs a one-time
180
+ cannot appear on one backend and stops the game dead on the other. Check
181
+ any canvas the game sizes from content that can be empty before it is drawn.
182
+ An empty or whitespace-only `Text` did this before 20.11; its glyph canvas
183
+ is now at least 32 pixels on each side.
184
+ - **3D meshes do not honour the advanced blend modes.** The mesh pass draws
185
+ with its own depth/blend state; asking for one of them logs a one-time
169
186
  `blend mode "…" is not supported for 3D meshes` warning and falls back to
170
187
  `"normal"`. Gradient fills warn the same way.
171
188
 
@@ -175,14 +192,18 @@ or the `#webgpu` / `#webgl` URI fragment on a page you can open by hand.
175
192
  |---|---|
176
193
  | effect works locally, not for a user | they got the Canvas fallback, or a different GPU backend |
177
194
  | sprites render unlit though lights are in the scene | Canvas fallback — normal-map shading is inert (check the console) |
178
- | mesh looks inside-out where it overlaps itself | Canvas fallback — painter's algorithm, no depth buffer |
195
+ | a few triangles of a mesh drawn over the wrong ones where it overlaps itself | Canvas fallback — painter's algorithm sorts whole triangles, no depth buffer |
196
+ | a mesh shows its far side through its near one (looks inside-out) on Canvas | melonJS older than 20.14 sorted the triangles the wrong way round; update |
197
+ | a textured OBJ + MTL mesh is a white silhouette on Canvas | melonJS older than 20.14 filled it with its material color and skipped the texture; update |
198
+ | a low-poly (palette) model looks faceted on Canvas, with faint lines between its triangles | melonJS older than 20.14 filled each of its triangles with one texel; update |
179
199
  | shader works on one machine only | GLSL-only effect, AUTO chose WebGPU elsewhere — the console carries the warning |
180
200
  | a solid box where an effect should be | carrier drawn while the effect is disabled |
181
201
  | nothing renders at all on an old device | `video.WEBGL` pinned; WebGL 1 is rejected, not downgraded |
182
202
  | ported shader is vertically flipped | the WebGL capture is bottom-up, the WebGPU one top-down |
183
- | `copyExternalImageToTexture: External image state is not valid` | a zero-sized upload source — most often a `Text` with an empty or whitespace-only string |
203
+ | `copyExternalImageToTexture: External image state is not valid` | a zero-sized upload source: a canvas sized from empty content (an empty `Text` before 20.11) |
204
+ | Canvas renderer on a machine that has a GPU, or `init()` rejects with a pinned backend | software rendering or a blocklisted driver, refused by `failIfMajorPerformanceCaveat` (default `true`) |
184
205
  | runs on the test machine, dies on the dev's | the harness fell back to WebGL and never exercised WebGPU |
185
- | a blended mode is ignored on a mesh | meshes fall back to `"normal"`; look for the one-time warning |
206
+ | an advanced blend mode is ignored on a mesh | meshes fall back to `"normal"` for those; look for the one-time warning |
186
207
  | textures vanish after the tab is restored | cached raw GPU handles not re-acquired on context restore |
187
208
 
188
209
  ## Related skills
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-scenes-and-state
3
- description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Tween, easing, fade out, flash, pulse, punch, shrink back, ease in, ease out, animate over time, countdown in update, Stage, state, state.set, state.change, state.transition, state.pause, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
3
+ description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Tween, easing, fade out, flash, pulse, punch, shrink back, ease in, ease out, animate over time, countdown in update, Stage, state, state.set, state.change, state.transition, pixelate, Trigger transition, retry level, state.pause, pauseOnBlur, resumeOnFocus, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -59,6 +59,12 @@ was drawn unstepped, and showed the stage's background alone for one frame.
59
59
  registered for that id, and `state.set` throws if the second argument is not a
60
60
  `Stage` instance. Neither fails quietly.
61
61
 
62
+ A change to the state that is **already current does nothing**: no
63
+ `onDestroyEvent`, no `onResetEvent`, no transition. So a "Retry" that calls
64
+ `state.change(state.PLAY)` from `PLAY` is ignored; reload the level with
65
+ `level.reload()`, or go through another state (a game-over or `READY` screen)
66
+ and back.
67
+
62
68
  ### Always leave `state.LOADING` — it is transitional
63
69
 
64
70
  `loader.preload()` switches to `state.LOADING` for you. Building your scene in
@@ -107,6 +113,9 @@ this.HUD.floating = true; // screen coordinates, not world
107
113
  app.world.addChild(this.HUD, 100); // explicit z — see melonjs-renderables
108
114
  ```
109
115
 
116
+ Everything inside a persistent container is kept with it, physics bodies
117
+ included (before 20.13 the children's bodies were dropped by the reset).
118
+
110
119
  ## Transitions and pausing
111
120
 
112
121
  ```js
@@ -119,11 +128,21 @@ app.freeze(150); // brief hit-stop (proxies state.fr
119
128
  ```
120
129
 
121
130
  `state.transition` is global and sticky — once set it applies to every later
122
- `state.change`. The `"mask"` form needs its fourth argument, an `Ellipse` or
131
+ `state.change`; `state.setTransition(id, false)` turns it off for changes
132
+ into one state.
133
+ The duration (1000 ms when omitted) is for each half: the cover, then the
134
+ reveal. The `"mask"` form needs its fourth argument, an `Ellipse` or
123
135
  `Polygon`; without one it warns and falls back to a direct switch. The
124
136
  `"pixelate"` form breaks the screen up into blocks (its fourth argument is the
125
- largest block size in pixels). It is a shader effect, so on Canvas it does
126
- nothing and the state changes directly.
137
+ largest block size in pixels; the color is not used). It is a shader effect,
138
+ so on Canvas it does nothing and the state changes directly.
139
+
140
+ A level change is not a state change, so `state.transition` does not apply to
141
+ it. A Tiled `Trigger` that loads a level has a transition of its own, set on
142
+ the trigger: `color` and `duration` (both needed, or the level loads with
143
+ none), and `transition: "fade"` (the default) or `"mask"` with a `shape`.
144
+ A `"mask"` without a `shape` fades instead, without a warning, and there is
145
+ no pixelate form for a trigger. See `melonjs-tilemaps`.
127
146
 
128
147
  A `state.change` asked for while a transition covers the screen (Enter pressed
129
148
  twice on a menu, a timer firing as the player skips a screen) needs no guard:
@@ -146,7 +165,8 @@ this.isDirty = state.current().update(this.updateDelta) || this.isDirty;
146
165
  Only `Container.update` consults the pause — it reads `state.isPaused()` and
147
166
  skips every child that is not `updateWhenPaused`. So world children (entities,
148
167
  sprites, meshes, a `GLTFModel`) freeze correctly, while **the Stage subclass's
149
- own `update()` keeps running**.
168
+ own `update()` keeps running**. (`event.GAME_UPDATE` is not emitted while
169
+ paused, so logic hung off it stops with the world.)
150
170
 
151
171
  A game that keeps its logic in entities pauses for free and never notices. A
152
172
  game that drives the simulation from `Stage.update` — common for an endless
@@ -172,6 +192,10 @@ Worth knowing that `pauseOnBlur` (default `true`) fires on a window `blur`, not
172
192
  only on tab `visibilitychange` — so clicking another window on the same screen
173
193
  triggers it while the canvas is still fully visible.
174
194
 
195
+ On focus, `resumeOnFocus` (default `true`) undoes only the pause the
196
+ application applied on blur. A game the player paused from its own menu stays
197
+ paused when the window comes back; before 20.9 it resumed underneath the menu.
198
+
175
199
  ## The update loop
176
200
 
177
201
  There is no loop you own. Logic goes in `update(dt)` overrides, or a
@@ -314,6 +338,14 @@ Four traps:
314
338
  - **Stop a tween before destroying its target**, or `onUpdate` fires against a
315
339
  dead renderable.
316
340
 
341
+ **A target can be an array: a path.** `to({ x: [200, 120, 300] })` runs from
342
+ the current value through each entry in turn, and
343
+ `interpolation: Tween.Interpolation.CatmullRom` smooths the corners. Each
344
+ `start()` builds the path afresh from the current value, so a replay or a chain
345
+ that comes back round runs the same path, and with `yoyo` the return leg plays
346
+ it back the way it came. Before 20.13 a restart ran a longer path each time and
347
+ a `yoyo` path turned the property into `NaN`.
348
+
317
349
  **To animate something that is not a property, tween a holder and push the
318
350
  value in `onUpdate`.** A shader uniform and a light's intensity are both set
319
351
  through a method, so there is nothing for `to()` to interpolate directly. The
@@ -387,6 +419,7 @@ import { save } from "melonjs";
387
419
 
388
420
  save.add({ hiscore: 0 }); // once; idempotent, loads any stored value
389
421
  save.hiscore = 1200; // plain assignment writes to localStorage
422
+ save.remove("hiscore"); // gone from localStorage; set it again to save it again
390
423
  ```
391
424
 
392
425
  **In TypeScript, keep what `add()` returns.** The namespace carries a
@@ -410,15 +443,22 @@ both.
410
443
  |---|---|
411
444
  | scene builds once and is empty on return | content created in the constructor, not `onResetEvent` |
412
445
  | `Undefined Stage for state 'N'` thrown | no `state.set` for that id first |
446
+ | `state.change(state.PLAY)` from `PLAY` does nothing ("Retry" ignored) | a change to the current state is a no-op — `level.reload()`, or go through another state |
447
+ | a level change through a `Trigger` ignores `state.transition` | it is a level change, not a state change: set `color` and `duration` (and `transition`) on the trigger |
448
+ | the game stops updating for good after one very slow update, such as a heavy first frame (20.12 and 20.13) | the loop could no longer save enough time to step the world: update |
449
+ | the game resumes under its own pause menu after a tab switch (before 20.9) | focus undid any pause; update, only the blur pause is undone |
413
450
  | a tween finishes in 1000 ms whatever you pass | `to()` takes `{ duration }`, not a number |
414
451
  | object frozen while its state updates | `update()` not returning `true` |
415
452
  | timers fire behind a pause menu | `window.setTimeout` instead of `timer.setTimeout` |
416
453
  | the world freezes on blur but the game keeps advancing | simulation lives in `Stage.update`, which the pause does not gate — only `Container.update` checks `state.isPaused()` |
417
454
  | the player character is left behind and scrolls off after a lost window | same cause: the stage advanced the camera while its world children were paused |
418
455
  | a tween does nothing | `.start()` never called |
456
+ | an array (path) tween wanders further on each replay, or goes `NaN` with `yoyo` | before 20.13 `start()` re-prepended the start value to the path and `yoyo` swapped the array into the start value; upgrade |
457
+ | a repeating or `yoyo` tween runs a single cycle when started again (a pulse pulses once, a yoyo stays at the far end) | before 20.13 `start()` did not restore the `repeat` count the first run had used up; upgrade |
419
458
  | effect stops during a hit-stop | tween needs `updateWhenPaused = true` |
420
459
  | crash after a stage switch | callback ran during teardown — guard with `state.isCurrent` |
421
460
  | HUD scrolls away with the camera | missing `floating = true` |
461
+ | a `save` key set again after `save.remove()` reads back fine, then is gone (or back at its default) after a reload (before 20.13) | the write skipped the list of keys restored at boot; update |
422
462
 
423
463
  ## Related skills
424
464
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-sprites-and-animation
3
- description: "Use this skill when creating sprites, playing frame animations, or working with texture atlases in melonJS. Covers Sprite construction from a plain image or an atlas region, addAnimation/setCurrentAnimation, TextureAtlas for TexturePacker/ShoeBox/Aseprite, tint, flip, NineSliceSprite, and the centred anchorPoint that offsets naive positioning. Triggers on: Sprite, new Sprite, addAnimation, setCurrentAnimation, isCurrentAnimation, TextureAtlas, TexturePacker, Aseprite, ShoeBox, atlas, region, framewidth, frameheight, tint, flipX, flipY, NineSliceSprite, animation."
3
+ description: "Use this skill when creating sprites, playing frame animations, or working with texture atlases in melonJS. Covers Sprite construction from a plain image or an atlas region, addAnimation/setCurrentAnimation, TextureAtlas for TexturePacker/ShoeBox/Aseprite, tint, flip, NineSliceSprite, and the centred anchorPoint that offsets naive positioning. Triggers on: Sprite, new Sprite, addAnimation, setCurrentAnimation, isCurrentAnimation, onFrame, onComplete, play, pause, flicker, anchorPoint, TextureAtlas, TexturePacker, Aseprite, ShoeBox, atlas, region, framewidth, frameheight, tint, flipX, flipY, NineSliceSprite, animation."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -20,6 +20,10 @@ sprites appear to disagree.
20
20
  const s = new Sprite(100, 100, { image: "hero", framewidth: 64, frameheight: 64 });
21
21
 
22
22
  s.anchorPoint.set(0, 0); // switch to top-left positioning if you prefer
23
+
24
+ // or in the settings, by name: "top-left", "bottom" (the feet), "center",
25
+ // "top", "left", "right", "top-right", "bottom-left", "bottom-right"
26
+ const hero = new Sprite(x, y, { image: "hero", anchorPoint: "bottom" });
23
27
  ```
24
28
 
25
29
  Writing top-left maths against the centred default puts everything half a sprite
@@ -93,7 +97,30 @@ attack) needs no manual switch back to idle.
93
97
 
94
98
  Re-selecting the animation that is already current is a no-op, so calling
95
99
  `setCurrentAnimation` every frame is safe, and an `isCurrentAnimation` check
96
- around it adds nothing. An unknown id throws.
100
+ around it adds nothing. **The exception is a `loop: false` run that has
101
+ finished**: selecting it again plays it again, so a death animation selected
102
+ every frame replays forever. Select a play-once animation once, on the event
103
+ that starts it. An unknown id throws.
104
+
105
+ A frame can carry its own delay, as `{ name, delay }` in place of the index or
106
+ region name: `[{ name: 3, delay: 200 }, { name: 4, delay: 100 }]`.
107
+ `play(name?, options?)` switches and plays (or, with no name, resumes),
108
+ `pause()` freezes the current frame and `stop()` rewinds to the first one and
109
+ pauses there; the options are `setCurrentAnimation`'s.
110
+
111
+ `addAnimation` with an id that exists replaces it, which is how you change an
112
+ animation's frames or speed (`hero.addAnimation("walk", [4, 5, 6, 7], 200)`).
113
+ Redefining the one that is playing takes effect at once: the playing frame is
114
+ kept if the new strip still has it, playback restarts on frame 0 if not, a
115
+ finished `loop: false` run holds the new last frame, and the
116
+ `setCurrentAnimation` options stay. Before 20.13 it kept the old frame count, so
117
+ a shorter strip threw on the next `update()` and a longer one never reached its
118
+ new frames.
119
+
120
+ Since 20.14, a frame the texture does not have (a number past the end of the
121
+ sheet, a name the atlas does not hold) is left out with a console warning
122
+ naming it, and the frames that exist play in order; `addAnimation` returns how
123
+ many it kept.
97
124
 
98
125
  ## Texture atlases
99
126
 
@@ -125,6 +152,9 @@ sprite.flipX(true); // mirror horizontally
125
152
  sprite.flipY(true);
126
153
  sprite.blendMode = "additive"; // see the effects skill for the full list
127
154
  sprite.scale(2, 2); // ← multiplicative, see below
155
+ sprite.flicker(1000, function () { // blink for a second, then…
156
+ this.ancestor.removeChild(this); // …`this` is the sprite; the flicker has ended
157
+ });
128
158
  ```
129
159
 
130
160
  **`scale()` is multiplicative, not absolute.** Calling it every frame compounds.
@@ -185,6 +215,11 @@ Two consequences:
185
215
  | event handlers leak on pooled objects | bound in the constructor rather than `onActivateEvent` |
186
216
  | animation never advances | no `framewidth`/`frameheight`, so the sheet is one frame |
187
217
  | animation plays once and stops | `{ loop: false }`, or a callback that returned `false` |
218
+ | a `loop: false` animation keeps replaying | `setCurrentAnimation` called every frame: a finished play-once run is restarted by selecting it again |
219
+ | a one-frame animation never calls its `onComplete` | a looping one-frame animation does not step; play it with `loop: false` or `next` (before 20.12 even those never completed) |
220
+ | `TypeError` reading `offset` or `delay` in `update()` after re-adding the playing animation with fewer frames, or a longer one never shows its new frames | before 20.13 redefining the current animation kept its old frame count; upgrade |
221
+ | a console warning `addAnimation("walk"): no frame … in the texture`, or a `TypeError` reading `delay` / `offset` on an older melonJS | a frame number or name the sheet or atlas does not have (often an atlas re-exported without one of its images): fix the frame list or re-export the atlas |
222
+ | `this` is wrong in a `flicker(ms, callback)` callback, or a flicker started from it stops at once | before 20.13.0 the callback ran on an internal object and the flicker was stopped after it; now `this` is the sprite and the flicker has already ended |
188
223
  | sprites do not batch | more distinct textures in one frame than `renderer.maxTextures` |
189
224
  | nine-slice corners stretch anyway | `insetX`/`insetY` instead of `insetx`/`insety` |
190
225