melonjs 20.2.0 → 20.3.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 (140) hide show
  1. package/README.md +86 -8
  2. package/build/application/settings.d.ts +3 -2
  3. package/build/application/settings.d.ts.map +1 -1
  4. package/build/audio/audio.d.ts +2 -2
  5. package/build/audio/audio.d.ts.map +1 -1
  6. package/build/audio/backend/buffer.d.ts +26 -0
  7. package/build/audio/backend/buffer.d.ts.map +1 -0
  8. package/build/audio/backend/cache.d.ts +2 -0
  9. package/build/audio/backend/cache.d.ts.map +1 -0
  10. package/build/audio/backend/core.d.ts +24 -0
  11. package/build/audio/backend/core.d.ts.map +1 -0
  12. package/build/audio/backend/engine.d.ts +54 -0
  13. package/build/audio/backend/engine.d.ts.map +1 -0
  14. package/build/audio/backend/sound.d.ts +79 -0
  15. package/build/audio/backend/sound.d.ts.map +1 -0
  16. package/build/audio/backend/spatial.d.ts +191 -0
  17. package/build/audio/backend/spatial.d.ts.map +1 -0
  18. package/build/audio/backend/types.d.ts +97 -0
  19. package/build/audio/backend/types.d.ts.map +1 -0
  20. package/build/audio/backend/voice.d.ts +33 -0
  21. package/build/audio/backend/voice.d.ts.map +1 -0
  22. package/build/audio/playback.d.ts +49 -4
  23. package/build/audio/playback.d.ts.map +1 -1
  24. package/build/audio/{backend.d.ts → state.d.ts} +13 -11
  25. package/build/audio/state.d.ts.map +1 -0
  26. package/build/audio/types.d.ts +93 -1
  27. package/build/audio/types.d.ts.map +1 -1
  28. package/build/camera/camera3d.d.ts +5 -5
  29. package/build/const.d.ts +2 -2
  30. package/build/index.d.ts.map +1 -1
  31. package/build/index.js +8793 -10171
  32. package/build/index.js.map +4 -4
  33. package/build/input/input.d.ts +19 -0
  34. package/build/input/input.d.ts.map +1 -1
  35. package/build/lang/deprecated.d.ts +4 -2
  36. package/build/lang/deprecated.d.ts.map +1 -1
  37. package/build/level/gltf/GLTFScene.d.ts +12 -0
  38. package/build/level/gltf/GLTFScene.d.ts.map +1 -1
  39. package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
  40. package/build/loader/loader.d.ts +4 -40
  41. package/build/loader/loader.d.ts.map +1 -1
  42. package/build/loader/parsers/audio.d.ts +31 -0
  43. package/build/loader/parsers/audio.d.ts.map +1 -0
  44. package/build/loader/parsers/shader.d.ts.map +1 -1
  45. package/build/physics/adapter.d.ts +3 -3
  46. package/build/physics/builtin/body.d.ts +23 -24
  47. package/build/physics/builtin/body.d.ts.map +1 -1
  48. package/build/physics/builtin/detector.d.ts +1 -1
  49. package/build/plugin/plugin.d.ts +9 -1
  50. package/build/plugin/plugin.d.ts.map +1 -1
  51. package/build/polyfill/index.d.ts +1 -4
  52. package/build/polyfill/index.d.ts.map +1 -1
  53. package/build/renderable/mesh.d.ts +16 -0
  54. package/build/renderable/mesh.d.ts.map +1 -1
  55. package/build/renderable/renderable.d.ts +16 -10
  56. package/build/renderable/renderable.d.ts.map +1 -1
  57. package/build/renderable/sprite.d.ts +7 -7
  58. package/build/renderable/sprite.d.ts.map +1 -1
  59. package/build/state/stage.d.ts +4 -2
  60. package/build/state/stage.d.ts.map +1 -1
  61. package/build/system/event.d.ts +1 -1
  62. package/build/system/platform.d.ts +21 -1
  63. package/build/system/platform.d.ts.map +1 -1
  64. package/build/utils/fetchdata.d.ts.map +1 -0
  65. package/build/video/blendmodes.d.ts +1 -1
  66. package/build/video/canvas/canvas_renderer.d.ts +5 -2
  67. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  68. package/build/video/effects/blur.d.ts +4 -4
  69. package/build/video/effects/blur.d.ts.map +1 -1
  70. package/build/video/effects/chromaticAberration.d.ts +5 -5
  71. package/build/video/effects/chromaticAberration.d.ts.map +1 -1
  72. package/build/video/effects/colorMatrix.d.ts +5 -5
  73. package/build/video/effects/colorMatrix.d.ts.map +1 -1
  74. package/build/video/effects/desaturate.d.ts +5 -5
  75. package/build/video/effects/desaturate.d.ts.map +1 -1
  76. package/build/video/effects/dissolve.d.ts +4 -4
  77. package/build/video/effects/dissolve.d.ts.map +1 -1
  78. package/build/video/effects/dropShadow.d.ts +5 -5
  79. package/build/video/effects/dropShadow.d.ts.map +1 -1
  80. package/build/video/effects/flash.d.ts +4 -4
  81. package/build/video/effects/flash.d.ts.map +1 -1
  82. package/build/video/effects/glow.d.ts +5 -5
  83. package/build/video/effects/glow.d.ts.map +1 -1
  84. package/build/video/effects/hologram.d.ts +4 -4
  85. package/build/video/effects/hologram.d.ts.map +1 -1
  86. package/build/video/effects/invert.d.ts +5 -5
  87. package/build/video/effects/invert.d.ts.map +1 -1
  88. package/build/video/effects/outline.d.ts +6 -6
  89. package/build/video/effects/outline.d.ts.map +1 -1
  90. package/build/video/effects/pixelate.d.ts +4 -4
  91. package/build/video/effects/pixelate.d.ts.map +1 -1
  92. package/build/video/effects/radialGradient.d.ts +2 -2
  93. package/build/video/effects/scanline.d.ts +6 -6
  94. package/build/video/effects/scanline.d.ts.map +1 -1
  95. package/build/video/effects/sepia.d.ts +5 -5
  96. package/build/video/effects/sepia.d.ts.map +1 -1
  97. package/build/video/effects/shadereffect.d.ts +12 -10
  98. package/build/video/effects/shadereffect.d.ts.map +1 -1
  99. package/build/video/effects/shine.d.ts +3 -3
  100. package/build/video/effects/shine.d.ts.map +1 -1
  101. package/build/video/effects/tintPulse.d.ts +4 -4
  102. package/build/video/effects/tintPulse.d.ts.map +1 -1
  103. package/build/video/effects/vignette.d.ts +6 -6
  104. package/build/video/effects/vignette.d.ts.map +1 -1
  105. package/build/video/effects/wave.d.ts +4 -4
  106. package/build/video/effects/wave.d.ts.map +1 -1
  107. package/build/video/texture/atlas.d.ts +1 -1
  108. package/build/video/webgl/glshader.d.ts +2 -2
  109. package/build/video/webgl/webgl_renderer.d.ts +2 -1
  110. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  111. package/build/video/webgpu/webgpu_renderer.d.ts +2 -1
  112. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  113. package/package.json +5 -9
  114. package/skills/AGENTS.md +67 -0
  115. package/skills/melonjs/SKILL.md +156 -0
  116. package/skills/melonjs-20-migration/SKILL.md +160 -0
  117. package/skills/melonjs-3d/SKILL.md +226 -0
  118. package/skills/melonjs-3d-assets/SKILL.md +239 -0
  119. package/skills/melonjs-audio/SKILL.md +215 -0
  120. package/skills/melonjs-camera-and-drawing/SKILL.md +171 -0
  121. package/skills/melonjs-deployment/SKILL.md +113 -0
  122. package/skills/melonjs-effects-and-shaders/SKILL.md +177 -0
  123. package/skills/melonjs-events/SKILL.md +159 -0
  124. package/skills/melonjs-getting-started/SKILL.md +183 -0
  125. package/skills/melonjs-input/SKILL.md +166 -0
  126. package/skills/melonjs-lighting/SKILL.md +139 -0
  127. package/skills/melonjs-loading-assets/SKILL.md +157 -0
  128. package/skills/melonjs-particles-and-trails/SKILL.md +136 -0
  129. package/skills/melonjs-performance/SKILL.md +178 -0
  130. package/skills/melonjs-physics/SKILL.md +321 -0
  131. package/skills/melonjs-plugins/SKILL.md +191 -0
  132. package/skills/melonjs-renderables/SKILL.md +214 -0
  133. package/skills/melonjs-renderer-backends/SKILL.md +173 -0
  134. package/skills/melonjs-scenes-and-state/SKILL.md +180 -0
  135. package/skills/melonjs-sprites-and-animation/SKILL.md +173 -0
  136. package/skills/melonjs-tilemaps/SKILL.md +193 -0
  137. package/skills/melonjs-ui-and-text/SKILL.md +175 -0
  138. package/build/audio/backend.d.ts.map +0 -1
  139. package/build/loader/parsers/fetchdata.d.ts.map +0 -1
  140. /package/build/{loader/parsers → utils}/fetchdata.d.ts +0 -0
