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,215 @@
1
+ ---
2
+ name: melonjs-audio
3
+ description: "Use this skill for sound and music in melonJS — loading audio, playing effects, background tracks, audio sprites, volume and muting, spatial audio, and procedural tone/noise generation. Covers the mandatory audio.init format list, the directory-not-file src convention, playTrack versus play, and browser autoplay unlocking. Triggers on: audio, sound, audio.init, audio.play, playTrack, stopTrack, audio.load, sfx, music, volume, mute, sprite audio, stereo, panner, audio.tone, audio.noise, getAudioContext, autoplay."
4
+ license: MIT
5
+ ---
6
+
7
+ # Audio
8
+
9
+ ## Two setup rules
10
+
11
+ ```js
12
+ import { audio, loader } from "melonjs";
13
+
14
+ audio.init("mp3,ogg"); // 1. BEFORE preloading any audio
15
+
16
+ await loader.preload([
17
+ { name: "cling", type: "audio", src: "data/sfx/" }, // 2. a PREFIX, not a file
18
+ ]);
19
+ ```
20
+
21
+ 1. **`audio.init(format)` must run before any audio asset is preloaded**, and
22
+ lists the formats you actually ship. Preloading first throws
23
+ `target audio extension(s) should be set through me.audio.init() before
24
+ calling the preloader.` It returns `false` when no audio backend is
25
+ available, so it is worth checking. The format list defaults to `"mp3"`.
26
+ 2. **`src` is a prefix, not a file.** The loader appends the asset `name` plus
27
+ each configured extension and uses the first that loads — so
28
+ `data/sfx/` + `cling` + `mp3` → `data/sfx/cling.mp3`. Passing a full filename
29
+ gives you a 404. A `data:audio/...` URL is the one exception: it is used
30
+ verbatim, prefix and extension skipped.
31
+
32
+ Order matters — melonJS tries the listed formats left to right, so put the
33
+ preferred one first. Two formats (`"webm,mp3"` or `"mp3,ogg"`) is the usual
34
+ belt-and-braces for codec coverage. Accepted tokens: `mp3`, `mpeg`, `opus`,
35
+ `ogg`, `oga`, `wav`, `aac`, `caf`, `m4a`, `m4b`, `mp4`, `weba`, `webm`, `dolby`,
36
+ `flac`.
37
+
38
+ ## Effects versus music
39
+
40
+ ```js
41
+ audio.play("cling"); // fire and forget
42
+ audio.play("cling", false, null, 0.5); // loop, onend, volume
43
+ const id = audio.play("engine", true); // looping; keep the id to stop it
44
+
45
+ audio.playTrack("theme"); // background music
46
+ audio.stopTrack();
47
+ ```
48
+
49
+ Use **`playTrack`** for music, not `play`. `playTrack(name, volume?)` always
50
+ loops, and it records the name as the current track — which is what
51
+ `app.pauseOnBlur` / `resumeOnFocus` / `stopOnBlur` act on, via
52
+ `state.pause(true)` → `pauseTrack()`. `stopTrack()`, `pauseTrack()`,
53
+ `resumeTrack()` and `getCurrentTrack()` all read that same single slot, so there
54
+ is only ever one current track. Stop it in `onDestroyEvent`.
55
+
56
+ Every per-clip function throws `audio clip <name> does not exist` if the clip was
57
+ never loaded, and `play` additionally throws when a named sprite region is
58
+ missing.
59
+
60
+ ## Audio sprites
61
+
62
+ One file carrying many effects, with named regions in milliseconds:
63
+
64
+ ```js
65
+ await loader.preload([{
66
+ name: "sfx", type: "audio", src: "data/sfx/",
67
+ sprite: { jump: [0, 450], hit: [2000, 250], music: [4000, 12000, true] },
68
+ }]);
69
+
70
+ audio.play("sfx", { sprite: "jump" });
71
+ ```
72
+
73
+ The optional third element marks a region as looping. `play()`'s second argument
74
+ takes either the original `loop` boolean or a `PlayOptions` object
75
+ (`sprite`, `loop`, `onend`, `volume`) — both forms work. Object fields win over
76
+ the positional `onend` / `volume` arguments when both are supplied.
77
+
78
+ ## Clip state
79
+
80
+ ```js
81
+ audio.duration("theme"); // seconds — 0 while still loading
82
+ audio.duration("sfx", id); // duration of the region THAT instance plays
83
+ audio.playing("theme"); // is anything playing?
84
+ audio.playing("theme", id); // is that instance playing?
85
+ audio.state("theme"); // "unloaded" | "loading" | "loaded"
86
+ ```
87
+
88
+ `stop()`, `pause()` and `resume()` take an optional instance `id` too; omit it
89
+ and the whole group is affected. `audio.stop()` with no arguments at all stops
90
+ every sound. `audio.seek(name)` reads the position, `audio.seek(name, s, id?)`
91
+ writes it; `audio.rate` and `audio.fade(name, from, to, ms, id?)` round it out.
92
+
93
+ ## Volume and muting
94
+
95
+ ```js
96
+ audio.setVolume(0.5); // global, 0..1
97
+ audio.getVolume(); // read it back
98
+ audio.mute("cling"); // one clip — mute(name, id?, shouldMute = true)
99
+ audio.unmute("cling");
100
+ audio.muteAll(); // everything (audio.disable() is an alias)
101
+ audio.unmuteAll(); // (audio.enable() is an alias)
102
+ audio.muted(); // global mute state
103
+ ```
104
+
105
+ `audio.unload(name)` / `audio.unloadAll()` free the decoded buffers; `unload`
106
+ also clears the current-track pointer if it named that clip.
107
+
108
+ ## Spatial audio
109
+
110
+ ```js
111
+ audio.position("engine", x, y, z);
112
+ audio.orientation("engine", x, y, z);
113
+ audio.stereo("engine", -1); // -1 left … 1 right
114
+ audio.panner("engine", { coneInnerAngle: 90, /* … */ });
115
+ ```
116
+
117
+ `position(name, x, y = 0, z = -0.5, id?)` and
118
+ `orientation(name, x, y, z, id?)` both default `id` to "the whole group".
119
+ Called with just the clip name, each of the four returns the current value —
120
+ `stereo` gives `0` and `position` gives `[0, 0, 0]` before they have ever been
121
+ set.
122
+
123
+ **Spatial audio is WebAudio-only.** On a clip loaded with `stream: true` (or
124
+ `html5: true`) `stereo` / `position` / `orientation` return early and do
125
+ nothing — which is exactly the case for the long music tracks people reach for
126
+ first.
127
+
128
+ ## Procedural sound
129
+
130
+ For beeps, blips and noise without shipping files. Both take **one options
131
+ object**, and `duration` (in seconds) is required:
132
+
133
+ ```js
134
+ audio.tone({ freq: 440, duration: 0.2, gain: 0.3, pan: -0.5 });
135
+ audio.tone({ freq: [440, 660, 880], duration: 0.4 }); // layered partials
136
+ audio.noise({ duration: 0.5, filter: { type: "lowpass", frequency: 800 } });
137
+ ```
138
+
139
+ `tone` also takes `wave` (default `"sine"`), `attack` (`0.005`) and `pitchSlide`
140
+ (`1`). `noise` takes `type` (`"white"` | `"pink"` | `"brown"`, default
141
+ `"white"`), `attack`, `pan`, `filter` and `filterSweep`. `gain` defaults to `0.1`
142
+ on both.
143
+
144
+ Both are **silent no-ops** when there is no WebAudio context — they bail on
145
+ `getAudioContext() === null` rather than throwing. Neither needs the clip to be
146
+ loaded; neither goes through the file-playback path at all.
147
+
148
+ `audio.getAudioContext()` and `audio.getMasterGain()` are the escape hatches for
149
+ custom Web Audio work; both return `null` when audio is unavailable, so guard.
150
+ Connect a custom graph to the master gain rather than `ctx.destination` if you
151
+ want it to respect `setVolume` / `muteAll`.
152
+
153
+ ## Autoplay: sound is silent until the first gesture
154
+
155
+ Browsers create the audio context in the `suspended` state and keep it there
156
+ until a user interaction. Nothing errors — audio simply does not play, which
157
+ reads as a bug. melonJS installs one-shot `touchstart` / `touchend` / `click` /
158
+ `keydown` unlock listeners on `document`, so the first real gesture anywhere
159
+ unlocks it and fires the clip's `on.unlock` callback. Design for it: start music
160
+ on the first click or key press, not on load. `tone` / `noise` additionally call
161
+ `ctx.resume()` themselves, best-effort, on every invocation.
162
+
163
+ Playback also starts **asynchronously**: `play()` returns an id while the
164
+ instance is still starting, so `playing()` can legitimately be `false` on the
165
+ very next line.
166
+
167
+ ## Load-time options
168
+
169
+ ```js
170
+ { name: "sfx", type: "audio", src: "data/sfx/",
171
+ sprite: { jump: [0, 450] },
172
+ pool: 5, // finished instances kept for reuse — NOT a concurrency cap
173
+ rate: 1.0, // initial playback rate, 0.5..4.0
174
+ mute: false,
175
+ loop: false,
176
+ autoplay: false,
177
+ preload: true, // or "metadata"
178
+ format: "mp3", // explicit hint when the URL has no usable extension
179
+ stream: true, // long music: stream rather than decode into memory
180
+ on: { play() {}, end() {}, stop() {}, unlock() {} },
181
+ }
182
+ ```
183
+
184
+ `pool` (default `5`) is a **reuse cap on finished voices**, not a limit on
185
+ simultaneous playback: when the number of ended voices exceeds it, the surplus
186
+ are disconnected and dropped. Raise it for a clip triggered in rapid bursts, not
187
+ to allow more overlap.
188
+
189
+ Use `stream: true` for long music tracks — it plays through an HTML5 `<audio>`
190
+ element instead of decoding the whole file into memory. (`html5: true` is the
191
+ same switch under its backend name.) `on` accepts the full lifecycle set:
192
+ `play`, `pause`, `stop`, `end`, `fade`, `seek`, `rate`, `volume`, `mute`,
193
+ `unlock`.
194
+
195
+ Re-preloading a manifest that already contains a loaded clip is a no-op —
196
+ `audio.unload(name)` first if you genuinely want to reload it.
197
+
198
+ ## Symptom → cause
199
+
200
+ | symptom | cause |
201
+ |---|---|
202
+ | audio assets 404 | `src` given as a full filename instead of a directory prefix |
203
+ | `target audio extension(s) should be set...` thrown at preload | `audio.init()` not called before preloading |
204
+ | `audio clip X does not exist` | the clip was never preloaded, or the name is mis-cased — every per-clip call throws this |
205
+ | nothing plays until the user clicks | browser autoplay policy — expected, design around it |
206
+ | `playing()` is false right after `play()` | playback starts asynchronously |
207
+ | music keeps playing after leaving a scene | used `play` instead of `playTrack`, or no `stopTrack` in teardown |
208
+ | `stereo` / `position` do nothing | the clip was loaded with `stream: true` — spatial is WebAudio-only |
209
+ | one format works, another does not | that codec is not in the `audio.init()` list |
210
+ | `audio.tone(440, {...})` does nothing / errors | `tone` takes a single options object with a required `duration` |
211
+ | `tone` / `noise` are silent with no error | no WebAudio context — `getAudioContext()` returned `null` |
212
+
213
+ ## Related skills
214
+
215
+ - `melonjs-getting-started` — preloading and asset ordering
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: melonjs-camera-and-drawing
3
+ description: "Use this skill for camera control and immediate-mode drawing in melonJS — following a target, viewport bounds, shake and fade, secondary cameras, and drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, and baking with CanvasRenderTarget. Triggers on: Camera2d, viewport, follow, setBounds, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d."
4
+ license: MIT
5
+ ---
6
+
7
+ # Camera and immediate-mode drawing
8
+
9
+ ## Following a target
10
+
11
+ ```js
12
+ app.viewport.follow(player, app.viewport.AXIS.BOTH, 0.1);
13
+ ```
14
+
15
+ **Pass the renderable, not `renderable.pos`.** `follow()` accepts a `Renderable`,
16
+ a `Vector2d` or a `Vector3d` and **throws** `invalid target for me.Camera2d.follow`
17
+ for anything else. A renderable's `pos` is an `ObservableVector3d`, which is not
18
+ a `Vector3d` subclass, so `follow(player.pos, …)` throws.
19
+
20
+ The third argument is a damping factor in `[0 .. 1]` — lower is smoother and
21
+ laggier, `1` snaps. Omit it for a hard lock (it defaults to `1`). It is
22
+ frame-rate independent since 19.7. `AXIS.HORIZONTAL` / `AXIS.VERTICAL` /
23
+ `AXIS.BOTH` / `AXIS.NONE`.
24
+
25
+ Call it from the followed entity's `onActivateEvent`, using `this.parentApp`.
26
+
27
+ ## Bounds are the thing people forget
28
+
29
+ The camera's bounds default to the **renderer size**, so it cannot scroll until
30
+ you widen them to the world:
31
+
32
+ ```js
33
+ app.viewport.setBounds(0, 0, mapWidth, mapHeight);
34
+ ```
35
+
36
+ Symptom: `follow()` is called, looks right, and the view never moves. Note a
37
+ canvas resize resets the default camera's bounds to `(0, 0, w, h)`, so re-apply
38
+ them from a `CANVAS_ONRESIZE` handler if your game resizes. Loading a Tiled map
39
+ sets them for you from the map size.
40
+
41
+ ## Camera effects
42
+
43
+ ```js
44
+ app.viewport.shake(4, 200); // intensity, ms
45
+ app.viewport.fadeIn("#000000", 500); // fade TO black
46
+ app.viewport.fadeOut("#000000", 500); // start black, fade back to normal
47
+ app.viewport.colorMatrix.contrast(1.1).saturate(1.1);
48
+ app.viewport.addPostEffect(new VignetteEffect(app.renderer));
49
+ ```
50
+
51
+ The fade pair is named from the *colour's* point of view, not the scene's:
52
+ `fadeIn` fades the colour in (screen goes to `color`), `fadeOut` fades it out
53
+ (screen starts at `color` and clears). Both default to a 1000 ms duration and
54
+ take an optional completion callback.
55
+
56
+ `colorMatrix` (`brightness` / `contrast` / `saturate` / `hueRotate` / `sepia` /
57
+ `invertColors`, all chainable) is the cheap way to grade a whole camera: the
58
+ camera appends it as a final post-effect pass by itself whenever the matrix is
59
+ non-identity, and drops it again after the frame. It is **not** shader-free —
60
+ it is a `ColorMatrixEffect`, i.e. a `ShaderEffect`, so like every post effect it
61
+ is inert on the Canvas renderer.
62
+
63
+ ## Coordinate conversion
64
+
65
+ ```js
66
+ app.viewport.worldToLocal(x, y); // world → screen
67
+ app.viewport.localToWorld(x, y); // screen → world
68
+ ```
69
+
70
+ Both return a `Vector2d`; with no third argument that vector comes from the
71
+ shared pool, so in a per-frame path pass your own `v` as the third argument, or
72
+ release it with `getPool("vector2d").release(v)`.
73
+
74
+ Pointer events already carry every frame of reference: `gameWorldX`/`gameWorldY`
75
+ (world), `gameScreenX`/`gameScreenY` (screen), `gameLocalX`/`gameLocalY`
76
+ (relative to the registered region's container) and `gameX`/`gameY`, which is
77
+ whichever of world/screen matches the region — screen for a `floating` region,
78
+ world otherwise. Use the screen pair for anything that *moves* the camera, or
79
+ the drag feeds back on itself.
80
+
81
+ ## Secondary cameras
82
+
83
+ Cameras are first-class — a minimap is a second camera with `autoResize = false`
84
+ and an overridden `postDraw`:
85
+
86
+ ```js
87
+ this.cameras.set("minimap", new MinimapCamera());
88
+ ```
89
+
90
+ ## Immediate-mode drawing
91
+
92
+ Inside a custom `draw(renderer)` you can draw shapes directly. Remember the two
93
+ renderable rules: read `this.pos`, and zero the anchor.
94
+
95
+ ```js
96
+ draw(renderer) {
97
+ renderer.setColor("#ff8800");
98
+ renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height);
99
+
100
+ renderer.setColor("#ffffff");
101
+ renderer.lineWidth = 2; // property, not setLineWidth()
102
+ renderer.stroke(new Line(0, 0, [/* … */]));
103
+ }
104
+ ```
105
+
106
+ Geometry available: `Rect`, `RoundRect`, `Ellipse`, `Polygon`, `Line`, `Point`
107
+ (and a `Bounds`). `renderer.fill(shape)` and `renderer.stroke(shape)` take any
108
+ of them and throw `Invalid geometry for fill/stroke` for anything else.
109
+
110
+ `Gradient` gives linear and radial fills, built with
111
+ `renderer.createLinearGradient(x0, y0, x1, y1)` /
112
+ `createRadialGradient(x0, y0, r0, x1, y1, r1)` and then handed to
113
+ `renderer.setColor(gradient)`. `NoiseTexture2d` generates cellular and simplex
114
+ noise — including seamless tiling and `asNormalMap` — so you rarely need to ship
115
+ noise PNGs.
116
+
117
+ ## Clipping and masking
118
+
119
+ ```js
120
+ renderer.clipRect(x, y, w, h); // rectangular clip
121
+ renderable.mask = new Ellipse(/* … */); // shape mask on an object
122
+ ```
123
+
124
+ Note that a `Container` with `clipping` enabled clips **after** its own
125
+ `translate(pos)`, in container-local coordinates — `clipRect(0, 0, width,
126
+ height)`. It also silently does nothing unless the container is non-root and has
127
+ a finite `width`/`height`: a `Container` built with no explicit size is
128
+ `Infinity × Infinity`, and `clipping = true` on it is a no-op.
129
+
130
+ ## Bake once, draw many
131
+
132
+ When you would otherwise issue hundreds of identical draws every frame, render
133
+ them once into a `CanvasRenderTarget` and draw the result:
134
+
135
+ ```js
136
+ const rt = new CanvasRenderTarget(width, height, { context: "2d" });
137
+ const ctx = rt.context; // a CanvasRenderingContext2D — draw into it once…
138
+ // then draw the backing canvas, not the target object:
139
+ renderer.drawImage(rt.canvas, x, y);
140
+ // or wrap it: new Sprite(0, 0, { image: rt.canvas })
141
+ ```
142
+
143
+ Pass `rt.canvas`, never `rt` — a `CanvasRenderTarget` is a wrapper, not a
144
+ drawable, and the engine's own bakes (the particle emitter's default texture)
145
+ hand `.canvas` on for exactly that reason.
146
+
147
+ This is the sanctioned performance idiom before reaching for a custom shader.
148
+
149
+ `renderer.toFrameTexture()` is the related tool for capturing the *current frame*
150
+ into a texture for a shader to sample.
151
+
152
+ ## Symptom → cause
153
+
154
+ | symptom | cause |
155
+ |---|---|
156
+ | `follow()` set but the view never moves | viewport bounds still at renderer size |
157
+ | `invalid target for me.Camera2d.follow` | passed `player.pos` — pass the renderable, a `Vector2d` or a `Vector3d` |
158
+ | camera drag jumps around | using world (`gameWorldX`) instead of screen (`gameScreenX`) coordinates |
159
+ | `colorMatrix` does nothing | it is a shader effect — inert on the Canvas renderer |
160
+ | custom drawing appears at the origin | `draw()` not reading `this.pos` |
161
+ | custom drawing offset by half its size | centred `anchorPoint` not zeroed |
162
+ | `setLineWidth` is not a function | it is the `lineWidth` property |
163
+ | clipping in the wrong place inside a container | container clips in local coordinates, after its own translate |
164
+ | `container.clipping = true` does nothing | container has no explicit size, so `width`/`height` are `Infinity` |
165
+ | baked target draws nothing / throws | passed the `CanvasRenderTarget`, not its `.canvas` |
166
+ | frame rate drops with many static draws | bake into a `CanvasRenderTarget` instead |
167
+
168
+ ## Related skills
169
+
170
+ - `melonjs-renderables` — the `draw()` contract and anchors
171
+ - `melonjs-3d` — `Camera3d`, which extends `Camera2d` and keeps all of the above
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: melonjs-deployment
3
+ description: "Use this skill when packaging, shipping or scaffolding a melonJS game — starting a project with create-melonjs, building for the web, wrapping as a native app with Capacitor or Cordova, publishing to WeChat Mini Games or Facebook Instant Games, browser support and transpiling. Triggers on: create-melonjs, scaffold, new project, build, deploy, publish, Capacitor, Cordova, native app, iOS, Android, WeChat, Instant Games, browser support, transpile, ES2022, bundle size, tree-shaking."
4
+ license: MIT
5
+ ---
6
+
7
+ # Starting and shipping a project
8
+
9
+ ## Scaffolding
10
+
11
+ Start from the template rather than assembling a build by hand:
12
+
13
+ ```bash
14
+ npm create melonjs@latest my-game
15
+ ```
16
+
17
+ The project name is required — without it the command prints its usage and
18
+ exits. It downloads the melonJS TypeScript boilerplate (Vite, TypeScript and the
19
+ debug plugin already wired in), which saves you the top-level-await and
20
+ asset-path issues that a hand-rolled config usually hits first.
21
+
22
+ ## Adding melonJS to an existing project
23
+
24
+ ```bash
25
+ npm install melonjs
26
+ ```
27
+
28
+ ```js
29
+ import { Application, Sprite, loader } from "melonjs";
30
+ ```
31
+
32
+ The package is a tree-shakeable ES module with TypeScript declarations included
33
+ and **no runtime dependencies**.
34
+
35
+ A CDN build exists for quick experiments, but not for production:
36
+
37
+ ```html
38
+ <script type="module">
39
+ import { Application } from "https://cdn.jsdelivr.net/npm/melonjs@20/+esm";
40
+ </script>
41
+ ```
42
+
43
+ ## Browser support and transpiling
44
+
45
+ The published bundle targets **ES2022** and uses private class members. A browser
46
+ that predates that **cannot parse the file at all** — it fails at load, not when
47
+ a feature is used, so the symptom is a blank page with a syntax error.
48
+
49
+ To support older browsers, transpile melonJS **together with your own code**.
50
+ The usual trap is that build setups skip `node_modules` by default, leaving
51
+ melonJS untouched however low you set your target:
52
+
53
+ ```js
54
+ // vite.config.js — esbuild downlevels dependencies too
55
+ export default { build: { target: "es2020" } };
56
+ ```
57
+
58
+ ```js
59
+ // webpack: the default `exclude: /node_modules/` would skip melonjs
60
+ { test: /\.js$/, exclude: /node_modules\/(?!melonjs)/, use: "babel-loader" }
61
+ ```
62
+
63
+ Two caveats worth knowing:
64
+
65
+ - **Transpiling handles syntax only.** Built-in methods need separate polyfills;
66
+ a bundler's `target` setting will never add them.
67
+ - Downlevelled private class members become `WeakMap` lookups, and melonJS uses
68
+ them in the renderer's hot paths. This is a real trade, not a free switch.
69
+
70
+ ## Native apps
71
+
72
+ **Capacitor** is the current recommendation for iOS and Android — see
73
+ `@melonjs/capacitor-plugin`. **Cordova** also works and is documented, but is the
74
+ older route.
75
+
76
+ The thing that breaks first in both is asset loading: the app is served from
77
+ `file://`, where `fetch()` cannot read local files. melonJS's loader falls back
78
+ to XHR for that scheme, so engine-loaded assets work — but any `fetch()` in
79
+ *your* game code will fail there.
80
+
81
+ ## Mini-game platforms
82
+
83
+ **WeChat Mini Games** and **Facebook Instant Games** are both documented in the
84
+ wiki. Note that `device.platform.isWeixin` detects WeChat's **in-app browser**,
85
+ which is a normal WebView. WeChat *Mini Games* are a different runtime with no
86
+ DOM, and need platform-specific adaptation rather than just detection — the one
87
+ hook the engine offers is that the renderer adopts a `globalThis.canvas` when
88
+ the runtime's adapter provides one, instead of creating its own.
89
+
90
+ ## Size
91
+
92
+ The whole engine is roughly **250 KB minzipped**. Tree-shaking helps less than
93
+ you might expect — the engine's subsystems reach each other, so a game importing
94
+ a handful of symbols still pulls most of it. Budget for the full figure.
95
+
96
+ Practical wins are in assets, not the engine: pack sprites into atlases, prefer
97
+ one tileset over several, and use `stream: true` for long music tracks so they
98
+ are not decoded into memory.
99
+
100
+ ## Symptom → cause
101
+
102
+ | symptom | cause |
103
+ |---|---|
104
+ | blank page, syntax error in the console | browser older than the ES2022 target |
105
+ | transpiling changes nothing | build config excludes `node_modules` |
106
+ | assets 404 in a packaged native app | `fetch()` in game code — `file://` needs XHR |
107
+ | a built-in method is missing after transpiling | transpiling is syntax-only; polyfills are separate |
108
+ | bundle much larger than expected | tree-shaking recovers little — subsystems are interconnected |
109
+
110
+ ## Related skills
111
+
112
+ - `melonjs` — the companion packages, including the Capacitor plugin
113
+ - `melonjs-getting-started` — the Application bootstrap the template produces
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: melonjs-effects-and-shaders
3
+ description: "Use this skill for post-processing effects, custom shaders, blend modes and colour grading in melonJS. Covers the built-in ShaderEffect presets, addPostEffect on renderables and cameras, writing a custom dual-language GLSL/WGSL effect, the screen_texture builtins, and why effects silently do nothing on the Canvas fallback. Triggers on: ShaderEffect, addPostEffect, removePostEffect, getPostEffect, VignetteEffect, GlowEffect, BlurEffect, PixelateEffect, ScanlineEffect, shader, GLSL, WGSL, uniform, setUniform, setTexture, setTime, blendMode, colorMatrix, screen_texture, toFrameTexture, post effect, filter."
4
+ license: MIT
5
+ ---
6
+
7
+ # Post effects and custom shaders
8
+
9
+ > melonJS ships a catalogue of post effects and a dual-language shader system.
10
+ > The recurring trap is that every shader path needs a GPU backend and
11
+ > **degrades to nothing** on Canvas rather than erroring. Blend modes are the
12
+ > exception — those work on all three renderers.
13
+
14
+ ## Applying an effect
15
+
16
+ Effects attach to a renderable **or** to a camera (for full-screen grading):
17
+
18
+ ```js
19
+ import { VignetteEffect, GlowEffect } from "melonjs";
20
+
21
+ sprite.addPostEffect(new GlowEffect(app.renderer));
22
+ app.viewport.addPostEffect(new VignetteEffect(app.renderer));
23
+ ```
24
+
25
+ `renderable.shader = …` is **deprecated since 19.2.0**, and its setter destroys
26
+ whatever it replaces. Use `addPostEffect` / `getPostEffect` / `removePostEffect`.
27
+
28
+ ## Toggle with `enabled`, do not remove
29
+
30
+ **`removePostEffect()` destroys the effect** — it calls `effect.destroy()` and
31
+ frees GPU resources, so the instance cannot be re-added. Same for
32
+ `clearPostEffects()` and for reassigning the deprecated `.shader`.
33
+
34
+ ```js
35
+ // ✗ destroys it; re-adding later fails or forces a recompile
36
+ sprite.removePostEffect(effect);
37
+
38
+ // ✓
39
+ effect.enabled = false;
40
+ ```
41
+
42
+ An effect with `effect.shared === true` opts out of auto-destroy because it is
43
+ reused across renderables.
44
+
45
+ ## Built-in effects
46
+
47
+ Eighteen presets, every one constructed as `new XEffect(renderer, options)` and
48
+ every one shipping both a GLSL and a WGSL body, so they all run on either GPU
49
+ backend: `VignetteEffect`, `ScanlineEffect` (optional CRT curvature),
50
+ `GlowEffect`, `BlurEffect`, `PixelateEffect`, `ChromaticAberrationEffect`,
51
+ `DropShadowEffect`, `FlashEffect`, `OutlineEffect`, `ColorMatrixEffect`,
52
+ `DesaturateEffect`, `DissolveEffect`, `TintPulseEffect`, `WaveEffect`,
53
+ `InvertEffect`, `SepiaEffect`, `HologramEffect`, `ShineEffect`.
54
+
55
+ For grading without writing a shader yourself, the camera has a colour matrix:
56
+
57
+ ```js
58
+ app.viewport.colorMatrix.contrast(1.1).saturate(1.1);
59
+ ```
60
+
61
+ It is still *implemented* as one: a non-identity matrix makes the camera append
62
+ an internal `ColorMatrixEffect` to its post-effect chain for that frame, so it
63
+ needs a GPU backend like everything else here.
64
+
65
+ ## Custom effects are dual-language
66
+
67
+ A custom `ShaderEffect` supplies a fragment **body**, not a whole program. To run
68
+ on both GPU backends it needs GLSL *and* WGSL — a GLSL-only effect silently does
69
+ nothing when `video.AUTO` lands on WebGPU.
70
+
71
+ ```js
72
+ const effect = new ShaderEffect(app.renderer, { glsl, wgsl });
73
+ effect.setUniform("uStrength", 0.5);
74
+ effect.setTexture("uScene", tex);
75
+ effect.setTime(timer.getTime() / 1000); // setTime takes SECONDS; getTime is ms
76
+ ```
77
+
78
+ Uniform names are shared across the two bodies — in WGSL they are the members of
79
+ one `@group(3) @binding(0) var<uniform>` struct — so a single `setUniform` call
80
+ feeds whichever backend is live. `setTime` is a convenience for a `uTime`
81
+ uniform and silently does nothing if the shader does not declare one.
82
+
83
+ Shaders can also be preloaded as assets with loader type `"shader"`.
84
+
85
+ ### Screen-reading builtins
86
+
87
+ For refraction, distortion and anything that samples what is already drawn:
88
+
89
+ - **`screen_texture`** — a sampler kept filled with everything drawn so far
90
+ (GLSL: annotate the sampler `uniform sampler2D tex : screen_texture;`)
91
+ - **`screen_uv`** — this fragment's 0..1 position in that capture
92
+ - **`noise_uv`** — 0..1 across the sprite regardless of its atlas frame
93
+
94
+ In WGSL the capture is sampled through `screen_sampler` (clamped) or
95
+ `screen_sampler_repeat` (wrapping) instead of GLSL's `: screen_texture`
96
+ annotation. `screen_uv` is y-**up** in GLSL and y-**down** in WGSL, which exactly
97
+ matches each backend's own capture orientation — so a body that samples
98
+ `screen_texture` at `screen_uv` needs no flip in *either* language.
99
+
100
+ Two places where a straight port really does differ:
101
+
102
+ - **A capture you bind yourself.** `renderer.toFrameTexture()` grabs the frame
103
+ into a `Texture2d` you hand to `setTexture` — the supported replacement for
104
+ `readPixels` tricks. The GL capture is bottom-up (GLSL bodies sample it with
105
+ `1.0 - uv.y`) and opaque RGB; the WebGPU capture is top-down (the WGSL twin
106
+ must *not* flip) and preserves alpha.
107
+ - **Vertical UV offsets** — a drop shadow, a directional smear. The WebGL pooled
108
+ multi-effect path composites through bottom-up FBOs, so declare a `uUVYDir`
109
+ uniform (initialise it to `1.0`) and multiply vertical offsets by it; the
110
+ renderer feeds `-1` on that path and `+1` everywhere else.
111
+
112
+ ## Blend modes
113
+
114
+ Fourteen modes; thirteen of them are honoured by all three renderers:
115
+
116
+ ```js
117
+ sprite.blendMode = "additive"; // or "multiply", "screen", "overlay", …
118
+ ```
119
+
120
+ Six ride fixed-function blend state and cost nothing extra: `normal`,
121
+ `additive` (spelled `add` or `lighter` too), `multiply`, `screen`, `exclusion`,
122
+ and `none` (blending off — the source replaces the destination, alpha included).
123
+ `none` is the one mode the Canvas backend does not implement; it reports
124
+ `"normal"` instead.
125
+
126
+ The other eight cannot be expressed as a multiply-add on the destination, so the
127
+ GPU backends capture the destination and composite through `BlendEffect` — one
128
+ capture plus one composite **per draw**: `difference`, `overlay`, `hard-light`,
129
+ `color-dodge`, `color-burn`, `soft-light`, `darken`, `lighten`. Right for
130
+ accents; expensive for hundreds of objects. The Canvas renderer reaches all
131
+ eight through `globalCompositeOperation` instead, at no extra cost.
132
+
133
+ Anything else collapses to `"normal"`, and so do 3D meshes and `Gradient` fills
134
+ whatever you ask for — the latter with a one-time console warning.
135
+ `renderer.setBlendMode(mode)` returns the mode it actually applied, so comparing
136
+ that against your request detects the fallback.
137
+
138
+ ## The Canvas fallback
139
+
140
+ `ShaderEffect` on a Canvas renderer logs a warning, leaves `enabled = false`, and
141
+ turns every method into a no-op. Nothing throws. The same happens on a GPU
142
+ backend when no body matches its language — a GLSL-only effect under WebGPU.
143
+
144
+ That is fine for decoration. It is **not** fine when the shader draws your
145
+ content — the carrier renderable then shows through raw. A 1×1 white texture
146
+ stretched to 280×156 renders as a white box where the effect should be.
147
+
148
+ Guard when the effect is load-bearing:
149
+
150
+ ```js
151
+ if (app.renderer.shaderLanguage === null) {
152
+ // no programmable pipeline — skip the carrier draw entirely
153
+ }
154
+ ```
155
+
156
+ `shaderLanguage` (`"glsl"`, `"wgsl"`, or `null`) is the flag to test here rather
157
+ than a backend name or `supportsDepthBuffer`: it answers "can this renderer
158
+ compile what I am about to hand it". Per-effect, `effect.enabled` answers the
159
+ same question after construction.
160
+
161
+ ## Symptom → cause
162
+
163
+ | symptom | cause |
164
+ |---|---|
165
+ | effect does nothing, warning in console | Canvas fallback — no programmable pipeline |
166
+ | effect does nothing on some machines only | GLSL-only shader, `video.AUTO` chose WebGPU |
167
+ | a white or solid box where the effect should be | carrier renderable drawn while the effect is disabled |
168
+ | effect cannot be re-enabled | `removePostEffect()` destroyed it — use `enabled` |
169
+ | ported shader renders upside down | a hand-bound `toFrameTexture()` capture — GL is bottom-up, WebGPU top-down |
170
+ | shadow/smear offset flips on some draws | vertical UV offset not multiplied by `uUVYDir` |
171
+ | frame rate collapses with many blended sprites | an *advanced* mode (overlay, darken, …) — each draw is a capture plus a composite |
172
+ | animated shader never moves | `setTime` fed milliseconds, or the shader declares no `uTime` |
173
+
174
+ ## Related skills
175
+
176
+ - `melonjs-renderables` — where post effects attach, and the destroy trap
177
+ - `melonjs-3d` — custom mesh shaders and the GPU-backend requirement