melonjs 20.10.1 → 20.11.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 +3 -5
- package/build/application/application.d.ts.map +1 -1
- package/build/application/defaultApplicationSettings.d.ts +1 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/settings.d.ts +48 -1
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +14 -4
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/types.d.ts +19 -1
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts +48 -4
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/effects/shake_effect.d.ts +3 -1
- package/build/camera/effects/shake_effect.d.ts.map +1 -1
- package/build/index.d.ts +4 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3174 -1038
- package/build/index.js.map +4 -4
- package/build/level/tiled/TMXLayer.d.ts +1 -1
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXObject.d.ts +196 -1
- package/build/level/tiled/TMXObject.d.ts.map +1 -1
- package/build/level/tiled/TMXObjectFactory.d.ts +18 -5
- package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
- package/build/level/tiled/TMXTile.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts +17 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/TMXUtils.d.ts.map +1 -1
- package/build/level/tiled/factories/shape.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/lighting/light2d.d.ts +30 -0
- package/build/lighting/light2d.d.ts.map +1 -1
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +25 -0
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/builtin/raycast.d.ts.map +1 -1
- package/build/physics/builtin/sat.d.ts +40 -1
- package/build/physics/builtin/sat.d.ts.map +1 -1
- package/build/renderable/animation.d.ts +10 -1
- package/build/renderable/animation.d.ts.map +1 -1
- package/build/renderable/colorlayer.d.ts.map +1 -1
- package/build/renderable/container.d.ts +22 -2
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts.map +1 -1
- package/build/renderable/imagelayer.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +69 -1
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +19 -7
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/sprite3d.d.ts +5 -3
- package/build/renderable/sprite3d.d.ts.map +1 -1
- package/build/renderable/text/text.d.ts.map +1 -1
- package/build/state/stage.d.ts +9 -2
- package/build/state/stage.d.ts.map +1 -1
- package/build/system/timer.d.ts +1 -0
- package/build/system/timer.d.ts.map +1 -1
- package/build/utils/fetchdata.d.ts.map +1 -1
- package/build/video/buffer/vertex.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +7 -7
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/ambientlight.d.ts +2 -0
- package/build/video/effects/ambientlight.d.ts.map +1 -0
- package/build/video/effects/bloom.d.ts +7 -9
- package/build/video/effects/bloom.d.ts.map +1 -1
- package/build/video/effects/blur.d.ts +3 -2
- package/build/video/effects/blur.d.ts.map +1 -1
- package/build/video/effects/chromaticAberration.d.ts +3 -2
- package/build/video/effects/chromaticAberration.d.ts.map +1 -1
- package/build/video/effects/dissolve.d.ts +5 -2
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +15 -3
- package/build/video/effects/dropShadow.d.ts.map +1 -1
- package/build/video/effects/flash.d.ts +6 -2
- package/build/video/effects/flash.d.ts.map +1 -1
- package/build/video/effects/frame.d.ts +48 -0
- package/build/video/effects/frame.d.ts.map +1 -0
- package/build/video/effects/glow.d.ts +8 -4
- package/build/video/effects/glow.d.ts.map +1 -1
- package/build/video/effects/glsl_realization.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts +5 -2
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +8 -4
- package/build/video/effects/outline.d.ts.map +1 -1
- package/build/video/effects/pixelate.d.ts +3 -2
- package/build/video/effects/pixelate.d.ts.map +1 -1
- package/build/video/effects/radialGradient.d.ts +9 -4
- package/build/video/effects/radialGradient.d.ts.map +1 -1
- package/build/video/effects/rgba.d.ts +2 -0
- package/build/video/effects/rgba.d.ts.map +1 -0
- package/build/video/effects/shadereffect.d.ts +13 -0
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +5 -2
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/squish.d.ts.map +1 -1
- package/build/video/effects/sway.d.ts +64 -0
- package/build/video/effects/sway.d.ts.map +1 -0
- package/build/video/effects/tintPulse.d.ts +5 -2
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +17 -0
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/effects/wgsl/scaffold.d.ts.map +1 -1
- package/build/video/effects/wgsl_realization.d.ts.map +1 -1
- package/build/video/gpu/frame.d.ts +2 -0
- package/build/video/gpu/frame.d.ts.map +1 -0
- package/build/video/renderer.d.ts +35 -11
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/rendertarget/canvasrendertarget.d.ts +14 -4
- package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/rendertarget.d.ts +12 -16
- package/build/video/rendertarget/rendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +23 -4
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/texture/cache.d.ts.map +1 -1
- package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/glshader.d.ts.map +1 -1
- package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts.map +1 -1
- package/build/video/webgl/shaders/frameclamp.d.ts +2 -0
- package/build/video/webgl/shaders/frameclamp.d.ts.map +1 -0
- package/build/video/webgl/shaders/multitexture-lit.d.ts.map +1 -1
- package/build/video/webgl/shaders/multitexture.d.ts.map +1 -1
- package/build/video/webgl/utils/uniforms.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts +0 -21
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts +2 -1
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts +2 -2
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/buffer/uniformring.d.ts.map +1 -1
- package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +21 -19
- package/skills/melonjs-audio/SKILL.md +22 -1
- package/skills/melonjs-camera-and-drawing/SKILL.md +63 -1
- package/skills/melonjs-display-and-scaling/SKILL.md +98 -2
- package/skills/melonjs-effects-and-shaders/SKILL.md +89 -7
- package/skills/melonjs-lighting/SKILL.md +39 -5
- package/skills/melonjs-math/SKILL.md +21 -1
- package/skills/melonjs-particles-and-trails/SKILL.md +36 -2
- package/skills/melonjs-performance/SKILL.md +1 -0
- package/skills/melonjs-renderables/SKILL.md +42 -3
- package/skills/melonjs-sprites-and-animation/SKILL.md +19 -3
- package/skills/melonjs-tilemaps/SKILL.md +126 -4
|
@@ -100,7 +100,27 @@ await loader.preload([{
|
|
|
100
100
|
audio.play("sfx", { sprite: "jump" });
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
### A loop with no gap
|
|
104
|
+
|
|
105
|
+
An MP3 encoder pads the end of its file. Chrome and Firefox trim the padding when
|
|
106
|
+
they decode it, Safari keeps it, so a looping MP3 has a short silence at every
|
|
107
|
+
loop there (27 ms on a 12 s clip at 22 kHz, measured). Declare a region of the
|
|
108
|
+
loop's exact length and loop on that: the voice never reaches the padding.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
await loader.preload([{
|
|
112
|
+
name: "ambience", type: "audio", src: "data/sfx/",
|
|
113
|
+
sprite: { loop: [0, 12000] },
|
|
114
|
+
}]);
|
|
115
|
+
audio.playTrack("ambience", { sprite: "loop", volume: 0.6 });
|
|
116
|
+
// or, as a plain clip: audio.play("ambience", { sprite: "loop", loop: true })
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Ogg Vorbis carries no such padding.
|
|
120
|
+
|
|
121
|
+
The optional third element marks a region as looping: it loops unless `play()` is
|
|
122
|
+
told `loop: false` (before 20.11 it only looped when `loop: true` was passed as
|
|
123
|
+
well). `play()`'s second argument
|
|
104
124
|
takes either the original `loop` boolean or a `PlayOptions` object
|
|
105
125
|
(`sprite`, `loop`, `onend`, `volume`, plus the spatial `follow`, `at` and
|
|
106
126
|
`stopWithTarget`) — both forms work. Object fields win over the positional
|
|
@@ -467,6 +487,7 @@ either way — blank screen, empty console.
|
|
|
467
487
|
| a looping sound outlives the entity that owns it | `follow` without `stopWithTarget: true` |
|
|
468
488
|
| 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 |
|
|
469
489
|
| 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 |
|
|
490
|
+
| 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") |
|
|
470
491
|
| 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 |
|
|
471
492
|
|
|
472
493
|
## Related skills
|
|
@@ -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 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, setBounds, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, 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, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d, lookAt, setBasis, toImageData, getImageData, toBlob, toDataURL, screenshot, read pixels."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,6 +24,24 @@ frame-rate independent since 19.7. `AXIS.HORIZONTAL` / `AXIS.VERTICAL` /
|
|
|
24
24
|
|
|
25
25
|
Call it from the followed entity's `onActivateEvent`, using `this.parentApp`.
|
|
26
26
|
|
|
27
|
+
**To frame the target off centre, set `offset`; do not follow a helper
|
|
28
|
+
object.** It shifts the view without moving the camera, in world units, and is
|
|
29
|
+
applied after following and the bounds, so the view can show past them:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
app.viewport.follow(player, app.viewport.AXIS.BOTH, 0.1);
|
|
33
|
+
app.viewport.offset.set(0, -70); // look a little above the feet
|
|
34
|
+
|
|
35
|
+
// or ahead of where the player faces, eased (see math.damp below)
|
|
36
|
+
const ahead = facingLeft ? -80 : 80;
|
|
37
|
+
app.viewport.offset.x = math.damp(app.viewport.offset.x, ahead, 4, dt / 1000);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Culling, tile layers, parallax `ImageLayer`s, lights and the pointer
|
|
41
|
+
(`localToWorld`, `worldToLocal`) all count it. `shake()` adds its jitter on top
|
|
42
|
+
and gives the value back when it ends, so setting it before or during a shake
|
|
43
|
+
is fine. `reset()` clears it.
|
|
44
|
+
|
|
27
45
|
**For anything that is NOT the camera, the same smoothing is `math.damp`.**
|
|
28
46
|
`follow()` is frame-rate independent for you; a value you smooth yourself is
|
|
29
47
|
not, and `lerp(current, target, alpha)` once per frame moves further on a slow
|
|
@@ -191,6 +209,37 @@ View state is per camera, not per scene: a `Camera3d` carries its own distance
|
|
|
191
209
|
fog (`setFog`), so a minimap or split-screen view fogs independently of the main
|
|
192
210
|
one — and a `Camera2d` never fogs. See `melonjs-3d`.
|
|
193
211
|
|
|
212
|
+
### What each camera draws: `visibilityLayer` and `cullMask`
|
|
213
|
+
|
|
214
|
+
Every camera draws the whole world. To keep something off one camera only, put
|
|
215
|
+
it on a layer that camera leaves out. A renderable's `visibilityLayer` is the
|
|
216
|
+
layers it is on, as bits (1, 2, 4...); a camera's `cullMask` is the layers it
|
|
217
|
+
draws. A camera draws a renderable only if the two share a bit:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
const PARTICLES = 2;
|
|
221
|
+
emitter.visibilityLayer = PARTICLES; // the particles go on layer 2
|
|
222
|
+
minimap.cullMask = ~PARTICLES; // the minimap draws every layer but 2
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- By default every renderable is on layer 1 and every camera draws every layer
|
|
226
|
+
(`cullMask` is `-1`), so nothing changes until a game sets one.
|
|
227
|
+
- A container left out is left out with everything inside it, however deep:
|
|
228
|
+
set it on an emitter and none of its particles are drawn. The world itself
|
|
229
|
+
obeys it too, and so does what it contains: a child is drawn only if it and
|
|
230
|
+
every container above it share a bit with the camera.
|
|
231
|
+
- A renderable on layer `0` is drawn by no camera; a `cullMask` of `0` draws
|
|
232
|
+
nothing.
|
|
233
|
+
- It decides drawing only: what a camera leaves out still updates, collides and
|
|
234
|
+
receives pointer events.
|
|
235
|
+
- A `Light2d` a camera leaves out, itself or a container it is in, neither
|
|
236
|
+
glows, nor cuts its hole in the darkness, nor lights normal-mapped sprites in
|
|
237
|
+
that camera. A `Light3d` lights the meshes of every camera.
|
|
238
|
+
- It adds to the floating rule: a floating renderable is drawn by a secondary
|
|
239
|
+
camera only with `visibleInAllCameras`, and only if the layers match as well.
|
|
240
|
+
- On a Tiled object, set it as an `int` custom property named
|
|
241
|
+
`visibilityLayer`.
|
|
242
|
+
|
|
194
243
|
## Immediate-mode drawing
|
|
195
244
|
|
|
196
245
|
Inside a custom `draw(renderer)` you can draw shapes directly. Remember the two
|
|
@@ -269,6 +318,14 @@ Pass `rt.canvas`, never `rt` — a `CanvasRenderTarget` is a wrapper, not a
|
|
|
269
318
|
drawable, and the engine's own bakes (the particle emitter's default texture)
|
|
270
319
|
hand `.canvas` on for exactly that reason.
|
|
271
320
|
|
|
321
|
+
**Create it once and keep it.** On a GPU backend, once drawn, its texture stays
|
|
322
|
+
on the GPU for as long as the renderer lives: a stage change does not release
|
|
323
|
+
it, and no public call does. A target made for every level, every restart or
|
|
324
|
+
every spawned object leaks one texture each time, so make it once (at module
|
|
325
|
+
level, say) and clear it for reuse. If you paint into it again after it has
|
|
326
|
+
been drawn, call `rt.invalidate(renderer)` before the next draw so the GPU copy
|
|
327
|
+
is refreshed.
|
|
328
|
+
|
|
272
329
|
This is the sanctioned performance idiom before reaching for a custom shader.
|
|
273
330
|
|
|
274
331
|
**For a solid fill you do not need a canvas at all.** `Renderer.getWhitePixel()`
|
|
@@ -338,7 +395,12 @@ const data = await rt.toImageData(0, 0, w, h);
|
|
|
338
395
|
| baked target draws nothing / throws | passed the `CanvasRenderTarget`, not its `.canvas` |
|
|
339
396
|
| `getImageData()` throws on one machine but not another | that one is on the WebGPU renderer, which cannot read back synchronously — `await toImageData()` instead |
|
|
340
397
|
| frame rate drops with many static draws | bake into a `CanvasRenderTarget` instead |
|
|
398
|
+
| GPU memory grows with every level or restart | a `CanvasRenderTarget` created each time: make one and reuse it |
|
|
399
|
+
| a repainted `CanvasRenderTarget` still shows the old pixels | call `rt.invalidate(renderer)` after painting |
|
|
341
400
|
| a 3D camera cannot be given an arbitrary up | `Camera3d#lookAt(target, up)` or `setBasis(right, up, forward)`; see `melonjs-3d` |
|
|
401
|
+
| 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` |
|
|
402
|
+
| something is missing from one camera only | its `visibilityLayer`, or a container's above it, shares no bit with that camera's `cullMask` |
|
|
403
|
+
| a renderable shows in no camera at all | its `visibilityLayer` is `0`, or the world's own layer is left out |
|
|
342
404
|
|
|
343
405
|
## Related skills
|
|
344
406
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-display-and-scaling
|
|
3
|
-
description: "Use this skill when a melonJS game looks blurry, soft, shimmering or cropped, or when choosing how the canvas fills its page. Covers the design resolution, scale and scaleMethod, scaleTarget, scaleRounding for pixel-perfect integer scaling, antiAlias, textureFilter, subPixel, devicePixelRatio, and which setting does which job. Triggers on: blurry, soft, fuzzy, shimmering, pixel art, pixel perfect, crisp, nearest, linear, scale, scaleMethod, scaleTarget, scaleRounding, integer scaling, antiAlias, textureFilter, subPixel, devicePixelRatio, retina, hidpi, design resolution, canvas size, canvas cropped, letterbox, image-rendering, upscale, resolution."
|
|
3
|
+
description: "Use this skill when a melonJS game looks blurry, soft, shimmering or cropped, or when choosing how the canvas fills its page. Covers the design resolution, scale and scaleMethod, scaleTarget, scaleRounding for pixel-perfect integer scaling, antiAlias, textureFilter and linear-mipmap for what is drawn small, subPixel, syncToDisplay for steady motion, devicePixelRatio, and which setting does which job. Triggers on: blurry, soft, fuzzy, shimmering, pixel art, pixel perfect, crisp, nearest, linear, scale, scaleMethod, scaleTarget, scaleRounding, integer scaling, antiAlias, textureFilter, linear-mipmap, mipmaps, minimap shimmer, zoomed out, subPixel, syncToDisplay, stutter, judder, uneven scrolling, devicePixelRatio, retina, hidpi, design resolution, canvas size, canvas cropped, letterbox, image-rendering, upscale, resolution."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -23,7 +23,9 @@ separate decisions. Knowing which setting owns which is most of the work.
|
|
|
23
23
|
| how a texture is sampled | `antiAlias`, `textureFilter` | nearest or linear, and the CSS upscale filter |
|
|
24
24
|
|
|
25
25
|
`subPixel` is a fifth, orthogonal to all of them: it decides whether a
|
|
26
|
-
renderable may sit on a fractional position WITHIN the frame.
|
|
26
|
+
renderable may sit on a fractional position WITHIN the frame. `syncToDisplay`
|
|
27
|
+
is about the frames in a row rather than one frame: whether motion advances
|
|
28
|
+
by the same amount on each.
|
|
27
29
|
|
|
28
30
|
## Design resolution
|
|
29
31
|
|
|
@@ -155,6 +157,42 @@ can have smooth model edges and nearest-sampled sprites. The presentation half
|
|
|
155
157
|
is not separable: there is no way to get MSAA silhouettes and a hard-edged
|
|
156
158
|
upscale at the same time.
|
|
157
159
|
|
|
160
|
+
## Drawn small: `textureFilter: "linear-mipmap"`
|
|
161
|
+
|
|
162
|
+
A sprite drawn much smaller than its texture (a zoomed-out camera, a minimap,
|
|
163
|
+
a scaled-down sprite) shimmers as it moves, even with linear sampling: each
|
|
164
|
+
screen pixel covers many texels and linear sampling blends only four of them.
|
|
165
|
+
`"linear-mipmap"` samples the texture's mipmaps, smaller copies averaged in
|
|
166
|
+
advance, wherever something is drawn below its size:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
const app = new Application(1280, 720, { textureFilter: "linear-mipmap" });
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
At its size or larger a texture draws as it does under `"linear"`, so a game
|
|
173
|
+
whose main view is at its size and whose minimap is zoomed out gets a smooth
|
|
174
|
+
minimap and an unchanged view. It covers sprites, text and tile layers, not a
|
|
175
|
+
video nor a texture the engine renders into, and a `nearest` texture keeps to
|
|
176
|
+
its full size. GPU backends only; on WebGPU each texture it applies to takes
|
|
177
|
+
about a third more memory.
|
|
178
|
+
|
|
179
|
+
A small mipmap level's texels span many of the art's pixels, so a sample near
|
|
180
|
+
the edge of a frame would reach into the next frame of the atlas. Under
|
|
181
|
+
`"linear-mipmap"` every sprite and tile keeps its samples half a texel of the
|
|
182
|
+
level it reads inside its own frame, so the next frame no longer streaks along
|
|
183
|
+
its edges. Two limits:
|
|
184
|
+
|
|
185
|
+
- a level's texel is an average of a square of the art, 2, 4, 8 pixels a side
|
|
186
|
+
and so on. Where a frame's edge falls inside such a square, its outermost
|
|
187
|
+
texel already holds some of the neighbour: drawn very small, a frame of an
|
|
188
|
+
atlas packed edge to edge can show a faint line of it. Frames on a grid of
|
|
189
|
+
a power of two (a 32 or 64 px sheet) are free of it, and a padding of a few
|
|
190
|
+
pixels between frames removes it elsewhere;
|
|
191
|
+
- a sprite drawn through a `ShaderEffect` samples the texture in the effect's
|
|
192
|
+
own program, which is not kept to the frame.
|
|
193
|
+
|
|
194
|
+
A repeating pattern is left to wrap.
|
|
195
|
+
|
|
158
196
|
## `subPixel` is about position, not scale
|
|
159
197
|
|
|
160
198
|
`subPixel: false` (the default) truncates a renderable's translation to whole
|
|
@@ -168,6 +206,58 @@ snaps positions and is then scaled by 2.667 has thrown that work away at the
|
|
|
168
206
|
last step, which is the contradiction `scaleRounding: "integer"` exists to
|
|
169
207
|
resolve.
|
|
170
208
|
|
|
209
|
+
`subPixel: true` alone does not make motion smooth: with nearest sampling a
|
|
210
|
+
sprite between two pixels is still drawn on one of them, so a scene moving
|
|
211
|
+
4.5 pixels a frame jumps by 4 and 5 in turn. Rendered art (not pixel art)
|
|
212
|
+
moves smoothly with `textureFilter: "linear"` as well, which blends it across
|
|
213
|
+
the two, while the upscale stays crisp (`antiAlias: true` would also smooth the
|
|
214
|
+
whole canvas as it is scaled up, which is what makes it look blurry). A still
|
|
215
|
+
scene is only sharp when the camera rests on a whole pixel, so give it a
|
|
216
|
+
target on whole pixels:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const app = new Application(1280, 720, {
|
|
220
|
+
scale: "auto",
|
|
221
|
+
subPixel: true,
|
|
222
|
+
textureFilter: "linear",
|
|
223
|
+
syncToDisplay: true,
|
|
224
|
+
});
|
|
225
|
+
// the camera follows a point on whole pixels, kept on the hero: it glides
|
|
226
|
+
// between pixels while following, and comes to rest on one
|
|
227
|
+
const focus = new Vector2d();
|
|
228
|
+
app.viewport.follow(focus, app.viewport.AXIS.BOTH, 0.12);
|
|
229
|
+
|
|
230
|
+
// in the hero's update(dt)
|
|
231
|
+
focus.set(Math.round(this.pos.x), Math.round(this.pos.y));
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## `syncToDisplay` is about steady motion
|
|
235
|
+
|
|
236
|
+
The game updates in fixed steps, 60 a second by default, as many as the time
|
|
237
|
+
since the last frame holds. Browsers report that time with up to a millisecond
|
|
238
|
+
of jitter, so on a 60 Hz display a frame now and then gets no update and the
|
|
239
|
+
next one gets two: the world holds still for a frame, then moves twice as far.
|
|
240
|
+
Scrolling, and anything else moving steadily, stutters while the frame rate
|
|
241
|
+
reads a solid 60.
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
const app = new Application(1280, 720, { scale: "auto", syncToDisplay: true });
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
With it on, frame times are counted in whole display frames and what is
|
|
248
|
+
rounded off is carried to the next frame: one update every frame at 60 Hz, one
|
|
249
|
+
every other frame at 120 Hz, and the game keeps its speed. A display within 2%
|
|
250
|
+
of `timer.maxfps` (60 by default), or of 2, 3 or 4 times it, or a half, a third
|
|
251
|
+
or a quarter of it, is counted at exactly that rate, so a 59.94 Hz screen runs
|
|
252
|
+
the game 0.1% slower than the wall clock; leave it off when the updates must
|
|
253
|
+
keep to the wall clock exactly. The display's rate is learned as the game runs,
|
|
254
|
+
and again when it changes (the window moved to another screen). At a refresh
|
|
255
|
+
that is no multiple of the update rate (144 Hz, 165 Hz) the steps cannot fall
|
|
256
|
+
evenly however the time is counted.
|
|
257
|
+
|
|
258
|
+
It is off by default. It sets `timer.syncToDisplay` when the application is
|
|
259
|
+
initialized, which a game can also switch at any time.
|
|
260
|
+
|
|
171
261
|
## Soft text next to crisp sprites
|
|
172
262
|
|
|
173
263
|
If everything is crisp EXCEPT the text, the scale factor is not the cause: a
|
|
@@ -266,6 +356,12 @@ const app = new Application(1024, 576, {
|
|
|
266
356
|
| the canvas is cropped or overflows its box | automatic scaling measured the wrong element; name it with `scaleTarget` |
|
|
267
357
|
| the canvas grows on every resize | the measured ancestor is auto-sized around the canvas itself |
|
|
268
358
|
| things shimmer as they move | fractional positions; check `subPixel`, and that the scale factor is whole |
|
|
359
|
+
| scrolling stutters while the frame rate reads 60 | a frame now and then runs no update and the next two; set `syncToDisplay: true` |
|
|
360
|
+
| moving rendered art jumps by whole pixels with `subPixel: true` | nearest sampling still draws it on one pixel; add `textureFilter: "linear"` |
|
|
361
|
+
| the whole game looks blurry with `antiAlias: true` | it also smooths the canvas as it is scaled up; use `textureFilter: "linear"` instead |
|
|
362
|
+
| a minimap or a zoomed-out view shimmers as it moves | linear sampling blends 4 texels where a pixel covers many; use `textureFilter: "linear-mipmap"` |
|
|
363
|
+
| faint lines along sprites drawn small, from the next frame of the atlas | under `"linear-mipmap"` the built-in sprite shaders keep to the frame, but not a `ShaderEffect`'s own sample, and the smallest levels still mix a frame not on a power-of-two grid with its neighbour: pad the atlas |
|
|
364
|
+
| a still scene looks a little soft with linear sampling | the camera rests between two pixels; follow a target rounded to whole pixels |
|
|
269
365
|
| 3D model edges are jagged | `antiAlias: false` also disables MSAA; turn it on and use `textureFilter` for textures |
|
|
270
366
|
| textures are smoothed when they should be blocky | `antiAlias: true` makes the default texture filter linear; set `textureFilter: "nearest"` |
|
|
271
367
|
| `scaleRounding: "integer"` changes nothing | it applies to `scaleMethod: "fit"` only; the others resize the renderer |
|
|
@@ -110,16 +110,66 @@ reused across renderables.
|
|
|
110
110
|
|
|
111
111
|
## Built-in effects
|
|
112
112
|
|
|
113
|
-
Twenty-
|
|
114
|
-
every one shipping both a GLSL and a WGSL body, so they all run on either
|
|
115
|
-
backend: `VignetteEffect`, `ScanlineEffect` (optional CRT curvature),
|
|
113
|
+
Twenty-three presets, every one constructed as `new XEffect(renderer, options)`
|
|
114
|
+
and every one shipping both a GLSL and a WGSL body, so they all run on either
|
|
115
|
+
GPU backend: `VignetteEffect`, `ScanlineEffect` (optional CRT curvature),
|
|
116
116
|
`BloomEffect`, `GlowEffect`, `BlurEffect`, `PixelateEffect`,
|
|
117
117
|
`ChromaticAberrationEffect`, `DropShadowEffect`, `FlashEffect`,
|
|
118
118
|
`OutlineEffect`, `ColorMatrixEffect`, `DesaturateEffect`, `DissolveEffect`,
|
|
119
|
-
`TintPulseEffect`, `WaveEffect`, `InvertEffect`, `SepiaEffect`,
|
|
119
|
+
`TintPulseEffect`, `WaveEffect`, `SwayEffect`, `InvertEffect`, `SepiaEffect`,
|
|
120
120
|
`HologramEffect`, `ShineEffect`, `RadialGradientEffect`, `SquishEffect`,
|
|
121
121
|
`ToneMappingEffect`.
|
|
122
122
|
|
|
123
|
+
**Colors take an alpha.** Every color option and `setColor` takes `[r, g, b]`
|
|
124
|
+
or `[r, g, b, a]`, each 0.0 to 1.0, and the alpha is the color's own opacity: a
|
|
125
|
+
half-transparent outline, a glow at half strength, a flash that only half
|
|
126
|
+
covers. `[r, g, b]` is opaque, as it always was. Any other length throws.
|
|
127
|
+
`RadialGradientEffect` takes a `Color` instead, and its alpha scales the spot.
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
sprite.addPostEffect(new OutlineEffect(app.renderer, { color: [1, 1, 1, 0.5] }));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**On a sprite cut from an atlas or a tileset, the effects that read around a
|
|
134
|
+
pixel keep to the sprite's own frame.** `GlowEffect`, `OutlineEffect`,
|
|
135
|
+
`BlurEffect`, `DropShadowEffect`, `ChromaticAberrationEffect`, `WaveEffect`,
|
|
136
|
+
`SwayEffect`, `PixelateEffect` and `BloomEffect` read the neighbouring frames
|
|
137
|
+
as transparent, as if the sprite were drawn on its own, so they never show
|
|
138
|
+
along its edges and the atlas needs no padding for them. Past the texture's own
|
|
139
|
+
edge (a standalone image, or the outside of an atlas) nothing changes.
|
|
140
|
+
|
|
141
|
+
**Their widths, offsets and sizes are in pixels of the texture they draw, with
|
|
142
|
+
nothing to pass.** Each of those effects takes the size of what it draws: the
|
|
143
|
+
sprite's whole texture (the atlas image, not the frame), the screen for a
|
|
144
|
+
camera's effect, a mesh's texture. Do not pass `textureSize` to get this: a
|
|
145
|
+
`textureSize` (or `setTextureSize()`) fixes the size instead, for a width
|
|
146
|
+
meant in some other texture's pixels.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
// 2 pixels of the sprite's art, whatever the size of its atlas
|
|
150
|
+
sprite.addPostEffect(new OutlineEffect(app.renderer, { width: 2 }));
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`SwayEffect` leans a sprite or a layer from side to side, as if in a breeze: the
|
|
154
|
+
top sways most and everything from its `anchor` row down stays put, so a tree's
|
|
155
|
+
trunk or a reed's root stays planted. It suits anything planted at its bottom;
|
|
156
|
+
something hanging from its top, like a banner, needs its own shader. It is a
|
|
157
|
+
`WaveEffect`, which takes the same `anchor`, so it adds no shader. Drive it with
|
|
158
|
+
`setTime(seconds)` every frame, like any time-based effect; it wraps the phase
|
|
159
|
+
itself, so a raw clock never costs precision on a phone's medium-precision
|
|
160
|
+
floats.
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
const sway = new SwayEffect(app.renderer, { strength: 0.02, anchor: 0.9 });
|
|
164
|
+
tree.addPostEffect(sway);
|
|
165
|
+
// each frame
|
|
166
|
+
sway.setTime(timer.getTime() / 1000);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Rows are measured across the texture the effect samples, so on a sprite cut from
|
|
170
|
+
an atlas the anchor is a row of the atlas: it suits a standalone image or a
|
|
171
|
+
layer best.
|
|
172
|
+
|
|
123
173
|
`RadialGradientEffect` is a procedural soft spot: solid at the centre of its
|
|
124
174
|
host quad, fading linearly to transparent at the edge, and elliptical for free
|
|
125
175
|
on a non-square quad. It is the one to reach for instead of baking a disc into
|
|
@@ -178,9 +228,8 @@ app.viewport.addPostEffect(
|
|
|
178
228
|
);
|
|
179
229
|
```
|
|
180
230
|
|
|
181
|
-
Its
|
|
182
|
-
|
|
183
|
-
when applying it to something smaller than the frame.
|
|
231
|
+
Its `radius` is in texels of what it filters, the screen on a camera, and it
|
|
232
|
+
follows a resize by itself.
|
|
184
233
|
|
|
185
234
|
**`ToneMappingEffect` goes after it, and goes last.** Bloom spreads the bright
|
|
186
235
|
parts; the tone map is what stops the sum of the two clipping flat at white,
|
|
@@ -460,6 +509,38 @@ loader.preload([{ name: "ramp", type: "shader", data: myFragmentBody }]);
|
|
|
460
509
|
label.addPostEffect(loader.getShader("ramp"));
|
|
461
510
|
```
|
|
462
511
|
|
|
512
|
+
### Sprites sharing one effect share one draw
|
|
513
|
+
|
|
514
|
+
Sprites drawn one after the other through the **same** effect instance are one
|
|
515
|
+
draw call, as plain sprites are, even when they come from different frames of
|
|
516
|
+
an atlas: a hundred enemies outlined by one shared `OutlineEffect` draw once.
|
|
517
|
+
Each still draws with the values it was given: a uniform set to a **new**
|
|
518
|
+
value draws the sprites queued before it first (setting the value it already
|
|
519
|
+
has changes nothing). What splits the batch:
|
|
520
|
+
|
|
521
|
+
- a uniform taking a new value between two sprites: give a few sprites the
|
|
522
|
+
same value one after the other, rather than a different one each;
|
|
523
|
+
- a different texture (the effect path samples one texture per draw);
|
|
524
|
+
- a different effect instance, or a sprite without the effect in between;
|
|
525
|
+
- a `noise_uv` effect on sprites of different frames or sizes (its rect is
|
|
526
|
+
per sprite);
|
|
527
|
+
- a `screen_texture` effect, which draws each sprite on its own, as each one
|
|
528
|
+
samples a capture of everything drawn before it.
|
|
529
|
+
|
|
530
|
+
So share one instance (the loader's `"shader"` type hands one back, see
|
|
531
|
+
above) rather than constructing an effect per sprite: *n* instances are *n*
|
|
532
|
+
draws as well as *n* programs. One you construct yourself and share needs
|
|
533
|
+
`effect.shared = true`, or the first renderable destroyed destroys it for the
|
|
534
|
+
others (the loader's are marked shared already):
|
|
535
|
+
|
|
536
|
+
```js
|
|
537
|
+
const outline = new OutlineEffect(renderer, { color: [1, 1, 1] });
|
|
538
|
+
outline.shared = true; // you destroy it, when no enemy is left
|
|
539
|
+
for (const enemy of enemies) {
|
|
540
|
+
enemy.addPostEffect(outline);
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
463
544
|
### Declare a level's effects with that level's assets
|
|
464
545
|
|
|
465
546
|
An effect the loader knows about can be prepared before anything draws it; an
|
|
@@ -650,6 +731,7 @@ same question after construction.
|
|
|
650
731
|
| ported shader renders upside down | a hand-bound `toFrameTexture()` capture — GL is bottom-up, WebGPU top-down |
|
|
651
732
|
| shadow/smear offset flips on some draws | vertical UV offset not multiplied by `uUVYDir` |
|
|
652
733
|
| frame rate collapses with many blended sprites | an *advanced* mode (overlay, darken, …) — each draw is a capture plus a composite |
|
|
734
|
+
| 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 |
|
|
653
735
|
| animated shader never moves | `setTime` fed milliseconds, or the shader declares no `uTime` |
|
|
654
736
|
| squash and stretch only bulges, the height never changes, under Matter | the adapter rewrites `currentTransform` every step; set the scale from the sprite's `update()` |
|
|
655
737
|
| `SquishEffect` squishes the whole screen instead of the sprite | it is chained with another effect, or on the camera; give the sprite it alone |
|
|
@@ -41,6 +41,34 @@ normal-mapped sprites.
|
|
|
41
41
|
returns immediately while its alpha is 0 — so with no ambient colour set there
|
|
42
42
|
is nothing to cut through and the lights just look like soft additive blobs.
|
|
43
43
|
|
|
44
|
+
## Soft edges: `softness`
|
|
45
|
+
|
|
46
|
+
A light's hole in the darkness is hard-edged by default: full light inside its
|
|
47
|
+
radius, full darkness outside. `light.softness` (0 to 1) fades it instead:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const torch = new Light2d(x, y, 220, 220, "#ffb46a", 0.4);
|
|
51
|
+
torch.softness = 0.8; // lit core of 20% of the radius, then fading out
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `1` thins the darkness straight from the centre to the edge, the same falloff
|
|
55
|
+
as the glow; in between, a fully lit core of `1 - softness` of the radius.
|
|
56
|
+
- Hard and soft lights mix in one scene. Where lights overlap, what is left of the
|
|
57
|
+
darkness is what each leaves, multiplied, so two half-lights make three
|
|
58
|
+
quarters light.
|
|
59
|
+
- The hole is the light's size. For an opening wider than the glow (a torch whose
|
|
60
|
+
light reaches further than its flame's halo), add a second, bigger light with
|
|
61
|
+
`illuminationOnly = true`: it cuts the darkness and draws nothing.
|
|
62
|
+
- Up to 32 lights in view are cut by one soft pass; lights outside the view do not
|
|
63
|
+
count. A scene with no soft light draws exactly as before.
|
|
64
|
+
- To keep a camera out of the darkness (a minimap showing the level lit),
|
|
65
|
+
override `drawLighting` on the stage and skip that camera.
|
|
66
|
+
- To keep one light out of a camera, put it, or a container it is in, on a
|
|
67
|
+
`visibilityLayer` the camera's `cullMask` leaves out: there it neither glows,
|
|
68
|
+
nor cuts the darkness, nor lights normal-mapped sprites (see
|
|
69
|
+
`melonjs-camera-and-drawing`). This is for a `Light2d`: a `Light3d` lights
|
|
70
|
+
the meshes of every camera.
|
|
71
|
+
|
|
44
72
|
## Multiple lights
|
|
45
73
|
|
|
46
74
|
Add as many as you need — the glow quads and the ambient cutouts accumulate with
|
|
@@ -131,8 +159,9 @@ Unlit areas of a normal-mapped sprite render pure black unless you raise
|
|
|
131
159
|
`Stage.ambientLightingColor` (default black) — that is the base level added to
|
|
132
160
|
every lit pixel, and it is a different knob from `Stage.ambientLight`.
|
|
133
161
|
|
|
134
|
-
Without a normal map a sprite is
|
|
135
|
-
|
|
162
|
+
Without a normal map a sprite is not shaded at all: it takes the unlit path on
|
|
163
|
+
every backend, and a light changes how it looks only through its additive glow
|
|
164
|
+
and its cutout in the ambient darkness.
|
|
136
165
|
|
|
137
166
|
## What needs a GPU backend, and what does not
|
|
138
167
|
|
|
@@ -140,8 +169,9 @@ Less than you would expect. `Light2d` is renderer-agnostic: it just calls
|
|
|
140
169
|
`renderer.drawLight(this)`, and every backend implements it — the GPU backends
|
|
141
170
|
through a shared procedural radial-falloff shader, the Canvas renderer by
|
|
142
171
|
rasterising a cached `Gradient` into one shared render target and compositing it
|
|
143
|
-
with `drawImage`. The ambient cutout pass is plain `setMask` + `fillRect
|
|
144
|
-
|
|
172
|
+
with `drawImage`. The hard ambient cutout pass is plain `setMask` + `fillRect`;
|
|
173
|
+
the soft one is one shader pass on the GPU backends and an offscreen canvas on
|
|
174
|
+
Canvas. So the lit-spot look, soft or hard, works everywhere, Canvas included.
|
|
145
175
|
|
|
146
176
|
What Canvas cannot do is **per-pixel normal-map lighting**:
|
|
147
177
|
`renderer.setLightUniforms()` is a no-op there, and the first frame that passes a
|
|
@@ -171,7 +201,11 @@ See the `melonjs-3d` skill.
|
|
|
171
201
|
| symptom | cause |
|
|
172
202
|
|---|---|
|
|
173
203
|
| lights added but the scene never darkens | no ambient colour set — `ambientLight` alpha is 0, so the pass returns early |
|
|
174
|
-
|
|
|
204
|
+
| the lit area has a hard rim | `softness` is 0 (the default): set it on the light |
|
|
205
|
+
| the darkness opens no wider than the glow | the hole is the light's size: add a bigger `illuminationOnly` light for the opening |
|
|
206
|
+
| a minimap or second camera is dark too | every camera draws the stage's darkness: override `drawLighting` and skip that camera |
|
|
207
|
+
| a light should show in one camera only | put it on a `visibilityLayer` the other camera's `cullMask` leaves out |
|
|
208
|
+
| sprite not shaded by a light, only glowed over | no `normalMap` on the sprite, or the Canvas backend is active |
|
|
175
209
|
| normal-mapped sprite is pure black where unlit | `Stage.ambientLightingColor` left at black |
|
|
176
210
|
| the 33rd light shades nothing | `MAX_LIGHTS = 32` for the normal-map pipeline |
|
|
177
211
|
| `TypeError` assigning `normalMap` | not an image-like value (a video element is rejected outright) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-math
|
|
3
|
-
description: "Use this skill for vector and matrix maths in melonJS — Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z., moveTowards ignores z, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix."
|
|
3
|
+
description: "Use this skill for vector and matrix maths in melonJS — Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z., moveTowards ignores z, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix, Noise, getNoise1d, flicker, wobble."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -291,6 +291,25 @@ math.pow(n); // n squared
|
|
|
291
291
|
math.DEG_TO_RAD math.RAD_TO_DEG math.TAU math.ETA math.EPSILON
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
+
### Values that wobble over time
|
|
295
|
+
|
|
296
|
+
`Noise`, the generator behind `NoiseTexture2d`, also samples directly. For a
|
|
297
|
+
value that should drift without repeating (a torch's flicker, a sway, a camera
|
|
298
|
+
wander), sample it with time:
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
import { Noise } from "melonjs";
|
|
302
|
+
|
|
303
|
+
const noise = new Noise({ seed: 7, frequency: 1.5, octaves: 3 });
|
|
304
|
+
// around 1; give each object its own offset so they do not move together
|
|
305
|
+
const flicker = 1 + 0.2 * noise.getNoise1d(seconds + offset);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`getNoise1d` returns `[-1, 1]` (with fractal octaves most values fall within
|
|
309
|
+
about ±0.6), `frequency` is in changes per unit of the input, and the same seed
|
|
310
|
+
gives the same field every run. A sum of sines repeats where people notice it,
|
|
311
|
+
and `Math.random()` every frame jitters instead of drifting.
|
|
312
|
+
|
|
294
313
|
## Symptoms
|
|
295
314
|
|
|
296
315
|
| symptom | cause |
|
|
@@ -305,6 +324,7 @@ math.DEG_TO_RAD math.RAD_TO_DEG math.TAU math.ETA math.EPSILON
|
|
|
305
324
|
| you built your own 16-number array | `Renderable.currentTransform` is already a `Matrix3d`; write its `val` |
|
|
306
325
|
| a value read back from a matrix differs | `val` is `Float32Array`; it holds float32 |
|
|
307
326
|
| easing differs at 120fps | per-frame `lerp`; use `damp` |
|
|
327
|
+
| a flicker or sway looks mechanical, or visibly repeats | a sum of sines or a per-frame `Math.random()`: sample `Noise#getNoise1d(time)` |
|
|
308
328
|
|
|
309
329
|
## Related skills
|
|
310
330
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-particles-and-trails
|
|
3
|
-
description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, 3D particle motion through elevation and minSpread/maxSpread in 20.8, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: Trail3d, ribbon, exhaust trail, speed streak, ski track, tyre mark, ribbonNormal, ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter."
|
|
3
|
+
description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, 3D particle motion through elevation and minSpread/maxSpread in 20.8, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: Trail3d, ribbon, exhaust trail, speed streak, ski track, tyre mark, ribbonNormal, ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter, tintOverLife, colour over life, color over life, flame, fire."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -60,7 +60,8 @@ pair it with `framewidth`/`frameheight` or it does nothing.
|
|
|
60
60
|
|
|
61
61
|
Note the variety is in SHAPE, not colour. `tint` belongs to the emitter, so
|
|
62
62
|
every particle from one emitter is tinted alike however many frames it picks
|
|
63
|
-
between.
|
|
63
|
+
between. For a colour that changes over each particle's life, use
|
|
64
|
+
`tintOverLife` (below) rather than drawing one frame per colour.
|
|
64
65
|
|
|
65
66
|
Frame dimensions must be positive integers that fit inside the image, and an
|
|
66
67
|
explicit `frames` sequence must be nonempty and contain valid integer indices.
|
|
@@ -69,6 +70,35 @@ Invalid configurations throw `RangeError` before changing an emitter during
|
|
|
69
70
|
only future particles, so ones already alive finish with their original image,
|
|
70
71
|
frame size and sequence.
|
|
71
72
|
|
|
73
|
+
## Colour over life: `tintOverLife`
|
|
74
|
+
|
|
75
|
+
`tintOverLife` is the list of tints a particle takes over its life: the first at
|
|
76
|
+
birth, the last at the end, evenly spaced and blended in between. Give as many
|
|
77
|
+
CSS colours as you like (repeat one to hold it longer); it replaces `tint`.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
// a flame: white hot, then orange, then deep red as it rises and dies
|
|
81
|
+
const flame = new ParticleEmitter(x, y, {
|
|
82
|
+
image: softDot, // a white texture takes the tints as they are
|
|
83
|
+
tintOverLife: ["#fff3b0", "#ffc455", "#ff8a26", "#e45414", "#a0280c"],
|
|
84
|
+
textureAdditive: true,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **Colour only.** Opacity is still the built-in fade over the particle's life;
|
|
89
|
+
the alpha of a colour in the list is ignored. On an additive emitter, a darker
|
|
90
|
+
colour is a dimmer particle, so fold a "fainter" step into the colour itself.
|
|
91
|
+
- **32 steps.** However many colours, the blend is taken in 32 steps, the same on
|
|
92
|
+
every renderer: a particle changes colour at most 32 times. That matters on
|
|
93
|
+
Canvas, which keeps a tinted copy of the texture for every colour it is asked
|
|
94
|
+
to draw: 32 at most per texture.
|
|
95
|
+
- **Author the texture white.** `tint` multiplies, so a coloured texture muddies
|
|
96
|
+
every step.
|
|
97
|
+
- It works alongside sprite-sheet `frames` (one picks the image, the other the
|
|
98
|
+
colour), and a list that is empty, not an array, or holds a non-string throws a
|
|
99
|
+
`TypeError` from the constructor or `reset()`, which then leaves the emitter as
|
|
100
|
+
it was. The list is copied: change it through `reset()`.
|
|
101
|
+
|
|
72
102
|
## Where particles are born
|
|
73
103
|
|
|
74
104
|
`width` and `height` are the spawn AREA, and both default to `1` — so by
|
|
@@ -290,6 +320,7 @@ answer when you want discrete puffs rather than a continuous ribbon.
|
|
|
290
320
|
| symptom | cause |
|
|
291
321
|
|---|---|
|
|
292
322
|
| trail looks flat under a `Camera3d` | `Trail` is 2D; use `Trail3d` |
|
|
323
|
+
| particles clutter a minimap or a second camera | every camera draws them: set the emitter's `visibilityLayer` to a layer that camera's `cullMask` leaves out (see `melonjs-camera-and-drawing`) |
|
|
293
324
|
| `Trail3d` draws nothing, with a console warning | it needs a `Camera3d`; the app's `cameraClass` is still the 2D default |
|
|
294
325
|
| `trail.tint` does nothing | `Trail` cannot be tinted at all; `Trail3d` can |
|
|
295
326
|
| assigning `maxPoints` throws | `Trail3d` sizes its geometry once; pass `length` to the constructor |
|
|
@@ -298,6 +329,9 @@ answer when you want discrete puffs rather than a continuous ribbon.
|
|
|
298
329
|
| the cloud follows a moving emitter | default `referenceSpace: "local"` — use `"world"` for trails |
|
|
299
330
|
| particles do not travel far enough | settings tuned pre-20.2, when drawn travel was doubled |
|
|
300
331
|
| one-shot emitters accumulate | missing `autoDestroyOnComplete: true` |
|
|
332
|
+
| every particle of an emitter is the same colour all its life | `tint` is one value for the emitter: use `tintOverLife` for a colour that changes as it ages |
|
|
333
|
+
| a `tintOverLife` colour's transparency has no effect | the list sets the colour only; opacity is the particle's fade |
|
|
334
|
+
| `tintOverLife` colours look muddy | the texture is coloured; author it white so the tints come through as given |
|
|
301
335
|
| frame rate drops with many effects | an emitter per event instead of one re-aimed emitter |
|
|
302
336
|
| a 3D burst is invisible under a `Camera3d` | before 20.8 a particle was culled at twice its depth; update |
|
|
303
337
|
| the whole burst launches from one pixel | `width`/`height` are the spawn area and default to `1` |
|
|
@@ -373,6 +373,7 @@ In rough order for a typical 2D game:
|
|
|
373
373
|
| a freeze the first time a level shows, never again | shader variants linking on first draw — warm them behind the loading screen |
|
|
374
374
|
| a freeze the first time an effect appears mid-play (a shield, a hit flash) | the effect attached in the same frame it is drawn — attach it when the pool is built, so the warm-up has a frame to finish |
|
|
375
375
|
| the same scene stutters once per entry | a derived texture rebuilt per stage: the texture cache keys on the image object, so each rebuild is a new upload |
|
|
376
|
+
| steady movement stutters while the frame rate reads 60 | not a cost: frame-time jitter gives a frame no update and the next two; set `syncToDisplay: true` (see `melonjs-display-and-scaling`) |
|
|
376
377
|
|
|
377
378
|
## Related skills
|
|
378
379
|
|