@@ -0,0 +1,214 @@
1
+ ---
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: Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating."
4
+ license: MIT
5
+ ---
6
+
7
+ # Renderables, input and effects
8
+
9
+ > Subclassing `Renderable`, custom drawing, receiving pointer events, and
10
+ > applying post effects — with the traps that produce silently wrong output
11
+ > rather than an error.
12
+
13
+ Everything here is a **silent failure**: the code runs, nothing throws, and the
14
+ result is wrong. Those are the cases worth memorising, because nothing in the
15
+ type signatures warns you.
16
+
17
+ ## 1. A custom `draw()` must position itself from `this.pos`
18
+
19
+ The renderer is **not** pre-translated to the renderable's position when `draw()`
20
+ is called. `Container.draw` translates by the *container's* own `pos` and then
21
+ calls `child.preDraw` / `draw` / `postDraw`; `preDraw` never translates by the
22
+ child's `pos`. Read `this.pos` and draw relative to it.
23
+
24
+ ```js
25
+ class Marker extends Renderable {
26
+ draw(renderer) {
27
+ // ✗ draws at the parent container's origin, not at the marker
28
+ renderer.fillRect(0, 0, this.width, this.height);
29
+
30
+ // ✓
31
+ renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height);
32
+ }
33
+ }
34
+ ```
35
+
36
+ The symptom is an object that renders correctly at position (0, 0) and appears
37
+ stuck at the top-left as soon as you move it.
38
+
39
+ **Zero the anchor in custom-draw renderables.** `preDraw` does not translate to
40
+ `pos`, but it *does* apply the anchor offset `(-width * anchorPoint.x,
41
+ -height * anchorPoint.y)`. With the default centred anchor, drawing at
42
+ `this.pos` therefore lands half a size off:
43
+
44
+ ```js
45
+ constructor(x, y, w, h) {
46
+ super(x, y, w, h);
47
+ this.anchorPoint.set(0, 0); // ← or every custom draw is offset by -w/2, -h/2
48
+ }
49
+ ```
50
+
51
+ ## 2. `isKinematic` must be `false` to receive input or collisions
52
+
53
+ `Renderable.isKinematic` **defaults to `true`**, which means *"physics collision
54
+ and input events will not impact this renderable"*. A kinematic renderable is
55
+ skipped by the pointer-event dispatcher entirely.
56
+
57
+ ```js
58
+ class Button extends Renderable {
59
+ constructor(x, y) {
60
+ super(x, y, 100, 40);
61
+ this.isKinematic = false; // ← required, or clicks never arrive
62
+ input.registerPointerEvent("pointerdown", this, () => { /* … */ });
63
+ }
64
+ }
65
+ ```
66
+
67
+ The symptom is a handler that is registered, looks correct, and never fires.
68
+
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.
73
+
74
+ The same flag has a second, less obvious effect: it is also the opt-in to the
75
+ world **broadphase**. The quadtree/octree insert skips any child with
76
+ `isKinematic === true`, so body-less objects you want `adapter.queryAABB` /
77
+ `querySphere` to find need `isKinematic = false` as well. The builtin
78
+ narrowphase likewise requires *both* partners to be non-kinematic.
79
+
80
+ ## 3. Draw order: only `addChild(child, z)` works
81
+
82
+ There are three separate traps here and they compound.
83
+
84
+ ```js
85
+ world.addChild(sprite, 10); // ✓ the only reliable form
86
+ ```
87
+
88
+ 1. **`renderable.z` does not exist.** There is no `z` accessor anywhere on
89
+ `Renderable` (nor on `Rect`/`Polygon` above it) — only `depth`, which proxies
90
+ `pos.z`, and the container's default `sortOn: "z"` comparator reads `pos.z`.
91
+ Writing `sprite.z = 100` creates an inert property that nothing reads.
92
+ Several shipped examples still contain `this.z = Number.POSITIVE_INFINITY`;
93
+ those HUDs draw on top only because they happen to be added last. (A `z` key
94
+ in a `Sprite`/`ImageLayer` *settings* object is a different thing and is real
95
+ — it assigns `pos.z` in the constructor — but trap 2 then overwrites it.)
96
+ 2. **Setting `depth` *before* `addChild` is overwritten.** With `autoDepth` on
97
+ (the default), `addChild` without an explicit `z` assigns
98
+ `child.pos.z = children.length` after the push, discarding whatever you set.
99
+ 3. **Setting `depth` *after* `addChild` does not reorder.** Containers sort when
100
+ a child is added (`autoSort`, deferred to the next tick), not when `pos.z`
101
+ changes. If you must change it later, call `container.sort()` yourself.
102
+
103
+ The exception: under `Camera3d` the world's `sortOn` is `"depth"`, and the
104
+ camera calls `container.sortNow(true)` every frame. Note that mode does **not**
105
+ sort by `pos.z` as a layer index — it sorts by squared distance from the camera
106
+ in world space (nearest first in the array, painted far→near). So changing
107
+ `depth` does reorder there, but as a position change, not as a layer number.
108
+
109
+ ```js
110
+ // ✗ overwritten by autoDepth
111
+ sprite.depth = 10;
112
+ world.addChild(sprite);
113
+
114
+ // ✗ no resort happens (except under Camera3d)
115
+ world.addChild(sprite);
116
+ sprite.depth = 10;
117
+
118
+ // ✓
119
+ world.addChild(sprite, 10);
120
+ ```
121
+
122
+ ## 4. `removePostEffect()` destroys the effect
123
+
124
+ To disable an effect temporarily, toggle it. Removing it frees its GPU
125
+ resources and leaves you holding a dead object.
126
+
127
+ ```js
128
+ // ✗ effect is destroyed; re-adding it will not work
129
+ sprite.removePostEffect(effect);
130
+
131
+ // ✓
132
+ effect.enabled = false;
133
+ ```
134
+
135
+ Exception worth knowing: an effect with `effect.shared === true` opts out of
136
+ auto-destroy, because it is reused across renderables.
137
+
138
+ Use `addPostEffect()` / `getPostEffect()` / `removePostEffect()`. Assigning
139
+ `renderable.shader = …` is deprecated since 19.2.0.
140
+
141
+ Post effects need a programmable pipeline. A `ShaderEffect` constructed against
142
+ the Canvas renderer (`renderer.shaderLanguage === null`) logs a warning, stays
143
+ at `enabled === false` and no-ops every method rather than throwing — and
144
+ `beginPostEffect` filters disabled effects out. Since `video.AUTO` picks the
145
+ backend at runtime, an effect that works on your machine may do nothing on
146
+ another.
147
+
148
+ ## 5. `Vector3d.set(x, y)` silently zeroes `z`
149
+
150
+ ```js
151
+ v.set(10, 20); // z becomes 0, not "unchanged"
152
+ v.set(10, 20, v.z); // keep it
153
+ ```
154
+
155
+ `pos` is an `ObservableVector3d`, whose `set(x = 0, y = 0, z = 0)` does the same
156
+ — so `this.pos.set(x, y)` wipes the object's depth.
157
+
158
+ ## Update and draw
159
+
160
+ ```js
161
+ class Enemy extends Renderable {
162
+ update(dt) {
163
+ this.pos.x += this.speed * (dt / 1000);
164
+ return true; // ← true means "redraw me"
165
+ }
166
+ draw(renderer) { /* … */ }
167
+ }
168
+ ```
169
+
170
+ `update()` returns a boolean meaning *"this object changed and needs
171
+ redrawing"*. The parent accumulates it into the container's `isDirty`, and the
172
+ application skips the whole draw pass while nothing is dirty — so returning
173
+ nothing (falsy) can leave the scene frozen even though state is updating. The
174
+ base implementation returns `this.isDirty`, so `return super.update(dt)` is the
175
+ other correct ending.
176
+
177
+ `update()` is only called when `obj.inViewport === true` or
178
+ `obj.alwaysUpdate === true`. An off-screen object with the default
179
+ `alwaysUpdate = false` does not tick at all.
180
+
181
+ ## Anchors and floating
182
+
183
+ - `anchorPoint` defaults to centre `(0.5, 0.5)` on `Renderable`, but `Container`
184
+ forces `(0, 0)`. Mixing them up shifts children by half their size.
185
+ - `floating = true` pins a renderable to the screen rather than the world, which
186
+ is what you want for HUD elements. It opts the object out of camera transforms
187
+ and out of ancestor position accumulation.
188
+
189
+ ## Prefer built-in features
190
+
191
+ Before writing custom `draw()` code, check whether the engine already covers it —
192
+ `ParticleEmitter`, `Tween`, the `ShaderEffect` presets, `UISpriteElement` and
193
+ `UITextButton`, the physics adapters, and the Tiled tilemap renderers all exist.
194
+ Hand-rolled equivalents miss the batching and the multi-backend support.
195
+
196
+ ## Symptom → cause
197
+
198
+ | symptom | cause |
199
+ |---|---|
200
+ | object draws at its parent's origin, ignores its position | `draw()` not reading `this.pos` |
201
+ | pointer handler registered but never fires | `isKinematic` left at its `true` default |
202
+ | wrong draw order | anything other than `addChild(child, z)` — see section 3 |
203
+ | effect cannot be re-enabled after removal | `removePostEffect()` destroyed it |
204
+ | effect silently does nothing | running on the Canvas renderer |
205
+ | object appears frozen while its state changes | `update()` not returning `true` |
206
+ | off-screen object never ticks | `update()` is gated on `inViewport` — set `alwaysUpdate = true` |
207
+ | custom draw offset by half the size | centred `anchorPoint` default not zeroed |
208
+ | spatial query never finds an object | `isKinematic` left `true` — not in the broadphase |
209
+ | `z` unexpectedly 0 after a `set` | `Vector3d.set(x, y)` defaults `z` to 0 |
210
+
211
+ ## Related skills
212
+
213
+ - `melonjs-getting-started` — Application lifecycle and the scene graph
214
+ - `melonjs-20-migration` — the deprecated `shader` property and other pre-20 APIs
@@ -0,0 +1,173 @@
1
+ ---
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."
4
+ license: MIT
5
+ ---
6
+
7
+ # Renderer backends and graceful degradation
8
+
9
+ > `video.AUTO` picks a backend at **runtime**. The same code runs on a different
10
+ > renderer depending on the machine, and several subsystems degrade to nothing
11
+ > rather than erroring. This is the root cause of most "works on my machine"
12
+ > reports.
13
+
14
+ ## The ladder
15
+
16
+ `video.AUTO` (the default) tries **WebGPU → WebGL 2 → Canvas** and uses the first
17
+ that initialises.
18
+
19
+ ```js
20
+ const app = new Application(800, 600, {
21
+ parent: "screen",
22
+ renderer: video.AUTO, // or video.WEBGPU / video.WEBGL / video.CANVAS
23
+ });
24
+ await app.init();
25
+ ```
26
+
27
+ WebGL 1 is not in the ladder — 20.x made WebGL 2 the baseline, and pinning
28
+ `video.WEBGL` **rejects** on a WebGL-1-only device rather than degrading.
29
+
30
+ ## What each backend can do
31
+
32
+ | | WebGPU | WebGL 2 | Canvas |
33
+ |---|---|---|---|
34
+ | sprites, tilemaps, text | ✅ | ✅ | ✅ |
35
+ | blend modes | ✅ | ✅ | ✅ except `"none"` |
36
+ | shaders and post effects | ✅ | ✅ | ❌ inert |
37
+ | `Light2d` glow (`drawLight`) | ✅ | ✅ | ✅ cached radial gradient |
38
+ | normal-map lighting, `Light3d` | ✅ | ✅ | ❌ inert |
39
+ | `Mesh` | ✅ | ✅ | ⚠️ CPU-projected, no depth |
40
+ | `Camera3d` / depth sorting | ✅ | ✅ | ❌ warns, renders wrong |
41
+ | `InstancedMesh` | ✅ | ✅ | ⚠️ one draw per instance |
42
+ | GPU tilemap rendering | ✅ | ✅ | ❌ per-tile CPU path |
43
+
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.
46
+
47
+ Two Canvas rows are worth spelling out because they are easy to misread as total
48
+ failures. A `Light2d` still *draws* — the Canvas renderer bakes a radial gradient
49
+ per light and blits it — what disappears is per-pixel normal-map shading, which
50
+ warns once (`Light2d normal-map lighting requires the WebGL renderer`) and then
51
+ 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
54
+ mode Canvas cannot express — there is no `globalCompositeOperation` that
55
+ disables blending for the drawn area alone — so it resolves to `"normal"`, and
56
+ `setBlendMode` reports that back.
57
+
58
+ ## Detecting what you have
59
+
60
+ ```js
61
+ app.renderer.type; // "WebGPU" | "WebGL2" | "CANVAS"
62
+ app.renderer.supportsDepthBuffer; // the practical "is this a GPU backend" test
63
+ app.renderer.shaderLanguage; // "wgsl" | "glsl" | null (null on Canvas)
64
+ ```
65
+
66
+ There are narrower flags for the features that do not travel together:
67
+ `supportsShaderTileLayers`, `supportsRetainedMesh` and `supportsInstancing` —
68
+ all three `false` on Canvas, `true` on both GPU backends. Prefer a capability
69
+ flag over comparing `type` against a string; `type` is an identity check for
70
+ code coupled to one backend's machinery.
71
+
72
+ For a scene that genuinely requires a GPU backend, fail loudly rather than
73
+ rendering something broken:
74
+
75
+ ```js
76
+ await app.init();
77
+ if (!app.renderer.supportsDepthBuffer) {
78
+ throw new Error("this game needs WebGL 2 or WebGPU");
79
+ }
80
+ ```
81
+
82
+ ## Writing code that degrades well
83
+
84
+ **Decorative effects** — let them no-op. A missing vignette is fine.
85
+
86
+ **Load-bearing effects** — guard the carrier. If a shader draws your content
87
+ rather than decorating it, the carrier renderable shows through raw when the
88
+ effect is disabled: a 1×1 white texture stretched to fill a quad renders as a
89
+ white box exactly where the effect should have been.
90
+
91
+ ```js
92
+ if (effect.enabled) {
93
+ // draw the carrier the shader consumes
94
+ }
95
+ ```
96
+
97
+ **Dual-language shaders** — a `ShaderEffect` compiles the body matching
98
+ `renderer.shaderLanguage`. With no matching body — a GLSL-only effect on WebGPU,
99
+ any effect on Canvas — the constructor warns once, leaves `enabled === false`,
100
+ and every method becomes a no-op. Pass `{ glsl, wgsl }` rather than a bare
101
+ string, or gate on `renderer.shaderLanguage`.
102
+
103
+ ## Pinning a backend
104
+
105
+ Pin only with a reason. `video.WEBGL` costs you the WebGPU path *and* the Canvas
106
+ fallback, so a WebGL-1-only device gets nothing at all.
107
+
108
+ Legitimate reasons: reproducing a backend-specific bug, a benchmark, or a known
109
+ driver issue in the field.
110
+
111
+ For one-off testing you do not need to touch the settings at all: the URI
112
+ fragments `#webgpu`, `#webgl` (or `#webgl2`) and `#canvas` override the
113
+ `renderer` setting for that run, and behave exactly like passing the matching
114
+ constant — `#webgl` on a WebGL-1-only device rejects rather than falling back.
115
+
116
+ ## Context loss
117
+
118
+ A GPU context can be lost at any time — tab backgrounding, driver reset, memory
119
+ pressure. melonJS handles restoration on both GPU backends: WebGL recompiles
120
+ every live `GLShader` and replays its uniform snapshot, WebGPU renegotiates a
121
+ device and rebuilds its batchers, and both then emit
122
+ `event.ONCONTEXT_RESTORED` (the paired loss event is `event.ONCONTEXT_LOST`).
123
+
124
+ What you must handle is your own GPU-side state. If you cached a raw texture or
125
+ program handle, re-acquire it on restore rather than holding the stale one:
126
+
127
+ ```js
128
+ import { event } from "melonjs";
129
+
130
+ event.on(event.ONCONTEXT_RESTORED, (renderer) => {
131
+ // re-acquire anything you were holding by raw handle
132
+ });
133
+ ```
134
+
135
+ ## Practical differences
136
+
137
+ - **Frame capture orientation differs**: row 0 of a `toFrameTexture()` capture is
138
+ the bottom of the frame under WebGL and the *top* under WebGPU, so a GLSL body
139
+ that flips with `1.0 - uv.y` must not do so in its WGSL twin. The WebGPU
140
+ capture also preserves alpha, where the WebGL one captures into an opaque RGB
141
+ texture.
142
+ - **The advanced blend modes cost far more than they look.** `overlay`,
143
+ `difference`, `hard-light`, `soft-light`, `color-dodge`, `color-burn`,
144
+ `darken` and `lighten` cannot be expressed as fixed-function blending, so on
145
+ both GPU backends each such draw is bracketed individually: capture the
146
+ destination, redraw offscreen, composite through `BlendEffect`. Fine for
147
+ accents, ruinous for hundreds of objects. `normal`, `additive`, `multiply`,
148
+ `screen` and `exclusion` are ordinary fixed-function state and cost nothing
149
+ extra.
150
+ - **3D meshes do not honour blend modes.** The mesh pass draws with its own
151
+ depth/blend state; asking for one of the advanced modes logs a one-time
152
+ `blend mode "…" is not supported for 3D meshes` warning and falls back to
153
+ `"normal"`. Gradient fills warn the same way.
154
+
155
+ ## Symptom → cause
156
+
157
+ | symptom | cause |
158
+ |---|---|
159
+ | effect works locally, not for a user | they got the Canvas fallback, or a different GPU backend |
160
+ | sprites render unlit though lights are in the scene | Canvas fallback — normal-map shading is inert (check the console) |
161
+ | mesh looks inside-out where it overlaps itself | Canvas fallback — painter's algorithm, no depth buffer |
162
+ | shader works on one machine only | GLSL-only effect, AUTO chose WebGPU elsewhere — the console carries the warning |
163
+ | a solid box where an effect should be | carrier drawn while the effect is disabled |
164
+ | nothing renders at all on an old device | `video.WEBGL` pinned; WebGL 1 is rejected, not downgraded |
165
+ | ported shader is vertically flipped | the WebGL capture is bottom-up, the WebGPU one top-down |
166
+ | a blended mode is ignored on a mesh | meshes fall back to `"normal"`; look for the one-time warning |
167
+ | textures vanish after the tab is restored | cached raw GPU handles not re-acquired on context restore |
168
+
169
+ ## Related skills
170
+
171
+ - `melonjs-getting-started` — renderer selection in the Application settings
172
+ - `melonjs-effects-and-shaders` — dual-language shaders and the Canvas no-op
173
+ - `melonjs-3d` — the tier that requires a GPU backend outright
@@ -0,0 +1,180 @@
1
+ ---
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: 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."
4
+ license: MIT
5
+ ---
6
+
7
+ # Scenes, game state and timing
8
+
9
+ ## Stages and the state manager
10
+
11
+ A `Stage` is a screen. Register it against a state id, then change to it:
12
+
13
+ ```js
14
+ import { Stage, state } from "melonjs";
15
+
16
+ class PlayScreen extends Stage {
17
+ onResetEvent(app) { /* build the scene */ }
18
+ onDestroyEvent(app) { /* tear it down */ }
19
+ }
20
+
21
+ state.set(state.PLAY, new PlayScreen()); // register first
22
+ state.change(state.PLAY); // then switch
23
+ ```
24
+
25
+ Built-in ids: `state.LOADING`, `state.MENU`, `state.READY`, `state.PLAY`,
26
+ `state.GAMEOVER` (no underscore), `state.GAME_END`, `state.SCORE`,
27
+ `state.CREDITS`, `state.SETTINGS`, `state.DEFAULT`. `state.USER` (100) is the
28
+ base for your own ids — `const CUTSCENE = state.USER + 1`.
29
+
30
+ **Build scene content in `onResetEvent`, not the constructor.** `onResetEvent`
31
+ runs on *every* entry to the state; the constructor runs once. Scene content
32
+ created in the constructor exists once and is not rebuilt when you return.
33
+
34
+ `onResetEvent(app)` receives the `Application` — use that rather than importing
35
+ the global `game`.
36
+
37
+ Extra arguments to `state.change` are forwarded:
38
+
39
+ ```js
40
+ state.change(state.PLAY, false, { level: 2 }); // → onResetEvent(app, { level: 2 })
41
+ ```
42
+
43
+ The second argument is `forceChange`: pass `true` to switch immediately instead
44
+ of on a deferred tick, which is what you want when changing state right after a
45
+ preload completes. It is ignored when a transition is configured — a transition
46
+ always drives the switch from its own completion callback.
47
+
48
+ `state.change` **throws** `Undefined Stage for state '<id>'` if nothing was
49
+ registered for that id, and `state.set` throws if the second argument is not a
50
+ `Stage` instance. Neither fails quietly.
51
+
52
+ ## Persistent objects across levels
53
+
54
+ ```js
55
+ if (typeof this.HUD === "undefined") {
56
+ this.HUD = new Container(); // construct once (or UIBaseElement)
57
+ }
58
+ this.HUD.isPersistent = true; // survives level changes
59
+ this.HUD.floating = true; // screen coordinates, not world
60
+ app.world.addChild(this.HUD, 100); // explicit z — see melonjs-renderables
61
+ ```
62
+
63
+ ## Transitions and pausing
64
+
65
+ ```js
66
+ state.transition("fade", "#FFFFFF", 250);
67
+ state.transition("mask", "#000", 500, new Ellipse(0, 0, 1, 1)); // shape required
68
+ state.pause(); // freeze update, keep drawing
69
+ state.resume();
70
+ app.freeze(150); // brief hit-stop (proxies state.freeze)
71
+ ```
72
+
73
+ `state.transition` is global and sticky — once set it applies to every later
74
+ `state.change`. The `"mask"` form needs its fourth argument, an `Ellipse` or
75
+ `Polygon`; without one it warns and falls back to a direct switch.
76
+
77
+ ## The update loop
78
+
79
+ There is no loop you own. Logic goes in `update(dt)` overrides, or a
80
+ `GAME_UPDATE` subscription for scene-level animation:
81
+
82
+ ```js
83
+ event.on(event.GAME_UPDATE, () => { /* per frame */ });
84
+ ```
85
+
86
+ `update(dt)` must **return `true`** when the object changed and needs redrawing.
87
+ Returning nothing is falsy and the object can appear frozen while its state moves.
88
+
89
+ `dt` is milliseconds since the last frame and is the right basis for motion —
90
+ the engine already paces it. Do not reach for `performance.now()`.
91
+
92
+ A common idiom for a per-frame game controller with nothing to draw is a
93
+ `Renderable(0, 0, 1, 1)` with `alwaysUpdate = true` and an empty `draw()`.
94
+
95
+ ## Timers: use the engine's, not the window's
96
+
97
+ ```js
98
+ import { timer } from "melonjs";
99
+
100
+ timer.setTimeout(fn, 1000);
101
+ const id = timer.setInterval(fn, 500);
102
+ timer.clearInterval(id); // in the Stage's onDestroyEvent
103
+ ```
104
+
105
+ `Stage` has only `onResetEvent` and `onDestroyEvent` — there is no
106
+ `onActivateEvent` / `onDeactivateEvent` on a stage. Those two are `Renderable`
107
+ hooks; put per-renderable teardown there instead.
108
+
109
+ Engine timers are **pause-aware**. `window.setTimeout` keeps firing while the
110
+ game is paused, which produces enemies spawning behind a pause menu. Pass
111
+ `false` as the third argument — `timer.setInterval(fn, 500, false)` — for one
112
+ that keeps running through a pause, which is what a pause-menu animation or a
113
+ countdown that should not freeze needs. (That argument was ignored by
114
+ `setInterval` before 20.3.)
115
+
116
+ ## Tweens
117
+
118
+ ```js
119
+ import { Tween, pool } from "melonjs";
120
+
121
+ const t = pool.pull("Tween", sprite.pos) // registered name is "Tween"
122
+ .to({ x: 300 }, { duration: 500 }) // options object, not a number
123
+ .easing(Tween.Easing.Quadratic.Out)
124
+ .onComplete(() => { /* … */ })
125
+ .start(); // ← without this, nothing happens
126
+ ```
127
+
128
+ Four traps:
129
+
130
+ - **A tween without `.start()`** (or `autoStart: true` in the `to()` options)
131
+ silently does nothing.
132
+ - **`to()`'s second argument is an options object**
133
+ (`{ duration, easing, yoyo, repeat, delay, repeatDelay, interpolation,
134
+ autoStart }`). Passing a bare number is not a duration — it is read for a
135
+ `.duration` property, finds none, and the tween silently runs the default
136
+ 1000 ms.
137
+ - **Tweens freeze during `state.pause()` / `freeze()`** unless
138
+ `tween.updateWhenPaused = true` — which is how you get an effect that decays
139
+ *through* a hit-stop.
140
+ - **Stop a tween before destroying its target**, or `onUpdate` fires against a
141
+ dead renderable.
142
+
143
+ ## Guard teardown-adjacent callbacks
144
+
145
+ Callbacks can fire while a stage is being torn down, when the current state is
146
+ already something else:
147
+
148
+ ```js
149
+ if (!state.isCurrent(state.PLAY)) return;
150
+ ```
151
+
152
+ Without the guard, a "children emptied" handler can reset the wrong stage.
153
+
154
+ ## Persistence
155
+
156
+ ```js
157
+ import { save } from "melonjs";
158
+
159
+ save.add({ hiscore: 0 }); // once; idempotent, loads any stored value
160
+ save.hiscore = 1200; // plain assignment writes to localStorage
161
+ ```
162
+
163
+ ## Symptom → cause
164
+
165
+ | symptom | cause |
166
+ |---|---|
167
+ | scene builds once and is empty on return | content created in the constructor, not `onResetEvent` |
168
+ | `Undefined Stage for state 'N'` thrown | no `state.set` for that id first |
169
+ | a tween finishes in 1000 ms whatever you pass | `to()` takes `{ duration }`, not a number |
170
+ | object frozen while its state updates | `update()` not returning `true` |
171
+ | timers fire behind a pause menu | `window.setTimeout` instead of `timer.setTimeout` |
172
+ | a tween does nothing | `.start()` never called |
173
+ | effect stops during a hit-stop | tween needs `updateWhenPaused = true` |
174
+ | crash after a stage switch | callback ran during teardown — guard with `state.isCurrent` |
175
+ | HUD scrolls away with the camera | missing `floating = true` |
176
+
177
+ ## Related skills
178
+
179
+ - `melonjs-getting-started` — the Application and the first scene
180
+ - `melonjs-renderables` — `floating`, draw order, update/draw contracts