melonjs 20.7.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 +9 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2770 -538
- 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/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 +35 -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/loader/loader.d.ts +9 -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/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 +9 -1
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +3 -2
- 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/sat3d.d.ts +46 -0
- package/build/physics/builtin/sat3d.d.ts.map +1 -1
- 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 +12 -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 +283 -4
- 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 +90 -27
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +5 -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 +27 -5
- 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/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/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/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 +11 -0
- 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 +0 -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/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/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/webgpu_renderer.d.ts +7 -75
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs-3d/SKILL.md +277 -19
- package/skills/melonjs-3d-assets/SKILL.md +76 -9
- 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-particles-and-trails/SKILL.md +106 -4
- package/skills/melonjs-performance/SKILL.md +79 -2
- package/skills/melonjs-physics/SKILL.md +101 -27
- package/skills/melonjs-renderables/SKILL.md +2 -1
- package/skills/melonjs-scenes-and-state/SKILL.md +121 -1
- 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-performance
|
|
3
|
-
description: "Use this skill when a melonJS game drops frames, allocates heavily, or needs to scale to many objects. Covers object pooling, draw-call batching, atlases, baking with CanvasRenderTarget, instancing, culling and alwaysUpdate, the debug plugin, and where the real costs are. Triggers on: performance, slow, frame rate, fps, lag, pool, pool.register, pool.pull, batching, draw calls, InstancedMesh, culling, alwaysUpdate, inViewport, CanvasRenderTarget, optimise, profiling, debug plugin, memory."
|
|
3
|
+
description: "Use this skill when a melonJS game drops frames, allocates heavily, or needs to scale to many objects. Covers object pooling, draw-call batching, atlases, baking with CanvasRenderTarget, instancing, culling and alwaysUpdate, the debug plugin, and where the real costs are. Triggers on: performance, slow, frame rate, fps, lag, pool, pool.register, pool.pull, batching, draw calls, InstancedMesh, culling, alwaysUpdate, inViewport, CanvasRenderTarget, optimise, profiling, debug plugin, memory, render targets, hdr."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -81,6 +81,23 @@ Two rules that cause subtle bugs when missed:
|
|
|
81
81
|
successfully recycled object never sees it. Pair event subscriptions with
|
|
82
82
|
`onActivateEvent` / `onDeactivateEvent` instead, or you leak handlers.
|
|
83
83
|
|
|
84
|
+
- **`removeChild` is DEFERRED, which breaks a pool you re-lend in the same
|
|
85
|
+
frame.** The removal is queued and runs after the update and draw stack has
|
|
86
|
+
unwound, so an object you remove and immediately hand out again is torn out
|
|
87
|
+
from under its new owner when the queue drains: it vanishes, and the frame
|
|
88
|
+
after that the removal throws `Child is not mine.` Take it out immediately
|
|
89
|
+
instead, keeping the instance alive for reuse:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
world.removeChildNow(bullet, true); // keepalive: do NOT destroy it
|
|
93
|
+
this.freeList.push(bullet);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`removeChildNow(child, keepalive)` with `keepalive` true skips the destroy,
|
|
97
|
+
which is what you want for anything you own the lifetime of. This is the
|
|
98
|
+
usual cause of a pooled effect that flickers out one frame after it is
|
|
99
|
+
recycled.
|
|
100
|
+
|
|
84
101
|
Engine classes are poolable too: `pool.pull("Tween", target)`. The canonical
|
|
85
102
|
names are unprefixed — `Entity`, `Collectable`, `Trigger`, `Light2d`,
|
|
86
103
|
`Particle`, `Sprite`, `NineSliceSprite`, `Renderable`, `Text`, `BitmapText`,
|
|
@@ -95,13 +112,34 @@ name in new code; do not "correct" an `me.`-prefixed one, it is not broken.
|
|
|
95
112
|
## `scale()` is multiplicative
|
|
96
113
|
|
|
97
114
|
A pooled sprite arrives carrying its previous life's transform, and calling
|
|
98
|
-
`scale()` each frame compounds. For absolute
|
|
115
|
+
`scale()` each frame compounds. `rotate()` is the same. For absolute values,
|
|
116
|
+
reset first:
|
|
99
117
|
|
|
100
118
|
```js
|
|
101
119
|
sprite.currentTransform.identity();
|
|
102
120
|
sprite.currentTransform.scale(s, s, 1);
|
|
103
121
|
```
|
|
104
122
|
|
|
123
|
+
**Order matters, and it is the opposite of what reads naturally.**
|
|
124
|
+
`Matrix2d.scale` multiplies the matrix ROWS, so it applies AFTER whatever the
|
|
125
|
+
matrix already holds. Scale first and then rotate; rotate first and the scale
|
|
126
|
+
lands in the rotated frame and SHEARS anything non-uniform:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
// correct: a long thin quad, rotated
|
|
130
|
+
t.identity();
|
|
131
|
+
t.scale(length / texW, width / texH);
|
|
132
|
+
t.rotate(angle);
|
|
133
|
+
|
|
134
|
+
// wrong: the same quad arrives as a parallelogram
|
|
135
|
+
t.identity();
|
|
136
|
+
t.rotate(angle);
|
|
137
|
+
t.scale(length / texW, width / texH);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The symptom is a sprite that looks right at 0 and 90 degrees and skews in
|
|
141
|
+
between, which reads as a bad texture rather than as a transform bug.
|
|
142
|
+
|
|
105
143
|
## Culling and update cost
|
|
106
144
|
|
|
107
145
|
- `Container` recomputes `inViewport` for every child each frame against every
|
|
@@ -231,6 +269,45 @@ SwiftShader, where the absolute numbers are wildly pessimistic and the JS
|
|
|
231
269
|
profile shows the time as `(program)`, outside JS entirely. Ratios transfer;
|
|
232
270
|
milliseconds do not.
|
|
233
271
|
|
|
272
|
+
## Render targets, and what `hdr` costs
|
|
273
|
+
|
|
274
|
+
A render target is a canvas-sized GPU surface. The engine allocates them
|
|
275
|
+
lazily and only where a multi-pass chain actually needs one, which is worth
|
|
276
|
+
knowing before assuming an effect is expensive:
|
|
277
|
+
|
|
278
|
+
- a renderable with **no** post effects: none
|
|
279
|
+
- a renderable with **exactly one** post effect: still none. It takes the
|
|
280
|
+
`customShader` fast path and draws straight into whatever target is already
|
|
281
|
+
bound
|
|
282
|
+
- a **camera** with effects: a pair (one capture, one ping-pong intermediate)
|
|
283
|
+
- a sprite with **two or more** effects: its own pair
|
|
284
|
+
|
|
285
|
+
So the first lever is the count, not the cost of any one effect: going from
|
|
286
|
+
one effect to two on a sprite is what allocates, not going from a cheap shader
|
|
287
|
+
to an expensive one.
|
|
288
|
+
|
|
289
|
+
`hdr: true` changes the format of that camera pair from 8 bits per channel to
|
|
290
|
+
half-float, which is **8 bytes per pixel instead of 4**. At 1920×1080 that is
|
|
291
|
+
about 8 MB per target instead of 4 MB, so roughly +8 MB for the pair. With
|
|
292
|
+
`antiAlias` on, the capture half is also multisampled and its multisampled
|
|
293
|
+
surface takes the same format, so that part doubles too.
|
|
294
|
+
|
|
295
|
+
Nothing else moves. Sprite chains and the advanced-blend target stay 8-bit
|
|
296
|
+
whatever `hdr` says, for a correctness reason rather than a thrifty one: their
|
|
297
|
+
final blit composites with `ONE_MINUS_SRC_ALPHA`, and additive blending
|
|
298
|
+
accumulates alpha past 1 in a float target, which would turn that factor
|
|
299
|
+
negative and subtract the backdrop.
|
|
300
|
+
|
|
301
|
+
**With `hdr` off, nothing changes at all** — every target keeps the 8-bit
|
|
302
|
+
attachment it has always had, and a game that never adds a camera post-effect
|
|
303
|
+
allocates no render targets in the first place. The setting costs nothing
|
|
304
|
+
until something is actually using it.
|
|
305
|
+
|
|
306
|
+
```js
|
|
307
|
+
// what you actually got, which is not always what you asked for
|
|
308
|
+
if (app.renderer.supportsHDR === false) { /* grade for a chain that clamps */ }
|
|
309
|
+
```
|
|
310
|
+
|
|
234
311
|
## Where the costs actually are
|
|
235
312
|
|
|
236
313
|
In rough order for a typical 2D game:
|
|
@@ -258,7 +258,8 @@ built-in-only spelling of this and are deprecated since 20.7.0.
|
|
|
258
258
|
| `RoundRect` | exact | exact (decomposed) | sampled down to 12 vertices |
|
|
259
259
|
| `Ellipse` | exact | circle of the average radius | circle of the average radius |
|
|
260
260
|
| `Line` | exact | thin quad along the segment | thin quad along the segment |
|
|
261
|
-
| `
|
|
261
|
+
| `Box3d`, `Sphere` | exact, and in 3D | **throws** | **throws** |
|
|
262
|
+
| `Point` | supported | **throws** | **throws** |
|
|
262
263
|
|
|
263
264
|
Two of those are worth knowing before you author geometry. Box2D caps a
|
|
264
265
|
polygon's vertex count and says nothing when you go over: measured, a
|
|
@@ -329,6 +330,29 @@ body's collision shapes are measured from that same frame, so a shape of
|
|
|
329
330
|
a shape's own `pos` still offsets it inside that frame (a small hitbox at the
|
|
330
331
|
feet, a hurtbox at the head).
|
|
331
332
|
|
|
333
|
+
**That frame's origin is its TOP-LEFT corner, which is the trap for a small
|
|
334
|
+
shape.** `new Rect(0, 0, w, h)` reads naturally because it spans the frame.
|
|
335
|
+
A circle or a `Sphere` does not span anything, so `new Sphere(0, 0, 0, r)` on
|
|
336
|
+
a centre-anchored sprite sits on the sprite's corner, not on the sprite. Put
|
|
337
|
+
it at half the frame:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
// centred on a 24x128 sprite whose anchorPoint is (0.5, 0.5)
|
|
341
|
+
bolt.bodyDef = { type: "dynamic", shapes: [new Sphere(24 / 2, 128 / 2, 0, r)] };
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Measured on exactly that sprite: with the shape at `(0, 0, 0)`,
|
|
345
|
+
`adapter.getBodyShapes()` reported it at `(-12, -64)` — **64 units up the
|
|
346
|
+
screen, four times its own radius** — so the projectile collided well away
|
|
347
|
+
from where the player saw it, in a way that still looked like a working game
|
|
348
|
+
because the targets were large.
|
|
349
|
+
|
|
350
|
+
The offset comes from the renderable's own `width` / `height`, NOT from what
|
|
351
|
+
it draws at. Scale a sprite through `currentTransform` and those stay the
|
|
352
|
+
texture's size, so the half-frame term is half the texture, not half the
|
|
353
|
+
drawn quad. The debug overlay is the quickest confirmation: the red shape
|
|
354
|
+
should sit on the art.
|
|
355
|
+
|
|
332
356
|
This was broken until 20.7: shapes were measured from `pos` regardless of the
|
|
333
357
|
anchor, so anything not anchored at its top-left corner collided where it was
|
|
334
358
|
not drawn, by half a body at the default centred anchor and by a full body
|
|
@@ -400,7 +424,30 @@ survives but fights collision resolution and jitters. To teleport, go through
|
|
|
400
424
|
The lifecycle is `onCollisionStart` (contact begins), `onCollisionActive` (every
|
|
401
425
|
step while it persists) and `onCollisionEnd` (contact breaks). None of them are
|
|
402
426
|
defined on `Renderable` — you just declare the ones you want and the dispatcher
|
|
403
|
-
checks `typeof`.
|
|
427
|
+
checks `typeof`. That is also why they cannot be declared on the base class: a
|
|
428
|
+
field there would SHADOW the prototype method every subclass that implements
|
|
429
|
+
one defines.
|
|
430
|
+
|
|
431
|
+
**In TypeScript the response is `CollisionResponse`**, exported from `melonjs`:
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import type { CollisionResponse, Renderable } from "melonjs";
|
|
435
|
+
|
|
436
|
+
class Coin extends Sprite {
|
|
437
|
+
onCollisionStart(response: CollisionResponse, other: Renderable) { … }
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
On a renderable you do not subclass, declare the hooks you want as OPTIONAL
|
|
442
|
+
members of a local interface and assign through one plain cast — that is the
|
|
443
|
+
opt-in, and it needs no `as unknown as`:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
interface Contacts {
|
|
447
|
+
onCollisionStart?: (response: CollisionResponse, other: Renderable) => void;
|
|
448
|
+
}
|
|
449
|
+
(rock as Contacts).onCollisionStart = (response, other) => { … };
|
|
450
|
+
``` The legacy `onCollision` still exists and has a trap:
|
|
404
451
|
|
|
405
452
|
> On the built-in world, `onCollision` fires **twice per frame** for
|
|
406
453
|
> dynamic-dynamic pairs — once per outer-loop visit — and gets the raw SAT
|
|
@@ -496,50 +543,76 @@ Two prerequisites that produce empty results rather than errors:
|
|
|
496
543
|
- On the **built-in** adapter, the broadphase is cleared and rebuilt inside
|
|
497
544
|
`world.update()`, so querying before the first update returns nothing.
|
|
498
545
|
|
|
499
|
-
## 3D collision: `Box3d` and the Z pushback
|
|
546
|
+
## 3D collision: `Box3d`, `Sphere` and the Z pushback
|
|
500
547
|
|
|
501
|
-
The narrowphase is not 2D-only.
|
|
502
|
-
and
|
|
503
|
-
|
|
548
|
+
The narrowphase is not 2D-only. Two shapes carry a depth extent — `Box3d` and
|
|
549
|
+
`Sphere` — and any pair of them is resolved in three dimensions, pushing back
|
|
550
|
+
along **Z** as well as X and Y.
|
|
504
551
|
|
|
505
552
|
```js
|
|
506
|
-
import { Box3d } from "melonjs";
|
|
553
|
+
import { Box3d, Sphere } from "melonjs";
|
|
507
554
|
|
|
508
|
-
// centre offset from the body, then half-extents on each axis
|
|
509
|
-
|
|
510
|
-
|
|
555
|
+
// a box: centre offset from the body, then half-extents on each axis
|
|
556
|
+
crate.body.addShape(new Box3d(0, 0, 0, 40, 60, 40));
|
|
557
|
+
// a sphere: centre offset, then one radius
|
|
558
|
+
rock.body.addShape(new Sphere(0, 0, 0, 30));
|
|
511
559
|
```
|
|
512
560
|
|
|
513
|
-
Reach for
|
|
561
|
+
Reach for these for anything moving in the XZ plane under a `Camera3d` — a
|
|
514
562
|
runner dodging obstacles, a 2.5D platformer, pickups on a course. Hand-rolling
|
|
515
563
|
a distance check there is the usual mistake, and it throws away the
|
|
516
564
|
penetration vector the response already carries.
|
|
517
565
|
|
|
518
|
-
**
|
|
519
|
-
|
|
520
|
-
|
|
566
|
+
**Which of the two.** A `Box3d` is world-axis-aligned and cannot be oriented:
|
|
567
|
+
it is right for walls, floors, crates and anything else that stays square to
|
|
568
|
+
the world, and wrong for anything that turns. A `Sphere` has no orientation to
|
|
569
|
+
get wrong, which makes it the shape for things that tumble, and for anything
|
|
570
|
+
laid out over a curved surface — on a globe every object's own "up" points a
|
|
571
|
+
different way, so no single axis-aligned box fits any of them. It is also the
|
|
572
|
+
cheaper test: one distance against a sum of radii.
|
|
573
|
+
|
|
574
|
+
**Z arrives beside `overlapN`, not inside it.** `overlapN` stays the 2D unit
|
|
575
|
+
vector it always was and `overlapNZ` carries the depth component, because
|
|
576
|
+
`Vector3d` does not extend `Vector2d` and widening the field would have broken
|
|
577
|
+
every existing 2D handler. The two together are a 3D unit vector, and
|
|
578
|
+
`overlapZ = overlapNZ * overlap` exactly as `overlapV = overlapN * overlap`.
|
|
579
|
+
|
|
580
|
+
**How much of the normal is in each half depends on the shapes.** A `Box3d`
|
|
581
|
+
pair separates along ONE world axis, so a depth contact leaves the 2D fields
|
|
582
|
+
at zero and a legacy 2D handler correctly applies no push. A `Sphere`
|
|
583
|
+
separates along the line between the two centres, which is generally diagonal:
|
|
584
|
+
all three components are non-zero at once. So subtract both halves rather than
|
|
585
|
+
branching on one:
|
|
521
586
|
|
|
522
587
|
```js
|
|
523
588
|
onCollision(response, other) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return
|
|
589
|
+
// works for either shape: the 2D half is zero when there is no 2D push
|
|
590
|
+
this.pos.x -= response.overlapV.x;
|
|
591
|
+
this.pos.y -= response.overlapV.y;
|
|
592
|
+
this.pos.z -= response.overlapZ;
|
|
593
|
+
return false;
|
|
529
594
|
}
|
|
530
595
|
```
|
|
531
596
|
|
|
532
|
-
|
|
533
|
-
|
|
597
|
+
Branching on `if (response.overlapNZ !== 0)` and then moving only `pos.z` is
|
|
598
|
+
the mistake to avoid: correct for a box pair, and for a sphere it throws away
|
|
599
|
+
the planar half of the contact.
|
|
534
600
|
|
|
535
|
-
**Mixed pairs degrade to 2D.** `Box3d` against a `Polygon`,
|
|
536
|
-
`RoundRect` or `Ellipse` tests the
|
|
537
|
-
shape as unbounded along Z
|
|
538
|
-
|
|
601
|
+
**Mixed pairs degrade to 2D.** A `Box3d` or a `Sphere` against a `Polygon`,
|
|
602
|
+
`Rectangle`, `RoundRect` or `Ellipse` tests the 3D shape's XY footprint and
|
|
603
|
+
treats the planar shape as unbounded along Z, which is what keeps a sphere or
|
|
604
|
+
box body colliding with an ordinary Tiled collision layer. `overlapZ` is zero
|
|
605
|
+
for those, since only one side has a depth to resolve. Give BOTH sides a 3D
|
|
606
|
+
shape when you want a depth result.
|
|
539
607
|
|
|
540
608
|
This is the narrowphase and is independent of the 3D broadphase queries above
|
|
541
|
-
— `querySphere` / `raycast3d` need `world.sortOn === "depth"`;
|
|
542
|
-
|
|
609
|
+
— `querySphere` / `raycast3d` need `world.sortOn === "depth"`; a 3D body shape
|
|
610
|
+
does not.
|
|
611
|
+
|
|
612
|
+
`raycast3d` measures a `Box3d` body as its box and a body that is one `Sphere`
|
|
613
|
+
as that sphere, both exactly. Anything else, including a body mixing the two,
|
|
614
|
+
is measured as the AABB containing its 3D shapes, and a body with none falls
|
|
615
|
+
back to a bounding sphere derived from the renderable's 2D bounds.
|
|
543
616
|
|
|
544
617
|
**Bounds are what the broadphase sorts by, and they come from the SHAPE.**
|
|
545
618
|
Every item is filed by its renderable's 2D `getBounds()`, so an object with no
|
|
@@ -698,6 +771,7 @@ use them. `adapter.capabilities` (`constraints`,
|
|
|
698
771
|
| a body sinks through one shape of a multi-shape body | fixed in 20.7; before that the compound pass only ever re-resolved the first overlapping pair |
|
|
699
772
|
| a body will not stay put on a slope on the builtin | it is riding the slope and sliding: the builtin has no surface friction, only `frictionAir` damping. Damp it yourself, or use matter/planck |
|
|
700
773
|
| the sprite is drawn offset from its hitbox | pre-20.7 `anchorPoint` moved the drawing but not the collision shapes; upgrade, and drop any offsets you added to compensate |
|
|
774
|
+
| a small shape sits at the sprite's corner | a shape's offset is measured from the drawn frame's TOP-LEFT; `new Sphere(0, 0, 0, r)` is the corner, `(width / 2, height / 2)` is the centre |
|
|
701
775
|
| a rotated renderable reports an unrotated bounding box | `autoTransform: false` opts `updateBounds()` out of the transform |
|
|
702
776
|
| a body in a pile sinks into the floor (built-in) | fixed in 20.7 for anything with an immovable side; bodies pinned only by other DYNAMIC bodies still overlap by a pixel or two, which is what planck/matter are for |
|
|
703
777
|
| forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-renderables
|
|
3
|
-
description: "Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, and z-ordering. Triggers on: Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating."
|
|
3
|
+
description: "Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, and z-ordering. Triggers on: Tween, easing, animate, Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -284,6 +284,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
|
|
|
284
284
|
| `isRenderable = false` did not hide the object | it gates bounds, not drawing — use `alpha` |
|
|
285
285
|
|
|
286
286
|
## Related skills
|
|
287
|
+
- `melonjs-scenes-and-state` — `Tween` and the easing families, for animating any of this
|
|
287
288
|
|
|
288
289
|
- `melonjs-getting-started` — Application lifecycle and the scene graph
|
|
289
290
|
- `melonjs-20-migration` — the deprecated `shader` property and other pre-20 APIs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-scenes-and-state
|
|
3
|
-
description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Stage, state, state.set, state.change, state.transition, state.pause, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
|
|
3
|
+
description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Tween, easing, fade out, flash, pulse, punch, shrink back, ease in, ease out, animate over time, countdown in update, Stage, state, state.set, state.change, state.transition, state.pause, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -165,6 +165,18 @@ Returning nothing is falsy and the object can appear frozen while its state move
|
|
|
165
165
|
`dt` is milliseconds since the last frame and is the right basis for motion —
|
|
166
166
|
the engine already paces it. Do not reach for `performance.now()`.
|
|
167
167
|
|
|
168
|
+
**Smooth a value toward a target with `math.damp`, not with `lerp` per
|
|
169
|
+
frame.** `lerp(current, target, 0.1)` once per frame moves further on a slow
|
|
170
|
+
frame than on a fast one, so the feel drifts with the frame rate:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
this.speed = math.damp(this.speed, targetSpeed, 5, dt / 1000);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`damp(current, target, lambda, dt)` takes SECONDS, so divide `dt`. Higher
|
|
177
|
+
`lambda` converges faster. See `melonjs-camera-and-drawing` for the camera's
|
|
178
|
+
own `follow()` damping, which is already frame-rate independent for you.
|
|
179
|
+
|
|
168
180
|
A common idiom for a per-frame game controller with nothing to draw is a
|
|
169
181
|
`Renderable(0, 0, 1, 1)` with `alwaysUpdate = true` and an empty `draw()`.
|
|
170
182
|
|
|
@@ -191,6 +203,59 @@ countdown that should not freeze needs. (That argument was ignored by
|
|
|
191
203
|
|
|
192
204
|
## Tweens
|
|
193
205
|
|
|
206
|
+
### You are hand-rolling a tween if you write this
|
|
207
|
+
|
|
208
|
+
The shape, in any of its spellings. If you are adding a field that counts
|
|
209
|
+
milliseconds and dividing it by a constant to get a `0..1`, stop:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
// ✗ a tween, written out longhand
|
|
213
|
+
private flashMs = 0;
|
|
214
|
+
// ... in update(dt)
|
|
215
|
+
if (this.flashMs > 0) {
|
|
216
|
+
this.flashMs -= dt;
|
|
217
|
+
const k = this.flashMs / FLASH_MS; // ← the 0..1
|
|
218
|
+
effect.setUniform("uIntensity", k * k); // ← and a curve, by hand
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
// ✓ the same thing
|
|
224
|
+
const driver = { k: 1 };
|
|
225
|
+
new Tween(driver)
|
|
226
|
+
.to({ k: 0 }, { duration: FLASH_MS })
|
|
227
|
+
.easing(Tween.Easing.Quadratic.In) // the curve, named
|
|
228
|
+
.onUpdate(() => effect.setUniform("uIntensity", driver.k))
|
|
229
|
+
.start();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The tells, any one of which is enough: a `*Ms` or `*Age` field that only
|
|
233
|
+
`update()` touches; a division by a `*_MS` constant; `k * k`, `(1 - k) ** 2` or
|
|
234
|
+
`** 1.4` written inline, which is an easing curve spelled by hand; an
|
|
235
|
+
`if (x <= 0)` arm that exists only to clean up at the end, which is
|
|
236
|
+
`onComplete`; a second timer counting the same duration to chain what happens
|
|
237
|
+
next, which is `delay`.
|
|
238
|
+
|
|
239
|
+
It matters beyond tidiness: a hand-rolled fade needs something to tick it, and
|
|
240
|
+
an effect that is NOT a renderable (one living on the camera, say) has nothing
|
|
241
|
+
that will. That forces an `update(dt)` method and a call site in the game loop
|
|
242
|
+
whose only job is to drive the fade. A tween updates itself, so both disappear.
|
|
243
|
+
|
|
244
|
+
### When NOT to reach for one
|
|
245
|
+
|
|
246
|
+
Four shapes that look similar and are not tweens:
|
|
247
|
+
|
|
248
|
+
| shape | what it is | use |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| approaching a value that keeps MOVING | a follow, not an animation | `math.damp` |
|
|
251
|
+
| a loop that never ends | oscillation | a sine of your own clock |
|
|
252
|
+
| a gate: cooldown, spawn interval, invulnerability window | a timer, nothing is interpolated | a plain countdown |
|
|
253
|
+
| the same effect on dozens of live objects | per-instance churn | a hand-rolled countdown really is cheaper |
|
|
254
|
+
|
|
255
|
+
The rule in one line: **fixed duration, known endpoint, one-shot, few
|
|
256
|
+
instances.** All four, or it is not a tween.
|
|
257
|
+
|
|
258
|
+
|
|
194
259
|
```js
|
|
195
260
|
import { Tween, pool } from "melonjs";
|
|
196
261
|
|
|
@@ -216,6 +281,61 @@ Four traps:
|
|
|
216
281
|
- **Stop a tween before destroying its target**, or `onUpdate` fires against a
|
|
217
282
|
dead renderable.
|
|
218
283
|
|
|
284
|
+
**To animate something that is not a property, tween a holder and push the
|
|
285
|
+
value in `onUpdate`.** A shader uniform and a light's intensity are both set
|
|
286
|
+
through a method, so there is nothing for `to()` to interpolate directly. The
|
|
287
|
+
callback is invoked with the tweened object as `this` and the eased progress
|
|
288
|
+
`0..1` as its argument:
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
const fade = { level: 1 };
|
|
292
|
+
pool.pull("Tween", fade)
|
|
293
|
+
.to({ level: 0 }, { duration: 400 })
|
|
294
|
+
.easing(Tween.Easing.Quadratic.Out)
|
|
295
|
+
.onUpdate(function () {
|
|
296
|
+
effect.setUniform("uIntensity", this.level);
|
|
297
|
+
muzzleLight.intensity = this.level * 3;
|
|
298
|
+
})
|
|
299
|
+
.start();
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Use a `function`, not an arrow, if you want `this` to be the tweened object;
|
|
303
|
+
an arrow keeps the enclosing `this` and you read the holder by name instead.
|
|
304
|
+
For a handful of pooled objects a hand-rolled countdown in `update()` is
|
|
305
|
+
cheaper than a `Tween` each — this is the right tool for one-off effects, not
|
|
306
|
+
for every particle.
|
|
307
|
+
|
|
308
|
+
### Pick the easing, not just the duration
|
|
309
|
+
|
|
310
|
+
Eleven families, each with `In`, `Out` and `InOut`. `Out` is what most UI
|
|
311
|
+
wants: the value arrives decisively instead of creeping in. The two worth
|
|
312
|
+
knowing by name beyond the power curves:
|
|
313
|
+
|
|
314
|
+
| family | what it does |
|
|
315
|
+
| --- | --- |
|
|
316
|
+
| `Back` | **overshoots past the target and settles back**, which is impact: a stamp, a button press, anything that should LAND rather than arrive |
|
|
317
|
+
| `Bounce` | lands and bounces, for something dropping onto a surface |
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
// a stamp: scale falls to 1 and springs through it
|
|
321
|
+
const driver = { scale: 5 };
|
|
322
|
+
new Tween(driver)
|
|
323
|
+
.to({ scale: 1 }, { duration: 420 })
|
|
324
|
+
.easing(Tween.Easing.Back.Out)
|
|
325
|
+
.onUpdate(() => { label.currentTransform.identity(); label.scale(driver.scale); })
|
|
326
|
+
.onComplete(() => camera.shake(12, 400)) // the thump, when it truly lands
|
|
327
|
+
.start();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`Back` and `Elastic` **leave the range** between your start and end, so clamp
|
|
331
|
+
anything that cannot take it, an opacity or a colour channel above all. The
|
|
332
|
+
overshoot follows the direction of travel: a value tweened DOWN overshoots
|
|
333
|
+
below its target.
|
|
334
|
+
|
|
335
|
+
Reach for `onComplete` for anything that must happen on arrival, rather than a
|
|
336
|
+
second timer counting the same duration. `delay` in the `to()` options chains
|
|
337
|
+
the next one without a timer either.
|
|
338
|
+
|
|
219
339
|
## Guard teardown-adjacent callbacks
|
|
220
340
|
|
|
221
341
|
Callbacks can fire while a stage is being torn down, when the current state is
|