melonjs 20.6.0 → 20.8.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 +6 -3
- package/build/application/defaultApplicationSettings.d.ts +5 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/settings.d.ts +88 -0
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/backend/engine.d.ts +0 -1
- package/build/audio/backend/sound.d.ts +0 -1
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +95 -8
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/geometries/box3d.d.ts +24 -6
- package/build/geometries/box3d.d.ts.map +1 -1
- package/build/geometries/ellipse.d.ts +14 -0
- package/build/geometries/ellipse.d.ts.map +1 -1
- package/build/geometries/polygon.d.ts +14 -0
- package/build/geometries/polygon.d.ts.map +1 -1
- package/build/geometries/sphere.d.ts +35 -6
- package/build/geometries/sphere.d.ts.map +1 -1
- package/build/index.d.ts +10 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23361 -20195
- package/build/index.js.map +4 -4
- package/build/input/pointerevent.d.ts +8 -0
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/lang/deprecated.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts +17 -31
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/gltf/GLTFScene.d.ts +40 -1
- package/build/level/gltf/GLTFScene.d.ts.map +1 -1
- package/build/level/level.d.ts +36 -0
- package/build/level/level.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +5 -2
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXObject.d.ts +0 -1
- package/build/level/tiled/TMXTile.d.ts +2 -1
- package/build/level/tiled/TMXTile.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +7 -4
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts +1 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/TMXTilesetGroup.d.ts +5 -0
- package/build/level/tiled/TMXTilesetGroup.d.ts.map +1 -1
- package/build/level/tiled/factories/shape.d.ts +0 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +6 -5
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +5 -0
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +8 -7
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +5 -0
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +19 -4
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/autodetect.d.ts +0 -4
- package/build/level/tiled/renderer/autodetect.d.ts.map +1 -1
- package/build/lighting/light2d.d.ts +68 -2
- package/build/lighting/light2d.d.ts.map +1 -1
- package/build/lighting/light3d.d.ts.map +1 -1
- package/build/loader/loader.d.ts +63 -0
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/loadingscreen.d.ts +1 -2
- package/build/loader/loadingscreen.d.ts.map +1 -1
- package/build/loader/parsers/fontface.d.ts +2 -0
- package/build/loader/parsers/fontface.d.ts.map +1 -1
- package/build/loader/parsers/mtl.d.ts.map +1 -1
- package/build/loader/parsers/shader.d.ts +0 -2
- package/build/math/color.d.ts +27 -4
- package/build/math/color.d.ts.map +1 -1
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +22 -1
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +69 -0
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +152 -3
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +9 -18
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts +0 -1
- package/build/physics/builtin/detector.d.ts.map +1 -1
- package/build/physics/builtin/sat.d.ts +0 -7
- package/build/physics/builtin/sat.d.ts.map +1 -1
- package/build/physics/builtin/sat3d.d.ts +46 -0
- package/build/physics/builtin/sat3d.d.ts.map +1 -1
- package/build/physics/physicseditor.d.ts +2 -0
- package/build/physics/physicseditor.d.ts.map +1 -0
- package/build/physics/response.d.ts +45 -62
- package/build/physics/response.d.ts.map +1 -1
- package/build/renderable/collectable.d.ts +6 -1
- package/build/renderable/collectable.d.ts.map +1 -1
- package/build/renderable/colorlayer.d.ts +4 -4
- package/build/renderable/colorlayer.d.ts.map +1 -1
- package/build/renderable/container.d.ts +23 -17
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/entity/entity.d.ts +14 -16
- package/build/renderable/entity/entity.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts +1 -1
- package/build/renderable/groundshadow.d.ts +1 -0
- package/build/renderable/groundshadow.d.ts.map +1 -1
- package/build/renderable/imagelayer.d.ts +12 -5
- package/build/renderable/imagelayer.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +2 -9
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +306 -5
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/nineslicesprite.d.ts +3 -13
- package/build/renderable/nineslicesprite.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +129 -28
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +77 -19
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/sprite3d.d.ts +27 -16
- package/build/renderable/sprite3d.d.ts.map +1 -1
- package/build/renderable/text/bitmaptext.d.ts +13 -3
- package/build/renderable/text/bitmaptext.d.ts.map +1 -1
- package/build/renderable/text/bitmaptextdata.d.ts +0 -4
- package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
- package/build/renderable/text/text.d.ts +5 -3
- package/build/renderable/text/text.d.ts.map +1 -1
- package/build/renderable/text/textmetrics.d.ts +2 -0
- package/build/renderable/text/textmetrics.d.ts.map +1 -1
- package/build/renderable/trail.d.ts +0 -1
- package/build/renderable/trail.d.ts.map +1 -1
- package/build/renderable/trigger.d.ts +39 -14
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/progressbar.d.ts +194 -0
- package/build/renderable/ui/progressbar.d.ts.map +1 -0
- package/build/renderable/ui/uibaseelement.d.ts +48 -3
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uispriteelement.d.ts +19 -3
- package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
- package/build/state/stage.d.ts +22 -0
- package/build/state/stage.d.ts.map +1 -1
- package/build/system/legacy_pool.d.ts +2 -2
- package/build/system/legacy_pool.d.ts.map +1 -1
- package/build/system/pool.d.ts +10 -0
- package/build/system/pool.d.ts.map +1 -1
- package/build/tweens/tween.d.ts +49 -2
- package/build/tweens/tween.d.ts.map +1 -1
- package/build/video/buffer/vertex.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +34 -23
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/blendEffect.d.ts +0 -1
- package/build/video/effects/bloom.d.ts +72 -0
- package/build/video/effects/bloom.d.ts.map +1 -0
- 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/colorMatrix.d.ts +3 -2
- package/build/video/effects/colorMatrix.d.ts.map +1 -1
- package/build/video/effects/desaturate.d.ts +10 -2
- package/build/video/effects/desaturate.d.ts.map +1 -1
- package/build/video/effects/dissolve.d.ts +3 -2
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +3 -2
- package/build/video/effects/dropShadow.d.ts.map +1 -1
- package/build/video/effects/flash.d.ts +3 -2
- package/build/video/effects/flash.d.ts.map +1 -1
- package/build/video/effects/glow.d.ts +3 -2
- package/build/video/effects/glow.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts +3 -2
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/invert.d.ts +10 -2
- package/build/video/effects/invert.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +3 -2
- 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 +8 -3
- package/build/video/effects/radialGradient.d.ts.map +1 -1
- package/build/video/effects/scanline.d.ts +3 -2
- package/build/video/effects/scanline.d.ts.map +1 -1
- package/build/video/effects/sepia.d.ts +10 -2
- package/build/video/effects/sepia.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +10 -4
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +3 -2
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/tintPulse.d.ts +3 -2
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/tonemap.d.ts +121 -0
- package/build/video/effects/tonemap.d.ts.map +1 -0
- package/build/video/effects/vignette.d.ts +3 -2
- package/build/video/effects/vignette.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +3 -2
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/gpu/meshmaterial.d.ts +2 -0
- package/build/video/gpu/meshmaterial.d.ts.map +1 -0
- package/build/video/gpu/vertexformat.d.ts +26 -0
- package/build/video/gpu/vertexformat.d.ts.map +1 -1
- package/build/video/gradient.d.ts +0 -1
- package/build/video/gradient.d.ts.map +1 -1
- package/build/video/renderer.d.ts +111 -7
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/renderstate.d.ts +2 -0
- package/build/video/renderstate.d.ts.map +1 -1
- package/build/video/rendertarget/canvasrendertarget.d.ts +3 -4
- package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/render_target_pool.d.ts +0 -1
- package/build/video/rendertarget/webglrendertarget.d.ts +0 -1
- package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/webgpurendertarget.d.ts +18 -2
- package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +1 -0
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/texture/cache.d.ts +1 -3
- package/build/video/texture/frametexture.d.ts +0 -1
- package/build/video/texture/frametexture.d.ts.map +1 -1
- package/build/video/texture/noise_texture2d.d.ts +1 -0
- package/build/video/texture/noise_texture2d.d.ts.map +1 -1
- package/build/video/utils/autodetect.d.ts +0 -2
- package/build/video/webgl/batchers/batcher.d.ts +0 -9
- package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts +13 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/material_batcher.d.ts +4 -2
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/mesh_batcher.d.ts +2 -3
- package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts +4 -0
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/buffer/index.d.ts +0 -1
- package/build/video/webgl/buffer/retained_geometry.d.ts +0 -1
- package/build/video/webgl/glshader.d.ts +5 -3
- package/build/video/webgl/glshader.d.ts.map +1 -1
- package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts +0 -4
- package/build/video/webgl/shaders/multitexture-lit.d.ts.map +1 -1
- package/build/video/webgl/texture/store.d.ts +0 -1
- package/build/video/webgl/webgl_renderer.d.ts +63 -24
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts +17 -0
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -3
- package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts +0 -2
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/webgpu_batcher.d.ts +0 -2
- package/build/video/webgpu/batchers/webgpu_batcher.d.ts.map +1 -1
- package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
- package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts +0 -2
- package/build/video/webgpu/texture/frametexture.d.ts +0 -2
- package/build/video/webgpu/texture/frametexture.d.ts.map +1 -1
- package/build/video/webgpu/texture/store.d.ts +0 -1
- package/build/video/webgpu/texture/store.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +8 -75
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs-3d/SKILL.md +293 -19
- package/skills/melonjs-3d-assets/SKILL.md +123 -13
- package/skills/melonjs-camera-and-drawing/SKILL.md +57 -1
- package/skills/melonjs-effects-and-shaders/SKILL.md +341 -29
- package/skills/melonjs-input/SKILL.md +48 -4
- package/skills/melonjs-lighting/SKILL.md +45 -1
- package/skills/melonjs-loading-assets/SKILL.md +1 -1
- package/skills/melonjs-particles-and-trails/SKILL.md +106 -4
- package/skills/melonjs-performance/SKILL.md +79 -2
- package/skills/melonjs-physics/SKILL.md +420 -29
- package/skills/melonjs-renderables/SKILL.md +15 -1
- package/skills/melonjs-scenes-and-state/SKILL.md +121 -1
- package/skills/melonjs-tilemaps/SKILL.md +9 -0
- package/skills/melonjs-ui-and-text/SKILL.md +165 -4
- package/build/level/gltf/srgb.d.ts +0 -20
- package/build/level/gltf/srgb.d.ts.map +0 -1
|
@@ -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, and baking with CanvasRenderTarget. Triggers on: Camera2d, viewport, follow, setBounds, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d."
|
|
3
|
+
description: "Use this skill for camera control and immediate-mode drawing in melonJS — following a target, viewport bounds, shake and fade, secondary cameras, and drawing shapes, lines and gradients inside a custom draw(). Covers world versus screen coordinate conversion, clipping and masking, and baking with CanvasRenderTarget. Triggers on: Camera2d, viewport, follow, setBounds, shake, fadeIn, fadeOut, worldToLocal, localToWorld, colorMatrix, renderer.fill, renderer.stroke, Rect, Ellipse, Polygon, Line, Gradient, clipRect, mask, CanvasRenderTarget, NoiseTexture2d, lookAt, setBasis."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,6 +24,25 @@ 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
|
+
**For anything that is NOT the camera, the same smoothing is `math.damp`.**
|
|
28
|
+
`follow()` is frame-rate independent for you; a value you smooth yourself is
|
|
29
|
+
not, and `lerp(current, target, alpha)` once per frame moves further on a slow
|
|
30
|
+
frame than on a fast one, so the feel changes with the frame rate:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { math } from "melonjs";
|
|
34
|
+
|
|
35
|
+
// dt is milliseconds, damp wants seconds
|
|
36
|
+
this.speed = math.damp(this.speed, targetSpeed, 5, dt / 1000);
|
|
37
|
+
this.turret.angle = math.damp(this.turret.angle, wanted, 8, dt / 1000);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`damp(current, target, lambda, dt)`. Higher `lambda` converges faster; it is
|
|
41
|
+
the rate, not a per-frame fraction. Use it for camera pans you drive yourself,
|
|
42
|
+
turret tracking, zoom, input smoothing and any value that should chase another
|
|
43
|
+
one — it is the tool that is easy to miss and easy to hand-write slightly
|
|
44
|
+
wrong.
|
|
45
|
+
|
|
27
46
|
## Bounds are the thing people forget
|
|
28
47
|
|
|
29
48
|
The camera's bounds default to the **renderer size**, so it cannot scroll until
|
|
@@ -102,6 +121,24 @@ coordinate that puts your label on the wrong side of the screen. Skip it.
|
|
|
102
121
|
Do not hand-roll this with `Matrix3d` — the perspective divide and the behind-
|
|
103
122
|
camera case are exactly what gets it wrong.
|
|
104
123
|
|
|
124
|
+
## Pointing a 3D camera
|
|
125
|
+
|
|
126
|
+
A 2D camera's only rotation is `roll`. A 3D one can be pointed at a target, and
|
|
127
|
+
optionally banked at the same time:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
camera.lookAt(target); // pitch and yaw; roll untouched
|
|
131
|
+
camera.lookAt(target, up); // and bank, where `up` is what should
|
|
132
|
+
// appear UP ON SCREEN
|
|
133
|
+
camera.setBasis(right, up, forward); // or pose it from a basis outright
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`up` has no default, so the one-argument form behaves exactly as it always
|
|
137
|
+
did. Because this engine renders Y-down, a level horizon is `(0, -1, 0)`.
|
|
138
|
+
`setBasis` is the writable counterpart of `getBasis` and takes the camera's
|
|
139
|
+
own columns, whose `up` points down the screen: the opposite sense, one
|
|
140
|
+
negation apart. See `melonjs-3d` for the full account.
|
|
141
|
+
|
|
105
142
|
## Secondary cameras
|
|
106
143
|
|
|
107
144
|
Cameras are first-class — a minimap is a second camera with `autoResize = false`
|
|
@@ -195,6 +232,24 @@ hand `.canvas` on for exactly that reason.
|
|
|
195
232
|
|
|
196
233
|
This is the sanctioned performance idiom before reaching for a custom shader.
|
|
197
234
|
|
|
235
|
+
**For a solid fill you do not need a canvas at all.** `Renderer.getWhitePixel()`
|
|
236
|
+
is a shared, lazily-created 1x1 white canvas, and stretching it is how you get
|
|
237
|
+
a coloured rectangle, a bar, a flat screen overlay, or a blank carrier for an
|
|
238
|
+
effect that paints from scratch:
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
const bar = new Sprite(0, 0, { image: Renderer.getWhitePixel() });
|
|
242
|
+
bar.anchorPoint.set(0, 0);
|
|
243
|
+
bar.tint.parseCSS("#39d0ff");
|
|
244
|
+
bar.scale(width, height); // a 1x1 texture, so scale IS the size in pixels
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
It is also the right texture under a `ShaderEffect` whose body ignores the
|
|
248
|
+
incoming colour, and it keeps the quad batched with everything else using it.
|
|
249
|
+
When you DO need a real canvas, `Renderer.createCanvas(width, height)` is the
|
|
250
|
+
engine's allocator: it hands back an `OffscreenCanvas` where that is safe and
|
|
251
|
+
throws on a zero dimension, instead of `document.createElement("canvas")`.
|
|
252
|
+
|
|
198
253
|
`renderer.toFrameTexture()` is the related tool for capturing the *current frame*
|
|
199
254
|
into a texture for a shader to sample.
|
|
200
255
|
|
|
@@ -213,6 +268,7 @@ into a texture for a shader to sample.
|
|
|
213
268
|
| `container.clipping = true` does nothing | container has no explicit size, so `width`/`height` are `Infinity` |
|
|
214
269
|
| baked target draws nothing / throws | passed the `CanvasRenderTarget`, not its `.canvas` |
|
|
215
270
|
| frame rate drops with many static draws | bake into a `CanvasRenderTarget` instead |
|
|
271
|
+
| a 3D camera cannot be given an arbitrary up | `Camera3d#lookAt(target, up)` or `setBasis(right, up, forward)`; see `melonjs-3d` |
|
|
216
272
|
|
|
217
273
|
## Related skills
|
|
218
274
|
|
|
@@ -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: ShaderEffect, addPostEffect, removePostEffect, getPostEffect, 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, shader, GLSL, WGSL, uniform, setUniform, setTexture, setTime, blendMode, colorMatrix, screen_texture, toFrameTexture, post effect, filter."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -41,7 +41,7 @@ quad.scale(viewW / anyImage.width, viewH / anyImage.height);
|
|
|
41
41
|
quad.floating = true; // its own uv is now screen space
|
|
42
42
|
quad.blendMode = "additive";
|
|
43
43
|
quad.addPostEffect(myEffect);
|
|
44
|
-
world.addChild(quad, HUD_Z - 10); //
|
|
44
|
+
world.addChild(quad, HUD_Z - 10); // 2D: below the labels. See the note.
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Two things follow from the quad filling the frame: its own `uv` is screen space
|
|
@@ -49,9 +49,47 @@ Two things follow from the quad filling the frame: its own `uv` is screen space
|
|
|
49
49
|
and the incoming `color` is the quad's own texture, which a body that paints
|
|
50
50
|
from scratch can ignore entirely.
|
|
51
51
|
|
|
52
|
-
Depth here is easy to get backwards
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
**Depth here is easy to get backwards, and the rule FLIPS between the two sort
|
|
53
|
+
modes.** Under the default `sortOn = "z"` a higher z draws later, i.e. on top,
|
|
54
|
+
so `HUD_Z - 10` puts the pass under the labels as written above. Under
|
|
55
|
+
`sortOn = "depth"`, which every `Camera3d` scene sets, a floating child's sort
|
|
56
|
+
key is `pos.z * pos.z` and the walk paints far to near, so the SMALLER
|
|
57
|
+
magnitude is nearer and draws on top — `HUD_Z - 10` there puts the pass OVER
|
|
58
|
+
the labels, the opposite of the comment.
|
|
59
|
+
|
|
60
|
+
So in a 3D scene give the overlay a larger magnitude than the HUD, and give
|
|
61
|
+
the HUD a small one (`melonjs-3d` and `melonjs-ui-and-text` both say a HUD
|
|
62
|
+
wants a small depth, not the huge z that would put it on top in 2D).
|
|
63
|
+
|
|
64
|
+
Either way, verify it rather than reasoning about it: return a flat colour from
|
|
65
|
+
the body for one frame and see what it tints.
|
|
66
|
+
|
|
67
|
+
## A renderable that draws with PRIMITIVES has to ask to be captured
|
|
68
|
+
|
|
69
|
+
One effect is applied the cheap way: the renderable is drawn with the effect's
|
|
70
|
+
own program instead of being captured offscreen and post-processed, which costs
|
|
71
|
+
no render target. That is equivalent only when everything the renderable draws
|
|
72
|
+
is a textured quad. `fillRect`, `strokeRect` and `renderer.fill(shape)` go
|
|
73
|
+
through a batcher that never reads that shader, so **one** effect on such a
|
|
74
|
+
renderable would silently do nothing, while **two** would work, because a chain
|
|
75
|
+
always captures.
|
|
76
|
+
|
|
77
|
+
So a renderable of your own that draws with primitives says so:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
class Bar extends Renderable {
|
|
81
|
+
constructor(x, y, w, h) {
|
|
82
|
+
super(x, y, w, h);
|
|
83
|
+
this.postEffectNeedsCapture = true; // ← or one effect is a no-op
|
|
84
|
+
}
|
|
85
|
+
draw(renderer) {
|
|
86
|
+
renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`ProgressBar` and `Trail` set it for you, and a sprite needs nothing: the flag
|
|
92
|
+
defaults to `false` and only ever changes the single-effect case.
|
|
55
93
|
|
|
56
94
|
## Toggle with `enabled`, do not remove
|
|
57
95
|
|
|
@@ -72,13 +110,257 @@ reused across renderables.
|
|
|
72
110
|
|
|
73
111
|
## Built-in effects
|
|
74
112
|
|
|
75
|
-
|
|
113
|
+
Twenty-one presets, every one constructed as `new XEffect(renderer, options)` and
|
|
76
114
|
every one shipping both a GLSL and a WGSL body, so they all run on either GPU
|
|
77
115
|
backend: `VignetteEffect`, `ScanlineEffect` (optional CRT curvature),
|
|
78
|
-
`GlowEffect`, `BlurEffect`, `PixelateEffect`,
|
|
79
|
-
`
|
|
80
|
-
`
|
|
81
|
-
`
|
|
116
|
+
`BloomEffect`, `GlowEffect`, `BlurEffect`, `PixelateEffect`,
|
|
117
|
+
`ChromaticAberrationEffect`, `DropShadowEffect`, `FlashEffect`,
|
|
118
|
+
`OutlineEffect`, `ColorMatrixEffect`, `DesaturateEffect`, `DissolveEffect`,
|
|
119
|
+
`TintPulseEffect`, `WaveEffect`, `InvertEffect`, `SepiaEffect`,
|
|
120
|
+
`HologramEffect`, `ShineEffect`, `RadialGradientEffect`, `ToneMappingEffect`.
|
|
121
|
+
|
|
122
|
+
`RadialGradientEffect` is a procedural soft spot: solid at the centre of its
|
|
123
|
+
host quad, fading linearly to transparent at the edge, and elliptical for free
|
|
124
|
+
on a non-square quad. It is the one to reach for instead of baking a disc into
|
|
125
|
+
a canvas, for a pickup highlight, a trigger marker or a damage flash. Its
|
|
126
|
+
falloff assumes the quad samples a FULL texture rect, so pair it with a
|
|
127
|
+
standalone image or `Renderer.getWhitePixel()` rather than a sprite that draws
|
|
128
|
+
a sub-region of an atlas, where the centre would land off-quad.
|
|
129
|
+
|
|
130
|
+
**`BloomEffect` and `GlowEffect` are not the same thing**, and reaching for the
|
|
131
|
+
wrong one is the usual disappointment here. `GlowEffect` draws a halo OUTSIDE a
|
|
132
|
+
sprite's silhouette: its body returns the colour untouched wherever alpha is
|
|
133
|
+
already non-zero, so on an opaque frame it returns early on every fragment and
|
|
134
|
+
a camera-wide glow does nothing at all. `BloomEffect` is the screen bloom —
|
|
135
|
+
bright pixels bleed into their neighbours — and it is the one to put on the
|
|
136
|
+
camera:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
game.viewport.addPostEffect(new BloomEffect(app.renderer));
|
|
140
|
+
// only the brightest highlights, with a wider halo
|
|
141
|
+
game.viewport.addPostEffect(
|
|
142
|
+
new BloomEffect(app.renderer, { threshold: 0.85, radius: 4 }),
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Its tap offsets are in texels, so it takes its size from the renderer at
|
|
147
|
+
construction; call `setTextureSize(w, h)` from an `event.ONRESIZE` handler, or
|
|
148
|
+
when applying it to something smaller than the frame.
|
|
149
|
+
|
|
150
|
+
**`ToneMappingEffect` goes after it, and goes last.** Bloom spreads the bright
|
|
151
|
+
parts; the tone map is what stops the sum of the two clipping flat at white,
|
|
152
|
+
which is the difference between an effect that reads as light and one that
|
|
153
|
+
reads as a white sticker. `exposure` lifts the image into the curve, and the
|
|
154
|
+
curve (`aces`, `reinhard` or `exponential`) is baked at construction:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
game.viewport.addPostEffect(new BloomEffect(app.renderer));
|
|
158
|
+
game.viewport.addPostEffect(
|
|
159
|
+
new ToneMappingEffect(app.renderer, { exposure: 1.6 }),
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
It is a grade, not a colour pipeline. By default melonJS renders into `RGBA8`
|
|
164
|
+
everywhere, so the frame reaching it is already clamped and `exposure` past 1
|
|
165
|
+
is what gives the curve range to compress.
|
|
166
|
+
|
|
167
|
+
**`hdr: true` in the application settings changes that**: render targets
|
|
168
|
+
become half-float, so the chain carries values above 1 instead of clamping at
|
|
169
|
+
every write, and the bloom threshold starts discriminating genuinely
|
|
170
|
+
over-bright pixels rather than merely bright ones. Off by default: it costs
|
|
171
|
+
GPU memory on the camera's two render targets and depends on the driver.
|
|
172
|
+
|
|
173
|
+
On its own it is **not HDR output**: the frame is still presented in SDR, so
|
|
174
|
+
anything above 1 clamps on the final blit. What it buys is that nothing clamped
|
|
175
|
+
BEFORE then. Removing that last clamp is a second, separate opt-in —
|
|
176
|
+
`hdrOutput`, below — and it is WebGPU-only.
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
const app = new Application(1024, 576, { hdr: true, … });
|
|
180
|
+
// …and check what you actually got, since a driver can refuse it
|
|
181
|
+
if (app.renderer.supportsHDR) { /* the chain now carries values above 1 */ }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Branch the GRADE on that flag, not just the feature. An exposure under 1 and a
|
|
185
|
+
bloom threshold at 1 make sense only where values above 1 exist; applied to a
|
|
186
|
+
chain that clamps, the same numbers tone map an already display-referred frame
|
|
187
|
+
and switch the bloom off, which reads as a dark, flat picture rather than as a
|
|
188
|
+
missing feature. Where there is no headroom, squeeze each emitter into range in
|
|
189
|
+
its own shader instead and put the bloom threshold back under 1.
|
|
190
|
+
|
|
191
|
+
**Nothing is added to your chain.** `hdr` is additive: the same frame, plus
|
|
192
|
+
additive content that no longer saturates per write and a threshold that means
|
|
193
|
+
something. Anything still above 1 at the end clamps on the blit, as it always
|
|
194
|
+
did.
|
|
195
|
+
|
|
196
|
+
A tone map is therefore a decision, not a consequence, and it is a bigger one
|
|
197
|
+
than it looks. Every curve maps 1 to less than 1, so a 2D look built ON the
|
|
198
|
+
clamp — clipped white specular, saturated neon — goes matte the moment you add
|
|
199
|
+
one, at any exposure. Add one when your art is authored in linear light and you
|
|
200
|
+
want the highlights rolled off; skip it when the clipping IS the look.
|
|
201
|
+
|
|
202
|
+
It applies to the CAMERA chain only. A sprite's chain keeps 8-bit targets on
|
|
203
|
+
purpose: its final blit composites with `ONE_MINUS_SRC_ALPHA`, and additive
|
|
204
|
+
blending accumulates alpha past 1 in a float target, so that factor would go
|
|
205
|
+
negative and subtract the backdrop.
|
|
206
|
+
|
|
207
|
+
### Once it is on: putting values above 1 into the frame
|
|
208
|
+
|
|
209
|
+
`hdr: true` on its own changes nothing visible. It removes a ceiling; something
|
|
210
|
+
still has to reach for it. No single draw exceeds 1 on its own, so a game that
|
|
211
|
+
turns the setting on and changes nothing else renders very nearly the frame it
|
|
212
|
+
did before and looks like the feature is broken. The exception is worth knowing
|
|
213
|
+
because it is free: additive content that already overlapped was being clipped
|
|
214
|
+
at every write, and stops being.
|
|
215
|
+
|
|
216
|
+
What produces over-bright pixels:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
// a built-in colour matrix, scaled past 1 — no custom shader needed
|
|
220
|
+
sprite.addPostEffect(new ColorMatrixEffect(app.renderer).brightness(4));
|
|
221
|
+
|
|
222
|
+
// in 3D, both of these were always unbounded; the 8-bit target was the only
|
|
223
|
+
// thing destroying the range. `emissive` is a 3-component Float32Array, set
|
|
224
|
+
// from the mesh settings or replaced wholesale — it is not a Color
|
|
225
|
+
const mesh = new Mesh(x, y, { emissive: [2.5, 1.8, 0.4], /* … */ });
|
|
226
|
+
light.intensity = 3;
|
|
227
|
+
|
|
228
|
+
// and additive blending, which accumulates past 1 once the target can hold it
|
|
229
|
+
sprite.blendMode = "additive";
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**One effect, not two.** A renderable carrying exactly ONE post effect takes
|
|
233
|
+
the renderer's `customShader` fast path: no offscreen target, and it draws
|
|
234
|
+
straight into the camera's half-float target with its range intact. A chain of
|
|
235
|
+
two or more is routed through a pooled intermediate that stays 8 bits per
|
|
236
|
+
channel on purpose, so the energy is clamped there, before the camera ever sees
|
|
237
|
+
it. Adding a second effect to an emitter is enough to silently undo its
|
|
238
|
+
headroom.
|
|
239
|
+
|
|
240
|
+
### Everything inside the camera is graded, including your UI
|
|
241
|
+
|
|
242
|
+
The tone curve runs on the camera's whole frame. A `floating` HUD is still
|
|
243
|
+
inside it, so a label authored at display white arrives at the curve as 1 and
|
|
244
|
+
leaves at `1 - exp(-exposure)` — at exposure 0.45 that is 0.36, and the HUD
|
|
245
|
+
comes out at roughly a third of its brightness for no reason the author can
|
|
246
|
+
see.
|
|
247
|
+
|
|
248
|
+
UI in an HDR chain has to be authored for that chain, exactly as the scene is:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
// drive the readout so the curve lands it back at display white. `shared`
|
|
252
|
+
// keeps one instance across every element: it opts the effect out of the
|
|
253
|
+
// per-renderable auto-destroy, so the first element torn down does not take
|
|
254
|
+
// the program the others are still using
|
|
255
|
+
const energy = new ColorMatrixEffect(app.renderer).brightness(2);
|
|
256
|
+
energy.shared = true;
|
|
257
|
+
for (const element of hudElements) element.addPostEffect(energy);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Put it on each ELEMENT, not on their container: `beginPostEffect` clears
|
|
261
|
+
`customShader` for any renderable with no effects of its own, so a container's
|
|
262
|
+
shader is thrown away by its first child. The alternative is to keep the UI out
|
|
263
|
+
of the camera pass entirely, by overriding `Stage.draw` and drawing it after
|
|
264
|
+
`super.draw(...)` — at the cost of the bloom that makes additive UI glow.
|
|
265
|
+
|
|
266
|
+
The quad trick under *A camera effect covers the HUD too* does not help here.
|
|
267
|
+
It works by hosting a pass BELOW the HUD's z, and a tone map cannot live there:
|
|
268
|
+
it has to be the last thing before the blit to the 8-bit canvas, which is the
|
|
269
|
+
camera. Anything the camera draws goes through it.
|
|
270
|
+
|
|
271
|
+
Pick the multiplier by measuring against the no-headroom build, not by
|
|
272
|
+
inverting the curve. Inverting it over-estimates, because additive UI is inside
|
|
273
|
+
the BLOOM too and driving it harder also makes it spill wider into the same
|
|
274
|
+
pixels.
|
|
275
|
+
|
|
276
|
+
### `hdrOutput`: actually presenting in HDR, and what it costs you
|
|
277
|
+
|
|
278
|
+
`hdr` gives the chain headroom and then throws it away at the very last step,
|
|
279
|
+
because the surface the browser presents into is 8 bits per channel. Values
|
|
280
|
+
above 1 clamp there, exactly as they did before the setting existed.
|
|
281
|
+
`hdrOutput` changes that surface, so they reach the compositor instead:
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
const app = new Application(1024, 576, { hdr: true, hdrOutput: true, … });
|
|
285
|
+
if (app.renderer.supportsHDROutput) { /* the display is getting values above 1 */ }
|
|
286
|
+
|
|
287
|
+
app.renderer.setHDROutput(false); // and at runtime, for a settings screen
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**It is WebGPU-only, and that is not a melonJS limitation.** The WebGPU
|
|
291
|
+
specification defines a tone mapping mode on the canvas configuration, and two
|
|
292
|
+
engines ship it. The WebGL2 equivalent needs a drawing-buffer tone mapping mode
|
|
293
|
+
that is still an unapproved specification change with no implementation in any
|
|
294
|
+
browser; the colour-space half of it exists in Chrome behind
|
|
295
|
+
`--enable-experimental-web-platform-features`, which is not something you can
|
|
296
|
+
ask a player to enable. So the WebGL2 backend reports `false`, warns once if
|
|
297
|
+
you asked, and presents in SDR as it always has.
|
|
298
|
+
|
|
299
|
+
That makes this the first setting where **the same machine answers differently
|
|
300
|
+
depending on which backend `video.AUTO` picked**. Branch on
|
|
301
|
+
`renderer.supportsHDROutput`, never on the setting.
|
|
302
|
+
|
|
303
|
+
**It requires `hdr`** — the last pass blits from the camera's target, so if
|
|
304
|
+
that target is 8 bits there is nothing above 1 left to present and the extra
|
|
305
|
+
cost would buy an identical picture. Turning `hdr` off turns this off with it.
|
|
306
|
+
|
|
307
|
+
**It changes how your game looks, on an HDR display.** Everything else here is
|
|
308
|
+
careful not to: `hdr` is additive, the tone curve defaults to none. This one
|
|
309
|
+
removes the final clamp, so anything you drove above 1 — an additive stack, an
|
|
310
|
+
emitter, a `brightness()` above 1 — gets genuinely brighter rather than
|
|
311
|
+
saturating at white. That is the point of it, and it is why it is a separate
|
|
312
|
+
opt-in rather than part of `hdr`. Two consequences worth planning for:
|
|
313
|
+
|
|
314
|
+
- a look built on clipped whites will not survive it unchanged, because the
|
|
315
|
+
clipping is what you were art-directing against
|
|
316
|
+
- a tone curve and `hdrOutput` pull in opposite directions. A curve exists to
|
|
317
|
+
fit values into `[0, 1]`; if you have removed the clamp, either drop the
|
|
318
|
+
curve or give it a white point high enough to leave the top of the range
|
|
319
|
+
alone
|
|
320
|
+
- **`"aces"` cannot produce HDR output.** Its expression clamps to `[0, 1]`
|
|
321
|
+
internally, so everything past the shoulder collapses to one value before
|
|
322
|
+
the white point scales it — different bright things come out equally bright.
|
|
323
|
+
Under `hdrOutput` use `"reinhard"` or `"exponential"`, which approach 1
|
|
324
|
+
asymptotically without clamping and so keep their ordering when the white
|
|
325
|
+
point lifts them past it, or use no curve at all
|
|
326
|
+
|
|
327
|
+
**What it is not.** It does not make an SDR display brighter, and there is no
|
|
328
|
+
way to ask melonJS how bright the panel is — `matchMedia("(dynamic-range:
|
|
329
|
+
high)")` is the closest the platform gets. Calibrating in nits, the way a
|
|
330
|
+
console settings screen does, is not reachable from the web platform today.
|
|
331
|
+
|
|
332
|
+
### Exposure, and the runtime toggle
|
|
333
|
+
|
|
334
|
+
Under `hdr` the useful exposure is often **below** 1, which surprises people who
|
|
335
|
+
have only used the effect as an LDR grade. The curve has to work where it
|
|
336
|
+
separates: at exposure 1 the ACES shoulder maps 2, 4 and 8 to 0.86, 0.96 and
|
|
337
|
+
0.99, so genuinely different energies all arrive at white and the headroom buys
|
|
338
|
+
nothing you can see. Lower exposure spreads them out. The right value is a
|
|
339
|
+
property of how bright your scene is authored, so measure it; there is no
|
|
340
|
+
default that is right for two different games.
|
|
341
|
+
|
|
342
|
+
A tone curve is a setting rather than an effect you wire up:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
renderer.setToneMapping("aces", { exposure: 0.4, white: 6 });
|
|
346
|
+
renderer.setToneMapping("none"); // and off again
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`"none"` by default. The renderer keeps the curve LAST on the camera, so
|
|
350
|
+
whatever you put in front of it is mapped along with everything else. Changing
|
|
351
|
+
the mode rebuilds one shader (the curve is baked); `exposure` and `white` are
|
|
352
|
+
uniform writes, so a settings screen can drive them live. `white` is the value
|
|
353
|
+
that comes out as display white — the knob a "brightness" or "peak" slider
|
|
354
|
+
wants, since exposure alone drags the midtones up with the highlights.
|
|
355
|
+
|
|
356
|
+
`renderer.setHDR(enable)` moves the whole pipeline at runtime, the way
|
|
357
|
+
`setAntiAlias` does, for a settings toggle. It empties the render-target pool
|
|
358
|
+
when the format actually changes, so it is not something to drive per frame.
|
|
359
|
+
The boot header reports what happened either way:
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
resolution: requested 1024x576 (HDR), got 1024x576 (SDR) ← the driver refused
|
|
363
|
+
```
|
|
82
364
|
|
|
83
365
|
For grading without writing a shader yourself, the camera has a colour matrix:
|
|
84
366
|
|
|
@@ -108,6 +390,23 @@ one `@group(3) @binding(0) var<uniform>` struct — so a single `setUniform` cal
|
|
|
108
390
|
feeds whichever backend is live. `setTime` is a convenience for a `uTime`
|
|
109
391
|
uniform and silently does nothing if the shader does not declare one.
|
|
110
392
|
|
|
393
|
+
**Never put a backtick in a shader body, including in its comments.** An inline
|
|
394
|
+
body is a JavaScript template literal, so a backtick ENDS it — and what follows
|
|
395
|
+
is parsed as code, which fails somewhere further down the file with an error
|
|
396
|
+
that says nothing about shaders:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
const glsl = `
|
|
400
|
+
// x * x, not `pow(x, 2.0)`: the base goes negative here <-- ends the literal
|
|
401
|
+
vec4 apply(vec4 color, vec2 uv) { ... }
|
|
402
|
+
`;
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Markdown-quoting an identifier is a reflex when writing a comment, and this is
|
|
406
|
+
the one place it breaks the file. Write `pow(x, 2.0)` bare. A body loaded from
|
|
407
|
+
a `.glsl` / `.wgsl` file has no such problem, which is one more reason to move
|
|
408
|
+
a shader out once it stops being a sketch.
|
|
409
|
+
|
|
111
410
|
### One effect is one program — prefer preloading, and share
|
|
112
411
|
|
|
113
412
|
A `ShaderEffect` compiles and links in its **constructor**, and the link is
|
|
@@ -201,28 +500,38 @@ Two places where a straight port really does differ:
|
|
|
201
500
|
uniform (initialise it to `1.0`) and multiply vertical offsets by it; the
|
|
202
501
|
renderer feeds `-1` on that path and `+1` everywhere else.
|
|
203
502
|
|
|
204
|
-
## A ShaderEffect
|
|
503
|
+
## A ShaderEffect on a mesh is a colour hook, and drops some builtins
|
|
205
504
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
505
|
+
`mesh.addPostEffect(fx)` shades a `Mesh` on **both** backends since 20.5. The
|
|
506
|
+
body is spliced into the engine's own mesh shader as a colour hook, so the mesh
|
|
507
|
+
keeps its placement, alpha cutout, lighting and fog, and `setUniform` /
|
|
508
|
+
`setTime` drive it exactly as they do over a sprite. Uniform-only bodies are
|
|
509
|
+
the supported case and they are most of them.
|
|
211
510
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
`video.AUTO` prefers WebGPU where it is available, this is the default outcome
|
|
215
|
-
on most desktops. It cost several hours here: the symptom was a water surface
|
|
216
|
-
rendering flat white, and alpha, bindings, transparency and lighting were all
|
|
217
|
-
eliminated before the backend was.
|
|
511
|
+
Five things the quad tier gives an effect that the mesh tier does not, each
|
|
512
|
+
refused rather than half-working:
|
|
218
513
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
514
|
+
| refused | why |
|
|
515
|
+
|---|---|
|
|
516
|
+
| its own `setTexture` samplers | resolved by the quad tier's per-effect device state; refused on both backends |
|
|
517
|
+
| `screen_texture`, `screen_uv` | need the screen capture only the quad tier takes |
|
|
518
|
+
| `noise_uv` | derived from a quad's image and object size |
|
|
519
|
+
| `vColor`, on WGSL only | the WGSL mesh module has no module-scope equivalent; the GLSL one declares it, so GLSL is fine |
|
|
520
|
+
| `InstancedMesh` | the instanced WGSL builder drops everything between the two stages |
|
|
521
|
+
|
|
522
|
+
Hitting one warns **once** and falls back to the built-in shading. Nothing
|
|
523
|
+
throws, so the symptom is a mesh drawing with its plain texture, which reads as
|
|
524
|
+
a broken shader, a bad binding or a texture that failed to load. Check the
|
|
525
|
+
console for the warning before you start eliminating alpha, bindings and
|
|
526
|
+
transparency: it names which of the five it was.
|
|
527
|
+
|
|
528
|
+
2D renderables never hit this. `Sprite`, `Text` and the rest go through the
|
|
529
|
+
quad path, which has the full feature set, so a gradient fill for text or a
|
|
530
|
+
refraction over a sprite is fine.
|
|
531
|
+
|
|
532
|
+
If you need one of the five on a mesh, supply a full dual-language `GLShader`
|
|
533
|
+
rather than a fragment body, and declare the fog uniforms yourself (see
|
|
534
|
+
`melonjs-3d`).
|
|
226
535
|
|
|
227
536
|
## Extra textures bind in setTexture order
|
|
228
537
|
|
|
@@ -294,13 +603,16 @@ same question after construction.
|
|
|
294
603
|
| effect does nothing, warning in console | Canvas fallback — no programmable pipeline |
|
|
295
604
|
| effect does nothing on some machines only | GLSL-only shader, `video.AUTO` chose WebGPU |
|
|
296
605
|
| a white or solid box where the effect should be | carrier renderable drawn while the effect is disabled |
|
|
606
|
+
| ONE effect does nothing but two of them work | the renderable draws with primitives — set `postEffectNeedsCapture` |
|
|
297
607
|
| effect cannot be re-enabled | `removePostEffect()` destroyed it — use `enabled` |
|
|
298
608
|
| ported shader renders upside down | a hand-bound `toFrameTexture()` capture — GL is bottom-up, WebGPU top-down |
|
|
299
609
|
| shadow/smear offset flips on some draws | vertical UV offset not multiplied by `uUVYDir` |
|
|
300
610
|
| frame rate collapses with many blended sprites | an *advanced* mode (overlay, darken, …) — each draw is a capture plus a composite |
|
|
301
611
|
| animated shader never moves | `setTime` fed milliseconds, or the shader declares no `uTime` |
|
|
612
|
+
| 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 |
|
|
302
613
|
|
|
303
614
|
## Related skills
|
|
615
|
+
- `melonjs-scenes-and-state` — `Tween` and the easing families, for animating any of this
|
|
304
616
|
|
|
305
617
|
- `melonjs-renderables` — where post effects attach, and the destroy trap
|
|
306
618
|
- `melonjs-3d` — the GPU-backend requirement, and why a custom mesh shader is
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-input
|
|
3
|
-
description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, click, touch, drag."
|
|
3
|
+
description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -64,9 +64,11 @@ subclasses with no body are the ones that catch people.
|
|
|
64
64
|
Register in `onActivateEvent` and release in `onDeactivateEvent`, not in the
|
|
65
65
|
constructor — pooled objects are reused and would otherwise accumulate handlers.
|
|
66
66
|
|
|
67
|
-
`app.viewport` works as a whole-screen region when you want global clicks
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
`app.viewport` works as a whole-screen region when you want global clicks. Note
|
|
68
|
+
it is asked **before** everything else, not as a fallback after nothing matched:
|
|
69
|
+
the dispatcher appends it to the candidate list and walks that list from the
|
|
70
|
+
end. So a viewport handler must not return `false` unless it really means to
|
|
71
|
+
swallow every pointer event in the game.
|
|
70
72
|
|
|
71
73
|
The accepted event names are exactly `"pointerdown"`, `"pointerup"`,
|
|
72
74
|
`"pointermove"`, `"pointercancel"`, `"pointerenter"`, `"pointerover"`,
|
|
@@ -76,6 +78,44 @@ throw `invalid event type`. melonJS maps the canonical name onto whichever
|
|
|
76
78
|
mouse/touch events the device actually supports, so you always register the
|
|
77
79
|
pointer name.
|
|
78
80
|
|
|
81
|
+
## Which object gets the event
|
|
82
|
+
|
|
83
|
+
The dispatcher collects the regions near the pointer from the broadphase,
|
|
84
|
+
bounds-checks each one, orders them **the way they are drawn** and asks the
|
|
85
|
+
topmost first, walking down until something consumes.
|
|
86
|
+
|
|
87
|
+
A handler consumes by returning `false`. This is the DOM sense and it reads
|
|
88
|
+
backwards: `false` means handled, and anything else, `undefined` included, lets
|
|
89
|
+
the walk carry on to whatever is underneath.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
input.registerPointerEvent("pointerdown", this, () => {
|
|
93
|
+
this.fire();
|
|
94
|
+
return false; // nothing under this gets the click
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Three things decide the order, and only the first is obvious:
|
|
99
|
+
|
|
100
|
+
- **`pos.z`, among siblings of one container.** Higher is on top.
|
|
101
|
+
- **Across containers, the common ancestor decides.** `pos.z` is
|
|
102
|
+
container-local, since `autoDepth` numbers each container's children from 1,
|
|
103
|
+
so a button at `z = 8` inside one panel does not outrank a second panel at
|
|
104
|
+
`z = 2`. The pair is resolved by walking both up to their shared parent and
|
|
105
|
+
comparing the two siblings there, which is exactly how drawing resolves it.
|
|
106
|
+
Raise a whole panel with `ancestor.moveToTop(panel)`, not by inflating a
|
|
107
|
+
child's `z`.
|
|
108
|
+
- **A child is asked before the container holding it**, since it draws over it.
|
|
109
|
+
|
|
110
|
+
Equal `z` between siblings falls back to child order, lower index on top. Two
|
|
111
|
+
callbacks registered on the **same** region for the same event run
|
|
112
|
+
last-registered-first.
|
|
113
|
+
|
|
114
|
+
Consuming a **move** also takes the pointer away from the regions below: each
|
|
115
|
+
one still holding it gets its `pointerleave`, because it is covered now and
|
|
116
|
+
being inside its own bounds no longer decides. Consuming a press, a release or a
|
|
117
|
+
wheel does not, since none of those says where the pointer is.
|
|
118
|
+
|
|
79
119
|
## World coordinates versus screen coordinates
|
|
80
120
|
|
|
81
121
|
The pointer carries several, and picking the wrong one produces a feedback loop:
|
|
@@ -159,6 +199,10 @@ input.triggerKeyEvent(input.KEY.LEFT, false); // release
|
|
|
159
199
|
| jump fires every frame while held | `bindKey` without its third `lock` argument |
|
|
160
200
|
| `invalid event type` thrown at registration | non-pointer name (`"click"`, `"mousedown"`, …) passed to `registerPointerEvent` |
|
|
161
201
|
| `no action defined for keycode N` | `bindGamepad` called before `bindKey` for that keycode |
|
|
202
|
+
| a widget under an overlapping panel still reacts | the panel consumes nothing — return `false` from its `onClick` and `onOver` |
|
|
203
|
+
| a widget stays highlighted after the pointer moves onto a panel over it | the panel consumes no `pointermove`, so nothing takes the pointer off the widget |
|
|
204
|
+
| a child's big `z` does not put it in front of another container | `pos.z` is container-local — use `ancestor.moveToTop()` on the container |
|
|
205
|
+
| every click in the game stops working | a handler on `app.viewport` returning `false`, which is asked first |
|
|
162
206
|
|
|
163
207
|
## Related skills
|
|
164
208
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-lighting
|
|
3
|
-
description: "Use this skill for 2D dynamic lighting in melonJS — Light2d, ambient light on a Stage, multiple lights, and per-pixel normal-mapped sprites authored in SpriteIlluminator. Covers the ambient-cutout model, which parts need a GPU backend, and why Light2d does not survive a Camera3d. Triggers on: Light2d, lighting, ambientLight, ambientLightingColor, normalMap, SpriteIlluminator, per-pixel lighting, drawLight, torch, glow, darkness."
|
|
3
|
+
description: "Use this skill for 2D dynamic lighting in melonJS — Light2d, ambient light on a Stage, multiple lights, and per-pixel normal-mapped sprites authored in SpriteIlluminator. Covers specular highlights via shininess, the ambient-cutout model, which parts need a GPU backend, and why Light2d does not survive a Camera3d. Triggers on: Light2d, specular, shininess, highlight, glint, lighting, ambientLight, ambientLightingColor, normalMap, SpriteIlluminator, per-pixel lighting, drawLight, torch, glow, darkness."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -83,6 +83,50 @@ atlas can carry one too: `new TextureAtlas(json, image, { normalMap })` pairs a
|
|
|
83
83
|
normal texture sharing the colour texture's UVs, and a `Sprite` built from that
|
|
84
84
|
atlas picks it up in preference to its own `settings.normalMap`.
|
|
85
85
|
|
|
86
|
+
## Specular highlights
|
|
87
|
+
|
|
88
|
+
A normal map gives a sprite **shape**: the light knows which way each texel
|
|
89
|
+
faces, so a torch reveals bumps and crevices. `shininess` decides whether it
|
|
90
|
+
also **shines**:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
const sword = new Sprite(x, y, {
|
|
94
|
+
image: "sword",
|
|
95
|
+
normalMap: "sword_n",
|
|
96
|
+
shininess: 64, // 0 (the default) is matte
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The difference is what the highlight *does*. Diffuse brightness depends only on
|
|
101
|
+
the angle between the surface and the light, so a torch moving past just makes
|
|
102
|
+
the sprite brighter. A specular highlight only appears where a texel reflects
|
|
103
|
+
the light **toward the screen**, so it slides across the surface as the light
|
|
104
|
+
moves: armour that glints as you walk by, rather than armour that is merely lit.
|
|
105
|
+
|
|
106
|
+
Low values give a broad sheen (worn metal, wet stone); high values a tight glint
|
|
107
|
+
(polished steel, glass). It is gated on the exponent exactly as MTL `Ns` is on
|
|
108
|
+
the 3D path, so `0` means matte however bright the scene, and a sprite that
|
|
109
|
+
never sets it renders exactly as it always did.
|
|
110
|
+
|
|
111
|
+
Three things worth knowing:
|
|
112
|
+
|
|
113
|
+
- **It needs a `normalMap`.** With no surface directions there is nothing to
|
|
114
|
+
reflect, and the sprite stays matte whatever `shininess` says.
|
|
115
|
+
- **The normal map's alpha channel masks it per texel.** 255 is fully
|
|
116
|
+
reflective, 0 is dead matte, so one sprite can be a shiny blade with a matte
|
|
117
|
+
leather grip. A normal map with no alpha detail (the usual case) shines
|
|
118
|
+
uniformly. Note the corollary: if your normal map already uses alpha for
|
|
119
|
+
something, that channel now also decides gloss.
|
|
120
|
+
- **It accumulates per light.** Each light contributes its own highlight in its
|
|
121
|
+
own colour, so a sprite between a warm lamp and a cool one carries two
|
|
122
|
+
distinct glints that move independently.
|
|
123
|
+
|
|
124
|
+
The highlight takes the light's own colour and is *added* on top of the lit
|
|
125
|
+
result rather than multiplied into it, which is why a glint blows out toward
|
|
126
|
+
white on a dark sprite instead of tinting with it.
|
|
127
|
+
|
|
128
|
+
See it running: [Normal Map example](https://melonjs.github.io/melonJS/examples/#/normal-map).
|
|
129
|
+
|
|
86
130
|
Unlit areas of a normal-mapped sprite render pure black unless you raise
|
|
87
131
|
`Stage.ambientLightingColor` (default black) — that is the base level added to
|
|
88
132
|
every lit pixel, and it is a different knob from `Stage.ambientLight`.
|