melonjs 20.13.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.
- package/README.md +65 -22
- package/build/application/application.d.ts +6 -1
- package/build/application/application.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/resize.d.ts.map +1 -1
- package/build/application/settings.d.ts +13 -8
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +8 -4
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/backend/engine.d.ts.map +1 -1
- package/build/audio/playback.d.ts +2 -2
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/spatial.d.ts.map +1 -1
- package/build/audio/types.d.ts +15 -5
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts +10 -9
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +11 -8
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/geometries/path2d.d.ts +9 -8
- package/build/geometries/path2d.d.ts.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +40993 -39738
- package/build/index.js.map +4 -4
- package/build/input/keyboard.d.ts.map +1 -1
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/level.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +2 -1
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +13 -3
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +1 -0
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +7 -3
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/lighting/light3d.d.ts +55 -0
- package/build/lighting/light3d.d.ts.map +1 -1
- package/build/loader/cache.d.ts +146 -97
- package/build/loader/cache.d.ts.map +1 -1
- package/build/loader/internal-types.d.ts +69 -0
- package/build/loader/internal-types.d.ts.map +1 -0
- package/build/loader/loader.d.ts +584 -292
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/parsers/audio.d.ts.map +1 -1
- package/build/loader/parsers/fontface.d.ts.map +1 -1
- package/build/loader/parsers/gltf.d.ts.map +1 -1
- package/build/loader/parsers/tmx.d.ts.map +1 -1
- package/build/math/color.d.ts +4 -0
- package/build/math/color.d.ts.map +1 -1
- package/build/math/noise.d.ts +16 -16
- package/build/math/noise.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +64 -25
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +9 -0
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/builtin-adapter.d.ts +25 -3
- package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts.map +1 -1
- package/build/physics/builtin/raycast.d.ts.map +1 -1
- package/build/physics/world.d.ts +40 -10
- package/build/physics/world.d.ts.map +1 -1
- package/build/renderable/container.d.ts +1 -0
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/draggable.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +16 -14
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +62 -5
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/trigger.d.ts +5 -0
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/uibaseelement.d.ts +8 -1
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uicheckbox.d.ts.map +1 -1
- package/build/system/device.d.ts +11 -2
- package/build/system/device.d.ts.map +1 -1
- package/build/system/fullscreen.d.ts.map +1 -1
- package/build/system/pool.d.ts.map +1 -1
- package/build/utils/utils.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +16 -14
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/canvas_factory.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +127 -0
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs/SKILL.md +38 -14
- package/skills/melonjs-20-migration/SKILL.md +50 -7
- package/skills/melonjs-3d/SKILL.md +138 -38
- package/skills/melonjs-3d-assets/SKILL.md +83 -33
- package/skills/melonjs-audio/SKILL.md +55 -14
- package/skills/melonjs-camera-and-drawing/SKILL.md +95 -8
- package/skills/melonjs-deployment/SKILL.md +92 -14
- package/skills/melonjs-display-and-scaling/SKILL.md +8 -3
- package/skills/melonjs-effects-and-shaders/SKILL.md +46 -15
- package/skills/melonjs-events/SKILL.md +3 -2
- package/skills/melonjs-getting-started/SKILL.md +5 -0
- package/skills/melonjs-input/SKILL.md +65 -15
- package/skills/melonjs-lighting/SKILL.md +13 -1
- package/skills/melonjs-loading-assets/SKILL.md +202 -27
- package/skills/melonjs-math/SKILL.md +12 -5
- package/skills/melonjs-particles-and-trails/SKILL.md +7 -3
- package/skills/melonjs-performance/SKILL.md +18 -8
- package/skills/melonjs-physics/SKILL.md +385 -127
- package/skills/melonjs-plugins/SKILL.md +45 -11
- package/skills/melonjs-renderables/SKILL.md +24 -7
- package/skills/melonjs-renderer-backends/SKILL.md +38 -17
- package/skills/melonjs-scenes-and-state/SKILL.md +30 -5
- package/skills/melonjs-sprites-and-animation/SKILL.md +23 -2
- package/skills/melonjs-tilemaps/SKILL.md +56 -18
- package/skills/melonjs-ui-and-text/SKILL.md +120 -25
|
@@ -19,10 +19,13 @@ await loader.preload([
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
1. **`audio.init(format)` must run before any audio asset is preloaded**, and
|
|
22
|
-
lists the formats you actually ship. Preloading first
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
lists the formats you actually ship. Preloading first fails the preload:
|
|
23
|
+
`await loader.preload(...)` rejects with the loader's `Failed loading
|
|
24
|
+
resource <src>`, which hides the real reason, `target audio extension(s)
|
|
25
|
+
should be set through me.audio.init() before calling the preloader.`
|
|
26
|
+
(thrown as it is by `audio.load()` called directly). It returns `false`
|
|
27
|
+
when no audio backend is available, so it is worth checking. The format
|
|
28
|
+
list defaults to `"mp3"`.
|
|
26
29
|
2. **`src` is a prefix, not a file.** The loader appends the asset `name` plus
|
|
27
30
|
each configured extension and uses the first that loads — so
|
|
28
31
|
`data/sfx/` + `cling` + `mp3` → `data/sfx/cling.mp3`. Passing a full filename
|
|
@@ -50,13 +53,19 @@ audio.playTrack("theme"); // background music
|
|
|
50
53
|
audio.stopTrack();
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
Use **`playTrack`** for music, not `play`. `playTrack(name, volume?)`
|
|
56
|
+
Use **`playTrack`** for music, not `play`. `playTrack(name, volume?)` (or
|
|
57
|
+
`playTrack(name, { volume, sprite })`, to loop one region of the clip) always
|
|
54
58
|
loops, and it records the name as the current track — which is what
|
|
55
59
|
`app.pauseOnBlur` / `resumeOnFocus` / `stopOnBlur` act on, via
|
|
56
60
|
`state.pause(true)` → `pauseTrack()`. `stopTrack()`, `pauseTrack()`,
|
|
57
61
|
`resumeTrack()` and `getCurrentTrack()` all read that same single slot, so there
|
|
58
62
|
is only ever one current *track name*.
|
|
59
63
|
|
|
64
|
+
`playTrack` on a track that is paused restarts it. `pauseTrack` and
|
|
65
|
+
`resumeTrack` act on the track alone, so effects played from the same file (an
|
|
66
|
+
audio sprite holding both) keep going, and `resumeTrack` on a track that is
|
|
67
|
+
playing does nothing.
|
|
68
|
+
|
|
60
69
|
**`playTrack` stops the track it replaces** (20.9.0 and later; before that it
|
|
61
70
|
only assigned the slot, and the previous bed kept looping underneath where
|
|
62
71
|
`stopTrack()` could no longer reach it). So a scene just states its own music
|
|
@@ -155,6 +164,11 @@ audio.unmuteAll(); // (audio.enable() is an alias)
|
|
|
155
164
|
audio.muted(); // global mute state
|
|
156
165
|
```
|
|
157
166
|
|
|
167
|
+
`setVolume` is a master level over everything; the `volume` given to a `play`
|
|
168
|
+
or `playTrack` is that sound's own level, 1 when left out, and what is heard is
|
|
169
|
+
the product of the two. So a music slider fades the track's own level
|
|
170
|
+
(`audio.fade(name, v, v, 1)`) and a master slider calls `setVolume`.
|
|
171
|
+
|
|
158
172
|
`audio.unload(name)` / `audio.unloadAll()` free the decoded buffers; `unload`
|
|
159
173
|
also clears the current-track pointer if it named that clip.
|
|
160
174
|
|
|
@@ -163,8 +177,10 @@ also clears the current-track pointer if it named that clip.
|
|
|
163
177
|
When the window loses focus the engine mutes all audio, and unmutes it on
|
|
164
178
|
focus. That covers sound effects, procedural `tone` / `noise` and the mixer
|
|
165
179
|
effects, not just the music track that `state.pause()` pauses. Set
|
|
166
|
-
`pauseOnBlur
|
|
167
|
-
in the background.
|
|
180
|
+
`app.pauseOnBlur = false` (with `stopOnBlur` left at its `false` default) if a
|
|
181
|
+
clip has to keep playing in the background. It is a property of the
|
|
182
|
+
application, not a constructor setting: `new Application(w, h, { pauseOnBlur:
|
|
183
|
+
false })` does nothing.
|
|
168
184
|
|
|
169
185
|
## Spatial audio
|
|
170
186
|
|
|
@@ -197,10 +213,15 @@ audio.play("portal", { at: { x: 1200, y: 480 } });
|
|
|
197
213
|
|
|
198
214
|
`follow` and `at` are mutually exclusive: passing both throws, rather than
|
|
199
215
|
quietly picking one. `follow` re-reads the target's **absolute** position each
|
|
200
|
-
frame, so a renderable nested in a moving container is placed correctly.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
216
|
+
frame, so a renderable nested in a moving container is placed correctly. That
|
|
217
|
+
position is where its `anchorPoint` sits: the center of a `Renderable` or a
|
|
218
|
+
`Sprite`, but the **top-left corner** of an `Entity` or an object loaded from
|
|
219
|
+
Tiled, whose anchor is `(0, 0)`, so a large one (a boss) sounds from up and to
|
|
220
|
+
the left of where it looks to be. `stopWithTarget` is what you want for a sound
|
|
221
|
+
that belongs to an entity: without it a looping clip on a destroyed renderable
|
|
222
|
+
keeps playing from wherever it last was. It also counts a pooled renderable
|
|
223
|
+
going back to its pool, which can lend it out again before the next frame.
|
|
224
|
+
`audio.unfollow(id)` detaches a sound and leaves it playing where it is.
|
|
204
225
|
|
|
205
226
|
### Move the listener, don't do the subtraction yourself
|
|
206
227
|
|
|
@@ -221,7 +242,12 @@ player's position.
|
|
|
221
242
|
`setListener` places the ear at the target's absolute position after the world
|
|
222
243
|
updates. A plain renderable contributes position only, so a side-scroller keeps
|
|
223
244
|
the default "facing into the screen" and gets left-right panning plus distance
|
|
224
|
-
falloff from the one call.
|
|
245
|
+
falloff from the one call. A `Camera2d` (`audio.setListener(app.viewport)` in a
|
|
246
|
+
2D game) is heard from the **center of its view**, its `worldView`, zoom and
|
|
247
|
+
offset included, not from its `pos`, the view's top-left corner (before 20.12
|
|
248
|
+
it was the corner, and everything panned right). Following stops on its own
|
|
249
|
+
when the target is destroyed, which a stage that owns its camera does on every
|
|
250
|
+
state change.
|
|
225
251
|
|
|
226
252
|
A `Camera3d` target also contributes its **orientation**, taken from its own
|
|
227
253
|
basis, so turning the camera swings the stereo image with it. That is the whole
|
|
@@ -380,6 +406,14 @@ a gate screen — a splash, a "press any key", or a health/photosensitivity noti
|
|
|
380
406
|
unconditionally. Note the unlock listens for `keydown` too, so a keyboard-only
|
|
381
407
|
player is covered.
|
|
382
408
|
|
|
409
|
+
Where there is no DOM (nx.js on the Nintendo Switch, see `melonjs-deployment`)
|
|
410
|
+
there is no page to gesture at and no autoplay policy: audio unlocks as soon as
|
|
411
|
+
the engine has its context, and music can start on load. nx.js's Web Audio has
|
|
412
|
+
buffer sources, gain and stereo panning, but not the `PannerNode`,
|
|
413
|
+
`OscillatorNode` and `BiquadFilterNode` behind 3D `position()` / `panner()` and
|
|
414
|
+
`tone()` / `noise()`, nor the reverb, echo and compressor nodes: those throw
|
|
415
|
+
"Method not implemented" there.
|
|
416
|
+
|
|
383
417
|
### The context can stop again, long after it was unlocked
|
|
384
418
|
|
|
385
419
|
Unlocking is only the first half. A context that has been running can be
|
|
@@ -490,8 +524,9 @@ either way — blank screen, empty console.
|
|
|
490
524
|
| symptom | cause |
|
|
491
525
|
|---|---|
|
|
492
526
|
| audio assets 404 | `src` given as a full filename instead of a directory prefix |
|
|
493
|
-
| `target audio extension(s) should be set...`
|
|
527
|
+
| `preload()` rejects with `Failed loading resource data/sfx/` (or `audio.load()` throws `target audio extension(s) should be set...`) | `audio.init()` not called before preloading |
|
|
494
528
|
| `audio clip X does not exist` | the clip was never preloaded, or the name is mis-cased — every per-clip call throws this |
|
|
529
|
+
| "Method not implemented" from `position()`, `panner()`, `tone()`, `noise()` or an effect, on nx.js | nx.js's Web Audio lacks the node it needs (panner, oscillator, filter, convolver...): use `stereo()` and recorded sounds there |
|
|
495
530
|
| nothing plays until the user clicks | browser autoplay policy — expected, design around it |
|
|
496
531
|
| `playing()` is false right after `play()` | playback starts asynchronously |
|
|
497
532
|
| music keeps playing after leaving a scene | used `play` instead of `playTrack`, or no `stopTrack` in teardown |
|
|
@@ -507,11 +542,17 @@ either way — blank screen, empty console.
|
|
|
507
542
|
| a positioned sound is silent, or only audible right next to the player | placed by hand with `audio.position()`, which keeps WebAudio's metre defaults (`refDistance: 1`) — use `follow` / `at`, or set `refDistance` in pixels with `audio.panner()` |
|
|
508
543
|
| a positioned sound does not follow the player | nothing is placing it — `audio.play(name, { follow: entity })`, and `audio.setListener(player)` once |
|
|
509
544
|
| a followed sound pans to the wrong side as the player moves | the listener was moved **and** positions are still relative to the player — with a listener in the world, pass absolute world coordinates |
|
|
510
|
-
| a
|
|
545
|
+
| with `audio.setListener(app.viewport)`, a sound in the middle of the screen is heard off to the right (before 20.12) | the ear was at the camera's top-left corner: update, it is at the center of the view |
|
|
546
|
+
| a followed `Entity` (or Tiled object) sounds offset up and to the left | `follow` uses the anchor point, which is the top-left corner under their `(0, 0)` anchor |
|
|
547
|
+
| a looping sound outlives the entity that owns it | `follow` without `stopWithTarget: true`; with it, before 20.14 a pooled entity going back to its pool did not count either |
|
|
548
|
+
| new sounds are quieter than the volume slider says, or the music stays silent after the volume went to 0 and back (before 20.14) | the global volume was applied twice: update |
|
|
549
|
+
| "Retry" from a pause menu restarts the level with no music (before 20.14) | `playTrack` on a paused track stopped it: update |
|
|
550
|
+
| a clip's load-time `on: { end }` stops firing after `audio.stop(name)` (before 20.14) | `stop(name)` removed it: update |
|
|
511
551
|
| a voice panned with `stereo()` loses its 3D placement, or the reverse | one panner node per voice and last writer wins — pick one per voice and stay with it |
|
|
512
552
|
| a reverb / compressor on the master gain does not affect the music | the music is `stream: true`, which bypasses the master gain — preload it buffered |
|
|
513
553
|
| a looping MP3 drops out for a moment at every loop, in Safari only | Safari keeps the encoder's padding at the end of the file: loop a sprite region of the clip's exact length (see "A loop with no gap") |
|
|
514
554
|
| sound effects keep playing after the window loses focus | `pauseOnBlur` and `stopOnBlur` are both off, so the engine is deliberately leaving the game running in the background |
|
|
555
|
+
| the game still pauses and goes quiet on blur although `pauseOnBlur: false` was passed to `new Application()` | it is not a setting: assign `app.pauseOnBlur = false` |
|
|
515
556
|
|
|
516
557
|
## Related skills
|
|
517
558
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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 what each one draws, and drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, baking with CanvasRenderTarget, and reading pixels back. Triggers on: Camera2d, viewport, follow, offset, look ahead, setBounds, minimap, second camera, split screen, cullMask, visibilityLayer, hide from a camera, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, setGlobalAlpha, fillArc, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d, lookAt, setBasis, toImageData, getImageData, toBlob, toDataURL, screenshot, read pixels."
|
|
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 what each one draws, and drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, baking with CanvasRenderTarget, and reading pixels back. Triggers on: Camera2d, viewport, follow, offset, look ahead, setBounds, minimap, second camera, split screen, cullMask, visibilityLayer, hide from a camera, zoom, roll, shake, fadeIn, fadeOut, CameraEffect, addCameraEffect, MaskEffect, PixelateTransitionEffect, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, setGlobalAlpha, fillArc, Rect, Ellipse, Polygon, Line, Path2D, SVG path, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d, lookAt, setBasis, toImageData, getImageData, toBlob, toDataURL, screenshot, read pixels."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -109,6 +109,13 @@ The fade pair is named from the *colour's* point of view, not the scene's:
|
|
|
109
109
|
(screen starts at `color` and clears). Both default to a 1000 ms duration and
|
|
110
110
|
take an optional completion callback.
|
|
111
111
|
|
|
112
|
+
`shake()` while a shake is still running is ignored, unless its fifth
|
|
113
|
+
argument, `force`, is `true`, which replaces the running one:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
app.viewport.shake(8, 300, app.viewport.AXIS.BOTH, undefined, true);
|
|
117
|
+
```
|
|
118
|
+
|
|
112
119
|
**`fadeIn` does not HOLD the colour.** The effect completes, the camera's own
|
|
113
120
|
update destroys it, and the screen snaps back — which is why the engine's
|
|
114
121
|
idiom does the work in the completion callback and then calls `fadeOut`. To
|
|
@@ -136,6 +143,48 @@ non-identity, and drops it again after the frame. It is **not** shader-free —
|
|
|
136
143
|
it is a `ColorMatrixEffect`, i.e. a `ShaderEffect`, so like every post effect it
|
|
137
144
|
is inert on the Canvas renderer.
|
|
138
145
|
|
|
146
|
+
### The camera effects, and your own
|
|
147
|
+
|
|
148
|
+
Each is a `CameraEffect`, added with `camera.addCameraEffect(effect)` (which
|
|
149
|
+
returns it), found with `getCameraEffect(Class)` and taken off with
|
|
150
|
+
`removeCameraEffect(effect)`, which destroys it:
|
|
151
|
+
|
|
152
|
+
| effect | options | what it does |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `ShakeEffect` | `intensity`, `duration`, `axis`, `onComplete` | jitters `offset`; what `shake()` adds. Several can run at once this way |
|
|
155
|
+
| `FadeEffect` | `color`, `duration` (1000), `direction: "in" \| "out"`, `onComplete` | what `fadeIn()` / `fadeOut()` add |
|
|
156
|
+
| `MaskEffect` | `shape`, `color`, `duration` (500), `direction: "hide" \| "reveal"`, `onComplete` | an iris or a wipe through a unit-sized `Ellipse` or `Polygon` centred on the origin |
|
|
157
|
+
| `PixelateTransitionEffect` | `renderer`, `size` (32), `duration` (500), `direction: "hide" \| "reveal"`, `onComplete` | the view breaks into blocks up to `size` screen pixels, or comes back from them. A shader: on Canvas it keeps its timing and draws nothing |
|
|
158
|
+
|
|
159
|
+
`state.transition("fade" | "mask" | "pixelate", …)` drives the last three
|
|
160
|
+
for a state change (see `melonjs-scenes-and-state`). A `Trigger` that loads a
|
|
161
|
+
level transitions only with both a `color` and a `duration`, and takes only
|
|
162
|
+
`transition: "fade"` (the default) or `"mask"` with a `shape`: anything else,
|
|
163
|
+
`"pixelate"` included, is a fade.
|
|
164
|
+
|
|
165
|
+
For an effect of your own, extend `CameraEffect`: `update(dt)` runs every
|
|
166
|
+
frame (`dt` in ms), `draw(renderer, width, height)` draws over the camera's
|
|
167
|
+
view, and `destroy()` frees what it holds. Set `isComplete = true` and the
|
|
168
|
+
camera's update destroys it that frame, unless it is `isPersistent`.
|
|
169
|
+
|
|
170
|
+
## Zoom and roll
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
app.viewport.zoom = 2; // above 1 zooms in, below 1 out
|
|
174
|
+
app.viewport.roll = Math.PI / 32; // a slight dutch angle, in radians
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
A zoomed camera follows, keeps to its bounds and centres on `focusOn` in world
|
|
178
|
+
units, and `localToWorld` / `worldToLocal` count the zoom. A zoom of 0 or less
|
|
179
|
+
is taken as 1.
|
|
180
|
+
|
|
181
|
+
`roll` is absolute (assigning the same angle twice changes nothing), and the
|
|
182
|
+
pointer conversions undo it, so picking under a rolled camera needs nothing.
|
|
183
|
+
It OWNS the camera's `currentTransform`: each assignment rebuilds it, so a
|
|
184
|
+
`rotate()` or `scale()` called on the camera itself is lost at the next one.
|
|
185
|
+
`reset()` puts it back to 0. On a `Camera3d` the same `roll` banks the view
|
|
186
|
+
(see below).
|
|
187
|
+
|
|
139
188
|
## Coordinate conversion
|
|
140
189
|
|
|
141
190
|
```js
|
|
@@ -291,8 +340,38 @@ backend, though: Canvas closes it with a straight chord, the GPU renderers fill
|
|
|
291
340
|
a pie slice from the centre.
|
|
292
341
|
|
|
293
342
|
Geometry available: `Rect`, `RoundRect`, `Ellipse`, `Polygon`, `Line`, `Point`
|
|
294
|
-
(and a `Bounds`)
|
|
295
|
-
|
|
343
|
+
(and a `Bounds`), plus the 3D `Box3d` and `Sphere`, drawn as their footprint
|
|
344
|
+
and their circle on the screen plane. `renderer.fill(shape)` and
|
|
345
|
+
`renderer.stroke(shape)` take any of them and throw `Invalid geometry for
|
|
346
|
+
fill/stroke` for anything else.
|
|
347
|
+
|
|
348
|
+
### Paths and SVG strings
|
|
349
|
+
|
|
350
|
+
For any other outline, build a path on the renderer's own `renderer.path2D`,
|
|
351
|
+
then call `fill()` / `stroke()` with no argument:
|
|
352
|
+
|
|
353
|
+
```js
|
|
354
|
+
renderer.path2D.parseSVGPath("M 50 0 L 100 100 L 0 100 Z");
|
|
355
|
+
renderer.setColor("#4CAF50");
|
|
356
|
+
renderer.fill();
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
`renderer.path2D` also takes the methods of the browser's `Path2D`
|
|
360
|
+
(`beginPath`, `moveTo`, `lineTo`, `arc`, `arcTo`, `ellipse`, `rect`,
|
|
361
|
+
`roundRect`, `quadraticCurveTo`, `bezierCurveTo`, `closePath`).
|
|
362
|
+
|
|
363
|
+
- **Every `moveTo`, every `M`, starts a sub-path, and filled, each one after
|
|
364
|
+
the first is a HOLE**: a donut, the counter of an "O". Wind a hole the
|
|
365
|
+
opposite way to the outline, as Canvas fills by the non-zero rule, where a
|
|
366
|
+
hole wound the same way fills in. Draw separate shapes with separate fills.
|
|
367
|
+
- **`parseSVGPath` reads absolute commands only** (`M L H V Q C A Z`). A
|
|
368
|
+
lowercase, relative command is read as its absolute twin, so a path
|
|
369
|
+
exported with relative commands comes out scrambled, and `H` / `V` take an
|
|
370
|
+
OFFSET from the pen, not the coordinate SVG means. Export absolute
|
|
371
|
+
coordinates.
|
|
372
|
+
- **The engine's `Path2D` class is not exported.** `new Path2D()` in a game is
|
|
373
|
+
the browser's own, which `fill()` / `stroke()` refuse as invalid geometry:
|
|
374
|
+
use `renderer.path2D`.
|
|
296
375
|
|
|
297
376
|
`Gradient` gives linear and radial fills, built with
|
|
298
377
|
`renderer.createLinearGradient(x0, y0, x1, y1)` /
|
|
@@ -353,10 +432,11 @@ drawable, and the engine's own bakes (the particle emitter's default texture)
|
|
|
353
432
|
hand `.canvas` on for exactly that reason.
|
|
354
433
|
|
|
355
434
|
**Create it once and keep it.** On a GPU backend, once drawn, its texture stays
|
|
356
|
-
on the GPU
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
435
|
+
on the GPU until `rt.destroy(renderer)` frees it: a stage change does not, and
|
|
436
|
+
`destroy()` without the renderer frees the canvas but not that copy. A target
|
|
437
|
+
made for every level, every restart or every spawned object and never
|
|
438
|
+
destroyed leaks one texture each time, so make it once (at module level, say)
|
|
439
|
+
and clear it for reuse. If you paint into it again after it has
|
|
360
440
|
been drawn, call `rt.invalidate(renderer)` before the next draw so the GPU copy
|
|
361
441
|
is refreshed.
|
|
362
442
|
|
|
@@ -435,7 +515,14 @@ const data = await rt.toImageData(0, 0, w, h);
|
|
|
435
515
|
| baked target draws nothing / throws | passed the `CanvasRenderTarget`, not its `.canvas` |
|
|
436
516
|
| `getImageData()` throws on one machine but not another | that one is on the WebGPU renderer, which cannot read back synchronously — `await toImageData()` instead |
|
|
437
517
|
| frame rate drops with many static draws | bake into a `CanvasRenderTarget` instead |
|
|
438
|
-
| GPU memory grows with every level or restart | a `CanvasRenderTarget` created each time: make one and reuse it |
|
|
518
|
+
| GPU memory grows with every level or restart | a `CanvasRenderTarget` created each time: make one and reuse it, or `destroy(renderer)` the old one |
|
|
519
|
+
| a second `shake()` does nothing | one is still running: pass `force` (the fifth argument) to replace it |
|
|
520
|
+
| a `Trigger` with `transition: "pixelate"` fades instead | a trigger takes only `"fade"` or `"mask"`; add a `PixelateTransitionEffect` yourself |
|
|
521
|
+
| a `Trigger` loads its level with no transition | it needs both `color` and `duration` |
|
|
522
|
+
| a `rotate()` or `scale()` on the camera is lost | `roll` owns the camera's `currentTransform` and rebuilds it; use `roll` and `zoom` |
|
|
523
|
+
| an SVG path from an editor comes out scrambled | `parseSVGPath` reads absolute commands only, and `H` / `V` as offsets |
|
|
524
|
+
| a hole in a path fills in, on Canvas only | it is wound the same way as the outline; wind it the opposite way |
|
|
525
|
+
| `fill(new Path2D(...))` throws `Invalid geometry for fill/stroke` | that is the browser's `Path2D`; build on `renderer.path2D` and call `fill()` |
|
|
439
526
|
| a repainted `CanvasRenderTarget` still shows the old pixels | call `rt.invalidate(renderer)` after painting |
|
|
440
527
|
| a 3D camera cannot be given an arbitrary up | `Camera3d#lookAt(target, up)` or `setBasis(right, up, forward)`; see `melonjs-3d` |
|
|
441
528
|
| particles or effects clutter a minimap | every camera draws everything: put them on their own `visibilityLayer` and leave it out of the minimap's `cullMask` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-deployment
|
|
3
|
-
description: "Use this skill when packaging, shipping or scaffolding a melonJS game — starting a project with create-melonjs, building for the web, wrapping as a native app with Capacitor or Cordova, publishing to WeChat Mini Games or Facebook Instant Games, browser support and transpiling. Triggers on: create-melonjs, scaffold, new project, build, deploy, publish, Capacitor, Cordova, native app, iOS, Android, WeChat, Instant Games, browser support, transpile, ES2022, bundle size, tree-shaking."
|
|
3
|
+
description: "Use this skill when packaging, shipping or scaffolding a melonJS game — starting a project with create-melonjs, building for the web, wrapping as a native app with Capacitor or Cordova, publishing to WeChat Mini Games or Facebook Instant Games, running with no DOM (nx.js on the Nintendo Switch, a Web Worker), browser support and transpiling. Triggers on: create-melonjs, scaffold, new project, build, deploy, publish, Capacitor, Cordova, native app, iOS, Android, WeChat, Instant Games, nx.js, Nintendo Switch, homebrew, no DOM, Web Worker, OffscreenCanvas, browser support, transpile, ES2022, bundle size, tree-shaking."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -15,9 +15,11 @@ npm create melonjs@latest my-game
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
The project name is required — without it the command prints its usage and
|
|
18
|
-
exits
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
exits — and it names a new directory: an existing one is refused. It downloads
|
|
19
|
+
the melonJS TypeScript boilerplate (Vite, TypeScript and the debug plugin
|
|
20
|
+
already wired in), which saves you the top-level-await and asset-path issues
|
|
21
|
+
that a hand-rolled config usually hits first. Then `npm install` and
|
|
22
|
+
`npm run dev` in that directory.
|
|
21
23
|
|
|
22
24
|
## Adding melonJS to an existing project
|
|
23
25
|
|
|
@@ -69,14 +71,22 @@ Two caveats worth knowing:
|
|
|
69
71
|
|
|
70
72
|
## Native apps
|
|
71
73
|
|
|
72
|
-
**Capacitor** is the current recommendation for iOS and Android
|
|
73
|
-
`@melonjs/capacitor-plugin
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
**Capacitor** is the current recommendation for iOS and Android. A melonJS game
|
|
75
|
+
runs in its WebView unchanged; `@melonjs/capacitor-plugin` adds the native
|
|
76
|
+
lifecycle: the game pauses when the app goes to the background and resumes when
|
|
77
|
+
it comes back (but stays paused if the player had paused it, from 1.0.1), the
|
|
78
|
+
hardware back button reaches the current stage (`bindStageBack`), and
|
|
79
|
+
orientation lock and the splash screen are one call each. The boilerplate's
|
|
80
|
+
Vite build writes to `dist/`, Capacitor's default `webDir`. See
|
|
81
|
+
`melonjs-plugins` for its options. **Cordova** also works and is documented,
|
|
82
|
+
but is the older route.
|
|
83
|
+
|
|
84
|
+
The thing that breaks first under Cordova is asset loading: the app is served
|
|
85
|
+
from `file://`, where `fetch()` cannot read local files. melonJS's loader falls
|
|
86
|
+
back to XHR for that scheme, so engine-loaded assets work — but any `fetch()` in
|
|
87
|
+
*your* game code will fail there. Capacitor serves the app from a local web
|
|
88
|
+
server instead (`https://localhost` on Android, `capacitor://localhost` on
|
|
89
|
+
iOS), so `fetch()` works there.
|
|
80
90
|
|
|
81
91
|
## Mini-game platforms
|
|
82
92
|
|
|
@@ -85,7 +95,67 @@ wiki. Note that `device.platform.isWeixin` detects WeChat's **in-app browser**,
|
|
|
85
95
|
which is a normal WebView. WeChat *Mini Games* are a different runtime with no
|
|
86
96
|
DOM, and need platform-specific adaptation rather than just detection — the one
|
|
87
97
|
hook the engine offers is that the renderer adopts a `globalThis.canvas` when
|
|
88
|
-
the runtime's adapter provides one, instead of creating its own.
|
|
98
|
+
the runtime's adapter provides one, instead of creating its own. What the engine
|
|
99
|
+
does without a DOM is described in the next section.
|
|
100
|
+
|
|
101
|
+
## Runtimes with no DOM: nx.js, Web Workers
|
|
102
|
+
|
|
103
|
+
melonJS (from 20.14) runs where there is no `document`: in a Web Worker, and in
|
|
104
|
+
runtimes that implement the web APIs a game uses but no page around it, such as
|
|
105
|
+
[nx.js](https://github.com/TooTallNate/nx.js), JavaScript homebrew for the
|
|
106
|
+
Nintendo Switch. There is no `<canvas>` to create there, so hand the
|
|
107
|
+
Application the canvas to draw to. On nx.js that is the global `screen`, the
|
|
108
|
+
1280x720 display:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import { Application, video } from "melonjs";
|
|
112
|
+
|
|
113
|
+
const app = new Application(1280, 720, {
|
|
114
|
+
canvas: screen, // nx.js's display; an OffscreenCanvas in a worker
|
|
115
|
+
renderer: video.AUTO, // WebGL 2 where the runtime has it, else Canvas
|
|
116
|
+
scale: 1,
|
|
117
|
+
});
|
|
118
|
+
await app.init();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
What changes without a DOM:
|
|
122
|
+
|
|
123
|
+
- **Design at the canvas's size.** There is no page to fit, so
|
|
124
|
+
`scale: "auto"` measures nothing and the game is drawn at the scale it was
|
|
125
|
+
given. On the Switch that is 1280x720 (the console scales it when docked).
|
|
126
|
+
- `app.parentElement` is `undefined`: the canvas is the display and is not
|
|
127
|
+
attached to anything. Fullscreen and pointer lock do nothing.
|
|
128
|
+
- **Assets** load through `fetch` and `createImageBitmap` as in a browser; on
|
|
129
|
+
nx.js a relative path resolves inside the app's RomFS. Tiled maps must be
|
|
130
|
+
**JSON** (`.tmj` / `.tsj`): XML needs `DOMParser` (see `melonjs-tilemaps`).
|
|
131
|
+
The `video` and `script` asset types need a DOM and fail.
|
|
132
|
+
- **Input**: touch arrives as `TouchEvent`s on the canvas, and melonJS reports
|
|
133
|
+
them as pointer events as usual. There is no mouse. On nx.js the Joy-Con are
|
|
134
|
+
standard gamepads (`melonjs-input`), and keyboard events only come from a
|
|
135
|
+
USB keyboard.
|
|
136
|
+
- **Audio** needs no gesture to unlock. nx.js cannot say which formats it
|
|
137
|
+
decodes (its `Audio` has no `canPlayType`), so melonJS tries the first
|
|
138
|
+
extension given to `audio.init()`: list one FFmpeg decodes, such as
|
|
139
|
+
`audio.init("ogg,mp3")`. nx.js's Web Audio has no `PannerNode`,
|
|
140
|
+
`OscillatorNode`, `BiquadFilterNode` or effect nodes, so plain playback,
|
|
141
|
+
volume and `stereo()` work, but 3D `position()` / `panner()`, `tone()` /
|
|
142
|
+
`noise()` and the reverb / echo / compressor effects throw there (see
|
|
143
|
+
`melonjs-audio`).
|
|
144
|
+
- **Fonts**: nx.js's `FontFace` only takes the font's bytes, so a `fontface`
|
|
145
|
+
asset (a URL) fails there. Load the bytes yourself and add the face before
|
|
146
|
+
using it in a `Text`. The face is ready once made, and its `load()` throws
|
|
147
|
+
on nx.js, so do not call it:
|
|
148
|
+
`fonts.add(new FontFace("Pixel", await (await fetch("font.ttf")).arrayBuffer()))`.
|
|
149
|
+
- **Save data**: melonJS checks `localStorage` when it loads. On nx.js, if the
|
|
150
|
+
app declares user save data (`userAccountSaveDataSize` in its NACP), that
|
|
151
|
+
first read is when the Switch asks the player to pick a user profile. An app
|
|
152
|
+
that saves nothing should leave that size at 0, so no picker appears.
|
|
153
|
+
- A Web Worker has no `Image`, `Audio` or `AudioContext` of its own: draw
|
|
154
|
+
there, and play sound from the page.
|
|
155
|
+
|
|
156
|
+
The engine's own test, `tests/nxjs-compatibility.spec.js`, runs a game in a
|
|
157
|
+
Web Worker made to look like nx.js. It is the reference for what is expected
|
|
158
|
+
to work, not a test on Switch hardware.
|
|
89
159
|
|
|
90
160
|
## Size
|
|
91
161
|
|
|
@@ -103,11 +173,19 @@ are not decoded into memory.
|
|
|
103
173
|
|---|---|
|
|
104
174
|
| blank page, syntax error in the console | browser older than the ES2022 target |
|
|
105
175
|
| transpiling changes nothing | build config excludes `node_modules` |
|
|
106
|
-
| assets 404 in a packaged
|
|
176
|
+
| assets 404 in a packaged Cordova app | `fetch()` in game code — `file://` needs XHR |
|
|
107
177
|
| a built-in method is missing after transpiling | transpiling is syntax-only; polyfills are separate |
|
|
108
178
|
| bundle much larger than expected | tree-shaking recovers little — subsystems are interconnected |
|
|
179
|
+
| a native app paused from its menu is running again after coming back from the background | capacitor-plugin < 1.0.1 resumed on every return; upgrade |
|
|
180
|
+
| `Cannot read properties of undefined (reading 'body')` from `new Application` in nx.js or a Web Worker | melonJS older than 20.14, which needed a DOM; update |
|
|
181
|
+
| WebGL 2 "could not be created" on nx.js | another context was taken on `screen` first: it holds one context kind, so let the Application be the first to call `getContext` on it |
|
|
182
|
+
| the game is drawn small, or cropped, on nx.js | its resolution is not the screen's 1280x720; with no page, `scale: "auto"` does not fit it |
|
|
183
|
+
| an XML Tiled map fails with "needs DOMParser" | no DOM to parse XML with: export the map and its tilesets as JSON (`.tmj` / `.tsj`) |
|
|
184
|
+
| a `fontface` asset fails on nx.js | nx.js's `FontFace` takes bytes, not a URL: fetch the file and add the face to `fonts` yourself |
|
|
185
|
+
| spatial sound, `tone()` or `noise()` throws "Method not implemented" on nx.js | the node it needs is missing from nx.js's Web Audio; use `stereo()` and recorded sounds |
|
|
109
186
|
|
|
110
187
|
## Related skills
|
|
111
188
|
|
|
112
189
|
- `melonjs` — the companion packages, including the Capacitor plugin
|
|
190
|
+
- `melonjs-plugins` — registering the Capacitor plugin, and its helpers
|
|
113
191
|
- `melonjs-getting-started` — the Application bootstrap the template produces
|
|
@@ -39,8 +39,11 @@ the usual arrangement: author once, scale to whatever you are given.
|
|
|
39
39
|
|
|
40
40
|
## `scale` and `scaleMethod`
|
|
41
41
|
|
|
42
|
-
`scale: "auto"` turns on automatic scaling
|
|
43
|
-
`scaleMethod`
|
|
42
|
+
`scale: "auto"` turns on automatic scaling, and so does naming any
|
|
43
|
+
`scaleMethod` other than `"manual"`, even with no `scale` given:
|
|
44
|
+
`{ scaleMethod: "flex" }` alone scales to its container. A number is a fixed
|
|
45
|
+
multiplier, used only while `scaleMethod` is `"manual"` (the default); with
|
|
46
|
+
automatic scaling on, the number is ignored.
|
|
44
47
|
|
|
45
48
|
`scaleMethod` decides how the available space is used:
|
|
46
49
|
|
|
@@ -54,7 +57,8 @@ the usual arrangement: author once, scale to whatever you are given.
|
|
|
54
57
|
both axes.
|
|
55
58
|
- **`"stretch"`** scales each axis independently, so the aspect ratio is not
|
|
56
59
|
preserved and circles become ovals.
|
|
57
|
-
- **`"manual"
|
|
60
|
+
- **`"manual"`**, the default, does nothing automatic with a numeric `scale`.
|
|
61
|
+
With `scale: "auto"` it scales as `"fit"` does.
|
|
58
62
|
|
|
59
63
|
The distinction that matters: **`fit` scales a fixed frame; everything else
|
|
60
64
|
changes how much of the world is visible.** A game whose layout assumes a
|
|
@@ -381,3 +385,4 @@ const app = new Application(1024, 576, {
|
|
|
381
385
|
| `scaleRounding: "integer"` changes nothing | it applies to `scaleMethod: "fit"` only; the others resize the renderer |
|
|
382
386
|
| text is soft but sprites are crisp | the glyphs were anti-aliased when `Text` baked them; use `BitmapText` (see below) |
|
|
383
387
|
| the game shows more world on a bigger window | that is `fill-*` or `flex-*` doing their job; use `"fit"` to keep a fixed frame |
|
|
388
|
+
| a numeric `scale` (say `2`) is ignored | a `scaleMethod` other than `"manual"` turns automatic scaling on, which ignores the number |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-effects-and-shaders
|
|
3
|
-
description: "Use this skill for post-processing effects, custom shaders, blend modes and colour grading in melonJS. Covers the built-in ShaderEffect presets, addPostEffect on renderables and cameras, writing a custom dual-language GLSL/WGSL effect, the screen_texture builtins, and why effects silently do nothing on the Canvas fallback. Triggers on: Tween, easing, animate an effect, ShaderEffect, addPostEffect, removePostEffect, getPostEffect, postEffectNeedsCapture, VignetteEffect, GlowEffect, BlurEffect, PixelateEffect, ScanlineEffect, shader, GLSL, WGSL, uniform, setUniform, setTexture, setTime, blendMode, colorMatrix, screen_texture, toFrameTexture, post effect, filter."
|
|
3
|
+
description: "Use this skill for post-processing effects, custom shaders, blend modes and colour grading in melonJS. Covers the built-in ShaderEffect presets, addPostEffect on renderables and cameras, writing a custom dual-language GLSL/WGSL effect, the screen_texture builtins, and why effects silently do nothing on the Canvas fallback. Triggers on: Tween, easing, animate an effect, ShaderEffect, addPostEffect, removePostEffect, getPostEffect, postEffectNeedsCapture, VignetteEffect, GlowEffect, BlurEffect, PixelateEffect, ScanlineEffect, OutlineEffect, BloomEffect, ToneMappingEffect, SwayEffect, SquishEffect, hdr, tone mapping, shader, GLSL, WGSL, uniform, setUniform, setTexture, setTime, getShader, prewarm, blendMode, colorMatrix, screen_texture, screen_uv, noise_uv, toFrameTexture, pixelate transition, post effect, filter."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -88,8 +88,9 @@ class Bar extends Renderable {
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
`ProgressBar` and `Trail` set it for you, and a sprite needs
|
|
92
|
-
defaults to `false` and only ever changes the single-effect
|
|
91
|
+
`ProgressBar`, `UIPanel` and `Trail` set it for you, and a sprite needs
|
|
92
|
+
nothing: the flag defaults to `false` and only ever changes the single-effect
|
|
93
|
+
case.
|
|
93
94
|
|
|
94
95
|
## Toggle with `enabled`, do not remove
|
|
95
96
|
|
|
@@ -229,7 +230,8 @@ squish.setBulge(1 - scaleY);
|
|
|
229
230
|
```
|
|
230
231
|
|
|
231
232
|
Apply that from the sprite's own `update()`, not from a tween's `onUpdate`. A
|
|
232
|
-
physics adapter that mirrors a body's angle (
|
|
233
|
+
physics adapter that mirrors a body's angle (matter, planck and Rapier do, and so does
|
|
234
|
+
the built-in once a body has an angle or angular velocity) rewrites
|
|
233
235
|
`currentTransform` on every step, before the renderables update, so a scale set
|
|
234
236
|
anywhere else is gone by the time the sprite is drawn. Let the tween animate a
|
|
235
237
|
plain `{ scaleY }` object and read it in `update()`.
|
|
@@ -457,8 +459,10 @@ renderer.setToneMapping("aces", { exposure: 0.4, white: 6 });
|
|
|
457
459
|
renderer.setToneMapping("none"); // and off again
|
|
458
460
|
```
|
|
459
461
|
|
|
460
|
-
`"none"` by default
|
|
461
|
-
|
|
462
|
+
`"none"` by default, and set at boot through the `toneMapping`,
|
|
463
|
+
`toneMappingExposure` and `toneMappingWhite` application settings. The
|
|
464
|
+
renderer keeps the curve LAST on the camera, so whatever you put in front of
|
|
465
|
+
it is mapped along with everything else. Changing
|
|
462
466
|
the mode rebuilds one shader (the curve is baked); `exposure` and `white` are
|
|
463
467
|
uniform writes, so a settings screen can drive them live. `white` is the value
|
|
464
468
|
that comes out as display white — the knob a "brightness" or "peak" slider
|
|
@@ -483,6 +487,13 @@ It is still *implemented* as one: a non-identity matrix makes the camera append
|
|
|
483
487
|
an internal `ColorMatrixEffect` to its post-effect chain for that frame, so it
|
|
484
488
|
needs a GPU backend like everything else here.
|
|
485
489
|
|
|
490
|
+
The `"pixelate"` state transition (`state.transition("pixelate", …)`, or a
|
|
491
|
+
`PixelateTransitionEffect` added with `addCameraEffect`) is another such guest:
|
|
492
|
+
while it runs it puts a `PixelateEffect` FIRST in the camera's chain, so a
|
|
493
|
+
vignette or scanlines you added stay sharp over the blocks, and it takes the
|
|
494
|
+
effect off again when it is done. Being a shader, it does nothing on Canvas,
|
|
495
|
+
where `state.transition` changes the state directly instead.
|
|
496
|
+
|
|
486
497
|
## Custom effects are dual-language
|
|
487
498
|
|
|
488
499
|
A custom `ShaderEffect` supplies a fragment **body**, not a whole program. To run
|
|
@@ -582,8 +593,8 @@ string in your own module:
|
|
|
582
593
|
|
|
583
594
|
```js
|
|
584
595
|
// with the level's other assets, not in the stage that uses them.
|
|
585
|
-
// `preload()`
|
|
586
|
-
//
|
|
596
|
+
// `preload()` or a bundle's `load(id)`, not `load(asset)`: those two warm
|
|
597
|
+
// the shaders they bring in, and run again per level, not just at boot
|
|
587
598
|
await loader.preload([
|
|
588
599
|
{ name: "ripples", type: "shader", src: {
|
|
589
600
|
glsl: "assets/level2/ripples.glsl",
|
|
@@ -596,6 +607,14 @@ await loader.preload([
|
|
|
596
607
|
`src` takes the dual-language pair, so one asset carries both realizations and
|
|
597
608
|
the two backends cannot drift apart. `data` takes inline source the same way.
|
|
598
609
|
|
|
610
|
+
A level's bundle (20.14: `loader.load("level2", [...])` declares and loads
|
|
611
|
+
one, warming like `preload` but with no loading screen) is also the shader's
|
|
612
|
+
lifetime. Unloading the last bundle that lists it, or `loader.unload()` of the
|
|
613
|
+
shader asset itself, destroys the shared instance whatever still draws with
|
|
614
|
+
it, and those renderables then draw without the effect. An effect that
|
|
615
|
+
outlives a level (a HUD's, a camera's) belongs in a bundle that outlives it
|
|
616
|
+
too; see `melonjs-loading-assets`.
|
|
617
|
+
|
|
599
618
|
With `prewarm` on (the default since 20.6), `loader.preload()` warms on its way
|
|
600
619
|
through — automatically, on every call — and compiles the GPU-side objects for
|
|
601
620
|
every shader it holds — on WebGPU that is the
|
|
@@ -632,7 +651,9 @@ variants are covered by the loading-screen warm-up and by the per-renderable one
|
|
|
632
651
|
For refraction, distortion and anything that samples what is already drawn:
|
|
633
652
|
|
|
634
653
|
- **`screen_texture`** — a sampler kept filled with everything drawn so far
|
|
635
|
-
(GLSL: annotate the sampler `uniform sampler2D tex : screen_texture
|
|
654
|
+
(GLSL: annotate the sampler `uniform sampler2D tex : screen_texture;`, or
|
|
655
|
+
`: screen_texture(repeat)` to sample it wrapping; `repeat-x` and
|
|
656
|
+
`repeat-y` work too)
|
|
636
657
|
- **`screen_uv`** — this fragment's 0..1 position in that capture
|
|
637
658
|
- **`noise_uv`** — 0..1 across the sprite regardless of its atlas frame, and
|
|
638
659
|
across the renderable when it has several effects (it does not slide with
|
|
@@ -650,7 +671,11 @@ Two places where a straight port really does differ:
|
|
|
650
671
|
into a `Texture2d` you hand to `setTexture` — the supported replacement for
|
|
651
672
|
`readPixels` tricks. The GL capture is bottom-up (GLSL bodies sample it with
|
|
652
673
|
`1.0 - uv.y`) and opaque RGB; the WebGPU capture is top-down (the WGSL twin
|
|
653
|
-
must *not* flip) and preserves alpha.
|
|
674
|
+
must *not* flip) and preserves alpha. It holds what was drawn when it was
|
|
675
|
+
called, so call it right before the draw that samples it. By default it
|
|
676
|
+
refills one shared, renderer-owned texture; `toFrameTexture({ target: null })`
|
|
677
|
+
hands back a capture of your own, for two alive at once, which you
|
|
678
|
+
`destroy()` when done.
|
|
654
679
|
- **Vertical UV offsets** — a drop shadow, a directional smear. The WebGL pooled
|
|
655
680
|
multi-effect path composites through bottom-up FBOs, so declare a `uUVYDir`
|
|
656
681
|
uniform (initialise it to `1.0`) and multiply vertical offsets by it; the
|
|
@@ -724,10 +749,14 @@ capture plus one composite **per draw**: `difference`, `overlay`, `hard-light`,
|
|
|
724
749
|
accents; expensive for hundreds of objects. The Canvas renderer reaches all
|
|
725
750
|
eight through `globalCompositeOperation` instead, at no extra cost.
|
|
726
751
|
|
|
727
|
-
Anything else collapses to `"normal"
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
752
|
+
Anything else collapses to `"normal"`. `renderer.setBlendMode(mode)` returns
|
|
753
|
+
the mode it actually applied, so comparing that against your request detects
|
|
754
|
+
an unknown name, and `none` on Canvas.
|
|
755
|
+
|
|
756
|
+
Two kinds of draw cannot take the eight composited modes on the GPU backends:
|
|
757
|
+
a 3D mesh and a `Gradient` fill draw them as `"normal"`, with a one-time
|
|
758
|
+
console warning naming which. `setBlendMode` cannot see that one, since it
|
|
759
|
+
happens at the draw, and still reports the mode you asked for.
|
|
731
760
|
|
|
732
761
|
## The Canvas fallback
|
|
733
762
|
|
|
@@ -767,9 +796,11 @@ same question after construction.
|
|
|
767
796
|
| ported shader renders upside down | a hand-bound `toFrameTexture()` capture — GL is bottom-up, WebGPU top-down |
|
|
768
797
|
| shadow/smear offset flips on some draws | vertical UV offset not multiplied by `uUVYDir` |
|
|
769
798
|
| frame rate collapses with many blended sprites | an *advanced* mode (overlay, darken, …) — each draw is a capture plus a composite |
|
|
799
|
+
| `overlay` (or another composited mode) does nothing on a mesh or a gradient fill, with a console warning | those two draws take the eight composited modes as `"normal"` on the GPU backends |
|
|
800
|
+
| an effect from `loader.getShader()` stops drawing after a level change | its asset, or the last bundle listing it, was unloaded, which destroys the shared instance |
|
|
770
801
|
| many sprites with the same effect still cost a draw each | an effect instance per sprite, or a uniform set to a different value per sprite — share one instance and group sprites by value |
|
|
771
802
|
| animated shader never moves | `setTime` fed milliseconds, or the shader declares no `uTime` |
|
|
772
|
-
| squash and stretch only bulges, the height never changes, under
|
|
803
|
+
| squash and stretch only bulges, the height never changes, under matter, planck or Rapier (or a rotating built-in body) | the adapter rewrites `currentTransform` every step; set the scale from the sprite's `update()` |
|
|
773
804
|
| `SquishEffect` squishes the whole screen instead of the sprite | it is chained with another effect, or on the camera; give the sprite it alone |
|
|
774
805
|
| syntax error in a file whose shader you just edited, pointing at a line of GLSL | a backtick in the shader body ended the template literal — most often one wrapped around an identifier in a comment |
|
|
775
806
|
|
|
@@ -55,7 +55,7 @@ game events in a `.ts` project.
|
|
|
55
55
|
| event | when |
|
|
56
56
|
|---|---|
|
|
57
57
|
| `GAME_BEFORE_UPDATE` / `GAME_UPDATE` / `GAME_AFTER_UPDATE` | around the world update — `GAME_UPDATE` is **not** emitted while paused |
|
|
58
|
-
| `GAME_BEFORE_DRAW` / `GAME_AFTER_DRAW` | around the draw pass |
|
|
58
|
+
| `GAME_BEFORE_DRAW` / `GAME_AFTER_DRAW` | around the draw pass, on frames that redraw: one where no update reported a change is not drawn |
|
|
59
59
|
| `WORLD_STEP` | after the physics step |
|
|
60
60
|
| `TICK` | the raw engine tick |
|
|
61
61
|
|
|
@@ -67,7 +67,7 @@ game events in a `.ts` project.
|
|
|
67
67
|
| `STATE_CHANGE`, `STATE_PAUSE`, `STATE_RESUME`, `STATE_STOP` | state machine |
|
|
68
68
|
| `STAGE_RESET` | a stage was reset |
|
|
69
69
|
| `LEVEL_LOADED` | a Tiled level finished loading |
|
|
70
|
-
| `LOADER_PROGRESS`, `LOADER_COMPLETE`, `LOADER_ERROR` | asset loading |
|
|
70
|
+
| `LOADER_PROGRESS`, `LOADER_COMPLETE`, `LOADER_ERROR` | asset loading: every `loader.preload()`, and a bundle loaded with `loader.load(id)` (20.14); not one asset loaded with `loader.load(descriptor)` |
|
|
71
71
|
| `BLUR` / `FOCUS` | the page lost or regained focus |
|
|
72
72
|
| `CANVAS_ONRESIZE`, `VIEWPORT_ONRESIZE` | relayout hooks |
|
|
73
73
|
| `POINTERMOVE` | global pointer movement |
|
|
@@ -153,6 +153,7 @@ Use `removeChild()`, or flag the work and drain it from `Stage.update`.
|
|
|
153
153
|
| crash during a stage switch | callback ran mid-teardown — guard with `state.isCurrent` |
|
|
154
154
|
| crash after a collision | `removeChildNow()` inside a contact callback — use `removeChild()` |
|
|
155
155
|
| progress callbacks never fire | assigned `loader.onProgress` (removed in 20.3) instead of subscribing to `LOADER_PROGRESS` |
|
|
156
|
+
| a `LOADER_COMPLETE` handler (a `state.change` to the first screen) runs again mid-game | a later `preload()`, or a level's bundle loaded with `loader.load(id)`, emits it too; subscribe with `event.once`, or use `preload()`'s promise or callback |
|
|
156
157
|
| `onActivateEvent` never runs | the child was added to a container not yet attached to the world |
|
|
157
158
|
| `onDeactivateEvent` runs twice when a group is removed (a count goes negative, a light or listener is released twice) | the group's `onDeactivateEvent` and its `destroy()` both reached the children, before 20.13.0 — now once per `onActivateEvent` |
|
|
158
159
|
| `onDeactivateEvent` never runs on a child of a group built off-world and destroyed | it never had `onActivateEvent` either: the pair is only sent to children that entered the world |
|