melonjs 20.7.0 → 20.9.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 +7 -3
- package/build/application/application.d.ts +66 -8
- package/build/application/application.d.ts.map +1 -1
- package/build/application/defaultApplicationSettings.d.ts +6 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/resize.d.ts +25 -0
- package/build/application/resize.d.ts.map +1 -1
- package/build/application/scaleMethods.d.ts +13 -3
- package/build/application/scaleMethods.d.ts.map +1 -1
- package/build/application/settings.d.ts +144 -5
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +29 -5
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/autopause.d.ts +36 -0
- package/build/audio/autopause.d.ts.map +1 -0
- package/build/audio/backend/engine.d.ts +10 -1
- package/build/audio/backend/engine.d.ts.map +1 -1
- package/build/audio/backend/sound.d.ts +0 -1
- package/build/audio/backend/sound.d.ts.map +1 -1
- package/build/audio/backend/spatial.d.ts.map +1 -1
- package/build/audio/context.d.ts +35 -0
- package/build/audio/context.d.ts.map +1 -0
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/procedural.d.ts.map +1 -1
- package/build/audio/spatial.d.ts +87 -0
- package/build/audio/spatial.d.ts.map +1 -0
- package/build/audio/state.d.ts +1 -0
- package/build/audio/state.d.ts.map +1 -1
- package/build/audio/types.d.ts +46 -6
- package/build/audio/types.d.ts.map +1 -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/camera/effects/fade_effect.d.ts.map +1 -1
- package/build/camera/effects/mask_effect.d.ts.map +1 -1
- package/build/camera/effects/shake_effect.d.ts.map +1 -1
- package/build/geometries/box3d.d.ts +25 -7
- package/build/geometries/box3d.d.ts.map +1 -1
- package/build/geometries/ellipse.d.ts +15 -1
- package/build/geometries/ellipse.d.ts.map +1 -1
- package/build/geometries/line.d.ts +1 -1
- package/build/geometries/line.d.ts.map +1 -1
- package/build/geometries/point.d.ts +1 -1
- package/build/geometries/point.d.ts.map +1 -1
- package/build/geometries/polygon.d.ts +15 -1
- package/build/geometries/polygon.d.ts.map +1 -1
- package/build/geometries/rectangle.d.ts +1 -1
- package/build/geometries/rectangle.d.ts.map +1 -1
- package/build/geometries/roundrect.d.ts +1 -1
- package/build/geometries/roundrect.d.ts.map +1 -1
- package/build/geometries/sphere.d.ts +36 -7
- package/build/geometries/sphere.d.ts.map +1 -1
- package/build/index.d.ts +16 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +38172 -33633
- package/build/index.js.map +4 -4
- package/build/input/gamepad.d.ts +129 -3
- package/build/input/gamepad.d.ts.map +1 -1
- package/build/input/pointerevent.d.ts +8 -0
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts +20 -33
- 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/TMXObjectFactory.d.ts +3 -2
- package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -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 +5 -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/cache.d.ts +220 -0
- package/build/loader/cache.d.ts.map +1 -1
- package/build/loader/loader.d.ts +20 -340
- 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/gltf.d.ts +115 -0
- package/build/loader/parsers/gltf.d.ts.map +1 -1
- package/build/loader/parsers/shader.d.ts +0 -2
- package/build/math/color.d.ts +28 -5
- package/build/math/color.d.ts.map +1 -1
- package/build/math/matrix2d.d.ts +1 -1
- package/build/math/matrix2d.d.ts.map +1 -1
- package/build/math/matrix3d.d.ts +1 -1
- package/build/math/matrix3d.d.ts.map +1 -1
- package/build/math/observableVector2d.d.ts +14 -2
- package/build/math/observableVector2d.d.ts.map +1 -1
- package/build/math/observableVector3d.d.ts +15 -1
- package/build/math/observableVector3d.d.ts.map +1 -1
- package/build/math/vector2d.d.ts +31 -1
- package/build/math/vector2d.d.ts.map +1 -1
- package/build/math/vector3d.d.ts +38 -1
- package/build/math/vector3d.d.ts.map +1 -1
- package/build/math/vertex.d.ts.map +1 -1
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +29 -2
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +104 -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/bounds.d.ts +1 -1
- package/build/physics/bounds.d.ts.map +1 -1
- package/build/physics/broadphase/octree.d.ts +24 -0
- package/build/physics/broadphase/octree.d.ts.map +1 -1
- package/build/physics/broadphase/quadtree.d.ts +31 -0
- package/build/physics/broadphase/quadtree.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +17 -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/pool.d.ts +4 -0
- package/build/pool.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 +17 -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 +56 -7
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +369 -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 +129 -28
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +7 -20
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/sprite3d.d.ts +31 -16
- package/build/renderable/sprite3d.d.ts.map +1 -1
- package/build/renderable/text/bitmaptext.d.ts +76 -5
- package/build/renderable/text/bitmaptext.d.ts.map +1 -1
- package/build/renderable/text/bitmaptextdata.d.ts +1 -5
- package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
- package/build/renderable/text/glypheffect.d.ts +44 -0
- package/build/renderable/text/glypheffect.d.ts.map +1 -0
- package/build/renderable/text/text.d.ts +22 -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/trail3d.d.ts +174 -0
- package/build/renderable/trail3d.d.ts.map +1 -0
- package/build/renderable/trigger.d.ts +28 -5
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/panelpaint.d.ts +5 -0
- package/build/renderable/ui/panelpaint.d.ts.map +1 -0
- package/build/renderable/ui/progressbar.d.ts +192 -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/uipanel.d.ts +245 -0
- package/build/renderable/ui/uipanel.d.ts.map +1 -0
- package/build/renderable/ui/uispriteelement.d.ts +19 -3
- package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
- package/build/renderable/ui/uitextbutton.d.ts +30 -1
- package/build/renderable/ui/uitextbutton.d.ts.map +1 -1
- package/build/state/state.d.ts.map +1 -1
- package/build/system/bootstrap.d.ts.map +1 -1
- package/build/system/legacy_pool.d.ts +24 -4
- package/build/system/legacy_pool.d.ts.map +1 -1
- package/build/system/pool.d.ts +15 -8
- package/build/system/pool.d.ts.map +1 -1
- package/build/tweens/tween.d.ts +50 -3
- 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 -7
- 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 -7
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/gpu/batcher.d.ts +8 -2
- package/build/video/gpu/batcher.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 +1 -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/rendertarget.d.ts +12 -5
- package/build/video/rendertarget/rendertarget.d.ts.map +1 -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 +24 -14
- package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +2 -1
- 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/texture/texture2d.d.ts +6 -0
- package/build/video/texture/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 +2 -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 +2 -2
- package/skills/AGENTS.md +1 -1
- package/skills/melonjs/SKILL.md +2 -0
- package/skills/melonjs-3d/SKILL.md +454 -22
- package/skills/melonjs-3d-assets/SKILL.md +111 -10
- package/skills/melonjs-audio/SKILL.md +229 -8
- package/skills/melonjs-camera-and-drawing/SKILL.md +127 -1
- package/skills/melonjs-display-and-scaling/SKILL.md +273 -0
- package/skills/melonjs-effects-and-shaders/SKILL.md +341 -29
- package/skills/melonjs-events/SKILL.md +8 -6
- package/skills/melonjs-input/SKILL.md +147 -7
- package/skills/melonjs-math/SKILL.md +313 -0
- package/skills/melonjs-particles-and-trails/SKILL.md +189 -13
- package/skills/melonjs-performance/SKILL.md +131 -23
- package/skills/melonjs-physics/SKILL.md +103 -28
- package/skills/melonjs-renderables/SKILL.md +8 -2
- package/skills/melonjs-scenes-and-state/SKILL.md +134 -3
- package/skills/melonjs-sprites-and-animation/SKILL.md +9 -4
- package/skills/melonjs-tilemaps/SKILL.md +40 -12
- package/skills/melonjs-ui-and-text/SKILL.md +546 -7
- 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-particles-and-trails
|
|
3
|
-
description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, Trail, explosion, smoke, sparks, exhaust, emitter."
|
|
3
|
+
description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, 3D particle motion through elevation and minSpread/maxSpread in 20.8, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: Trail3d, ribbon, exhaust trail, speed streak, ski track, tyre mark, ribbonNormal, ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -28,6 +28,142 @@ emitter.burstParticles(); // one shot
|
|
|
28
28
|
`duration` setting, which defaults to `Infinity` — a stream runs until
|
|
29
29
|
`stopStream()`. `burstParticles(n)` launches `n`, or `totalParticles`.
|
|
30
30
|
|
|
31
|
+
## Sprite-sheet particles
|
|
32
|
+
|
|
33
|
+
Use `framewidth` and `frameheight` (the same names as on `Sprite`) to divide
|
|
34
|
+
`image` into a regular grid. Each omitted dimension defaults to that of the
|
|
35
|
+
image. Frames are numbered from zero left-to-right, then top-to-bottom; only
|
|
36
|
+
whole cells count, so partial cells along the right/bottom edges are ignored.
|
|
37
|
+
Margins and spacing are not supported.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const explosion = new ParticleEmitter(x, y, {
|
|
41
|
+
image: loader.getImage("explosion-sheet"),
|
|
42
|
+
framewidth: 32, frameheight: 32,
|
|
43
|
+
frames: [0, 1, 2, 3],
|
|
44
|
+
minLife: 400, maxLife: 800,
|
|
45
|
+
});
|
|
46
|
+
world.addChild(explosion);
|
|
47
|
+
explosion.burstParticles();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Omit `frames` to use every cell. Each particle traverses the sequence once
|
|
51
|
+
over its own lifetime, with equal time for each entry; repeated indices hold
|
|
52
|
+
a frame longer. Existing scale, opacity and rotation still apply. Bounds and
|
|
53
|
+
the rotation pivot use one frame's dimensions, rather than the whole sheet.
|
|
54
|
+
|
|
55
|
+
Set `randomFrame: true` for debris: a particle chooses one entry at birth and
|
|
56
|
+
keeps it until it dies. Different frames share the same image and GPU batch.
|
|
57
|
+
Without sheet settings, particles still draw the whole image as before, and
|
|
58
|
+
that includes `randomFrame` on its own: it needs a grid to choose from, so
|
|
59
|
+
pair it with `framewidth`/`frameheight` or it does nothing.
|
|
60
|
+
|
|
61
|
+
Note the variety is in SHAPE, not colour. `tint` belongs to the emitter, so
|
|
62
|
+
every particle from one emitter is tinted alike however many frames it picks
|
|
63
|
+
between.
|
|
64
|
+
|
|
65
|
+
Frame dimensions must be positive integers that fit inside the image, and an
|
|
66
|
+
explicit `frames` sequence must be nonempty and contain valid integer indices.
|
|
67
|
+
Invalid configurations throw `RangeError` before changing an emitter during
|
|
68
|
+
`reset()`. The frame sequence is copied on construction/reset; resetting changes
|
|
69
|
+
only future particles, so ones already alive finish with their original image,
|
|
70
|
+
frame size and sequence.
|
|
71
|
+
|
|
72
|
+
## Where particles are born
|
|
73
|
+
|
|
74
|
+
`width` and `height` are the spawn AREA, and both default to `1` — so by
|
|
75
|
+
default every particle launches from the same pixel. That is fine for a
|
|
76
|
+
fountain and wrong for an explosion: additive sprites piled on one point clip
|
|
77
|
+
all three channels there however well they are tinted, so a burst reads as one
|
|
78
|
+
bright flash rather than as debris.
|
|
79
|
+
|
|
80
|
+
Give it the size of whatever broke. The area is measured from the emitter's own
|
|
81
|
+
position, NOT centred on it, so offset by half or the whole burst sits down and
|
|
82
|
+
to the right of its source:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const area = radius * 2;
|
|
86
|
+
const emitter = new ParticleEmitter(x - area / 2, y - area / 2, {
|
|
87
|
+
width: area, height: area,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Particles in 3D
|
|
92
|
+
|
|
93
|
+
By default a particle keeps the depth it was born at for its whole life: it
|
|
94
|
+
moves across the emitter's plane and never toward or away from the camera.
|
|
95
|
+
Under a `Camera3d` that reads as a flat sticker anywhere but dead ahead.
|
|
96
|
+
|
|
97
|
+
`elevation` lifts the launch out of that plane and `elevationVariation` spreads
|
|
98
|
+
it. `angle` is the azimuth within the plane, as it always was:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const emitter = new ParticleEmitter(x, y, {
|
|
102
|
+
angle: 0, angleVariation: Math.PI * 2,
|
|
103
|
+
elevation: 0, elevationVariation: Math.PI / 2, // a sphere of debris
|
|
104
|
+
speed: 9,
|
|
105
|
+
});
|
|
106
|
+
world.addChild(emitter, z); // the depth slice the burst belongs to
|
|
107
|
+
emitter.burstParticles();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`speed` is the length of the whole 3D vector, so an "all directions" burst
|
|
111
|
+
covers the sphere evenly instead of bunching toward the poles.
|
|
112
|
+
|
|
113
|
+
Both default to `0`, and that is a gate rather than the 3D path with zeros in
|
|
114
|
+
it: an emitter that asks for no elevation runs the same two trig calls it
|
|
115
|
+
always did, writes no depth and starts no sort.
|
|
116
|
+
|
|
117
|
+
**The launch is in WORLD axes.** `vel.x` and `vel.y` add to the particle's
|
|
118
|
+
`pos.x`/`pos.y` and `velZ` adds to its depth, so `angle` and `elevation`
|
|
119
|
+
describe a direction in the world, not on the screen. Building them from a
|
|
120
|
+
camera basis works only while the camera's axes ARE the world axes, which stops
|
|
121
|
+
being true the moment you pose a `Camera3d`. For a direction `d` already in
|
|
122
|
+
world terms the pair is `angle: Math.atan2(-d.y, d.x)`,
|
|
123
|
+
`elevation: Math.asin(d.z)`.
|
|
124
|
+
|
|
125
|
+
### Cones, rings and spheres: aiming around an axis
|
|
126
|
+
|
|
127
|
+
`angleVariation` and `elevationVariation` are sampled INDEPENDENTLY, spreading
|
|
128
|
+
particles over a rectangle of azimuth by elevation. That cannot describe a
|
|
129
|
+
shape defined against a direction. A ring of debris leaving a point on a sphere
|
|
130
|
+
is the set of directions at ninety degrees to that point's normal, and on a
|
|
131
|
+
rectangle the elevation that satisfies it is a function of the azimuth, so no
|
|
132
|
+
pair of variations gets there.
|
|
133
|
+
|
|
134
|
+
Set `maxSpread` and `angle`/`elevation` become an AXIS instead. Each particle
|
|
135
|
+
leaves at a polar angle between `minSpread` and `maxSpread` off that axis,
|
|
136
|
+
around a uniformly random azimuth:
|
|
137
|
+
|
|
138
|
+
| `minSpread` … `maxSpread` | shape |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `0` … `0.5` | a tight cone along the axis |
|
|
141
|
+
| `0` … `Math.PI` | the whole sphere |
|
|
142
|
+
| either value at `Math.PI / 2` | a flat disc perpendicular to the axis |
|
|
143
|
+
|
|
144
|
+
An explosion on a planet's surface is a ring tangent to it: set the axis to the
|
|
145
|
+
surface normal and use `minSpread: Math.PI / 2 - 0.3, maxSpread: Math.PI / 2`.
|
|
146
|
+
Keeping the band at or inside ninety degrees is what stops debris tilting back
|
|
147
|
+
into the body it came off. `angleVariation` and `elevationVariation` are unused
|
|
148
|
+
while `maxSpread` is set.
|
|
149
|
+
|
|
150
|
+
### Sorting, and why it is keyed off the blend mode
|
|
151
|
+
|
|
152
|
+
A 3D burst only needs depth sorting when its blend is order dependent, and the
|
|
153
|
+
emitter decides that for you. Additive blending is commutative, so an additive
|
|
154
|
+
burst looks the same however it is ordered and pays nothing; `"normal"` is not,
|
|
155
|
+
so a 3D burst drawn with it is sorted back to front.
|
|
156
|
+
|
|
157
|
+
The two differ in more than cost. Additive sprites overlapping near the centre
|
|
158
|
+
of a burst sum to white however they are tinted, so debris that has to keep its
|
|
159
|
+
colour wants `textureAdditive: false` and accepts the sort. A common shape is
|
|
160
|
+
two emitters: a solid, sorted body of debris with a sparser additive spray of
|
|
161
|
+
sparks over it.
|
|
162
|
+
|
|
163
|
+
The default particle sprite is a filled SQUARE, which additive blending hides
|
|
164
|
+
and normal blending does not. Pass your own soft `image` for non-additive
|
|
165
|
+
debris.
|
|
166
|
+
|
|
31
167
|
For one-shot effects, `autoDestroyOnComplete: true` removes the emitter once its
|
|
32
168
|
particles have finished. The `onComplete` callback fires at that same moment
|
|
33
169
|
either way.
|
|
@@ -89,18 +225,48 @@ Omit `target` and feed it yourself with `trail.addPoint(x, y)` — the mode for
|
|
|
89
225
|
sword slash, where the ribbon follows a weapon tip rather than an object. Points
|
|
90
226
|
closer together than `minDistance` (default 4 px) are dropped.
|
|
91
227
|
|
|
92
|
-
**`Trail` is 2D
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
228
|
+
**`Trail` is 2D. Use `Trail3d` in a 3D scene** (20.9.0 and later). `Trail`
|
|
229
|
+
stores points without a `z` and draws connected screen-space quads, which carry
|
|
230
|
+
one depth for the whole renderable, so under a `Camera3d` the ribbon stays flat
|
|
231
|
+
and does not narrow along its own length.
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
const exhaust = new Trail3d(0, 0, {
|
|
235
|
+
target: ship, // samples pos.x / pos.y / pos.z
|
|
236
|
+
width: 6, // world units, and free to change per frame
|
|
237
|
+
lifetime: 260,
|
|
238
|
+
gradient: ["#eaffff", "#2ea8ff", "#2ea8ff00"],
|
|
239
|
+
});
|
|
240
|
+
exhaust.blendMode = "additive";
|
|
241
|
+
app.world.addChild(exhaust); // no z: it derives its own from its points
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`Trail3d` is a `Mesh`, which is most of the difference:
|
|
245
|
+
|
|
246
|
+
- `billboard` is spelled and valued exactly as on `Sprite3d` —
|
|
247
|
+
`"spherical"` (the default here) faces the camera on all axes,
|
|
248
|
+
`true` / `"cylindrical"` faces it but stays upright, `false` uses the
|
|
249
|
+
`ribbonNormal` you give it. `billboard: false, ribbonNormal: [0, -1, 0]`
|
|
250
|
+
lays the ribbon in the ground plane for a tyre mark or a wake.
|
|
251
|
+
- **`tint` works**, and is the per-frame colour lever. On `Trail` it does
|
|
252
|
+
nothing at all, because the primitive batcher reads the draw colour and never
|
|
253
|
+
the tint. `setColor()` / `setGradient()` rebuild the gradient for a change of
|
|
254
|
+
state; they allocate, so they are not per-frame.
|
|
255
|
+
|
|
256
|
+
**If the colour is going to change, author the gradient WHITE.** `tint`
|
|
257
|
+
multiplies, so tinting a cyan ribbon red gives muddy brown rather than red. A
|
|
258
|
+
neutral gradient hands the hue entirely to the tint, which is what lets one
|
|
259
|
+
lever take a trail from white through to red as something overheats.
|
|
260
|
+
- A post effect reaches it properly, where `Trail` has to force a screen
|
|
261
|
+
capture.
|
|
262
|
+
- It is **cheaper**: one indexed draw call, against one filled path per segment.
|
|
263
|
+
- `length` is fixed at construction, because the geometry is sized once.
|
|
264
|
+
Assigning `maxPoints` throws rather than silently truncating the ribbon, and
|
|
265
|
+
a `length` that is not a finite number throws at construction.
|
|
266
|
+
- It needs a `Camera3d`. Under a 2D camera it draws nothing and warns once.
|
|
96
267
|
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
depth to each particle so `Camera3d` projects them correctly. This is the
|
|
100
|
-
built-in answer for exhaust, ski tracks and speed trails in 3D.
|
|
101
|
-
- **A procedural `Mesh`** built from the object's recent path, with
|
|
102
|
-
`mesh.vertexColors` for a per-vertex gradient — more work, but a continuous
|
|
103
|
-
ribbon rather than discrete puffs.
|
|
268
|
+
A world-space `ParticleEmitter` (`referenceSpace: "world"`) is still the right
|
|
269
|
+
answer when you want discrete puffs rather than a continuous ribbon.
|
|
104
270
|
|
|
105
271
|
## Performance
|
|
106
272
|
|
|
@@ -123,12 +289,22 @@ tool for a trail behind an object moving *into* the scene. For that:
|
|
|
123
289
|
|
|
124
290
|
| symptom | cause |
|
|
125
291
|
|---|---|
|
|
126
|
-
| trail looks flat under a `Camera3d` | `Trail` is 2D
|
|
292
|
+
| trail looks flat under a `Camera3d` | `Trail` is 2D; use `Trail3d` |
|
|
293
|
+
| `Trail3d` draws nothing, with a console warning | it needs a `Camera3d`; the app's `cameraClass` is still the 2D default |
|
|
294
|
+
| `trail.tint` does nothing | `Trail` cannot be tinted at all; `Trail3d` can |
|
|
295
|
+
| assigning `maxPoints` throws | `Trail3d` sizes its geometry once; pass `length` to the constructor |
|
|
296
|
+
| `Trail3d: \`length\` must be a finite number` | a computed `length` came out `NaN`; it is refused rather than silently rendering nothing |
|
|
127
297
|
| emitter added but nothing appears | `streamParticles()` / `burstParticles()` never called |
|
|
128
298
|
| the cloud follows a moving emitter | default `referenceSpace: "local"` — use `"world"` for trails |
|
|
129
299
|
| particles do not travel far enough | settings tuned pre-20.2, when drawn travel was doubled |
|
|
130
300
|
| one-shot emitters accumulate | missing `autoDestroyOnComplete: true` |
|
|
131
301
|
| frame rate drops with many effects | an emitter per event instead of one re-aimed emitter |
|
|
302
|
+
| a 3D burst is invisible under a `Camera3d` | before 20.8 a particle was culled at twice its depth; update |
|
|
303
|
+
| the whole burst launches from one pixel | `width`/`height` are the spawn area and default to `1` |
|
|
304
|
+
| the burst sits down-right of its source | the spawn area starts at the emitter's position; offset by half |
|
|
305
|
+
| the middle of a burst goes white | additive sprites overlapping — `textureAdditive: false`, or spread them |
|
|
306
|
+
| debris chunks look like squares | the default sprite is a filled square; pass your own `image` |
|
|
307
|
+
| a ring tilts with the camera instead of staying on a surface | the launch axis is in WORLD axes, not the camera basis |
|
|
132
308
|
|
|
133
309
|
## Related skills
|
|
134
310
|
|
|
@@ -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
|
|
|
@@ -59,16 +59,28 @@ Anything spawned frequently — bullets, particles, pickups, damage numbers —
|
|
|
59
59
|
should be pooled rather than allocated:
|
|
60
60
|
|
|
61
61
|
```js
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
import { createPool } from "melonjs";
|
|
63
|
+
|
|
64
|
+
const bulletPool = createPool((x, y) => {
|
|
65
|
+
const instance = new Bullet(x, y);
|
|
66
|
+
return { instance, reset: (x, y) => instance.onResetEvent(x, y) };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const b = bulletPool.get(x, y); // recycled: reset(x, y)
|
|
70
|
+
// fresh: new Bullet(x, y)
|
|
71
|
+
world.removeChild(b); // returns to bulletPool automatically
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
The container returns a child to whichever pool built it, so removal is enough;
|
|
75
|
+
you only call `release()` yourself for something that was never added to the
|
|
76
|
+
world.
|
|
77
|
+
|
|
78
|
+
**`me.pool` is the older, string-keyed version and is deprecated since
|
|
79
|
+
18.0.0.** `pool.register(name, Class, true)` / `pool.pull(name)` still work and
|
|
80
|
+
print a one-off console notice, but a recycled object is no longer returned on
|
|
81
|
+
removal, so pooling through it now costs an allocation per spawn. To register a
|
|
82
|
+
class so a Tiled map can name it, use `registerTiledObjectClass` — that is a
|
|
83
|
+
separate registry and is not deprecated.
|
|
72
84
|
|
|
73
85
|
Two rules that cause subtle bugs when missed:
|
|
74
86
|
|
|
@@ -76,32 +88,89 @@ Two rules that cause subtle bugs when missed:
|
|
|
76
88
|
alpha, tint, scale, animation, velocity. Anything you forget carries into the
|
|
77
89
|
next use, which looks like a random visual glitch.
|
|
78
90
|
- **Pooled objects do not fire `onDestroyEvent` on removal.** Removal always
|
|
79
|
-
calls `onDeactivateEvent`, then tries
|
|
80
|
-
it fall through to `destroy()`,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
calls `onDeactivateEvent`, then tries to return the object to the pool that
|
|
92
|
+
built it; only when nothing owns it does it fall through to `destroy()`,
|
|
93
|
+
which is what calls `onDestroyEvent`. So a successfully recycled object never
|
|
94
|
+
sees it. Pair event subscriptions with `onActivateEvent` /
|
|
95
|
+
`onDeactivateEvent` instead, or you leak handlers.
|
|
96
|
+
|
|
97
|
+
- **`removeChild` is DEFERRED, which breaks a pool you re-lend in the same
|
|
98
|
+
frame.** The removal is queued and runs after the update and draw stack has
|
|
99
|
+
unwound, so an object you remove and immediately hand out again is torn out
|
|
100
|
+
from under its new owner when the queue drains: it vanishes, and the frame
|
|
101
|
+
after that the removal throws `Child is not mine.` Take it out immediately
|
|
102
|
+
instead, keeping the instance alive for reuse:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
world.removeChildNow(bullet, true); // keepalive: do NOT destroy it
|
|
106
|
+
this.freeList.push(bullet);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`removeChildNow(child, keepalive)` with `keepalive` true skips the destroy,
|
|
110
|
+
which is what you want for anything you own the lifetime of. This is the
|
|
111
|
+
usual cause of a pooled effect that flickers out one frame after it is
|
|
112
|
+
recycled.
|
|
113
|
+
|
|
114
|
+
**The engine's own classes are NOT in this pool.** They were registered in it
|
|
115
|
+
once, which bought nothing: most had recycling off, so `pool.pull("Sprite")`
|
|
116
|
+
was a plain construction behind a string key. The ones worth recycling have
|
|
117
|
+
typed pools instead, reached by key and fully typed:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const tween = getPool("tween").get(target);
|
|
121
|
+
const label = getPool("text").get(x, y, { font: "Arial", size: 12 });
|
|
122
|
+
getPool("text").release(label); // yours to release, as with particles
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A recycled label comes back with its **base-class** state reset as well as its
|
|
126
|
+
own: `floating`, `blendMode`, `alpha`, `tint`, the transform, the anchor, the
|
|
127
|
+
behaviour flags, any mask and any post effect, and `depth` back to 0. The
|
|
128
|
+
settings object names none of those, so set them again after `get()`. A pooled
|
|
129
|
+
HUD number that is not re-marked `floating` is drawn through the camera, i.e.
|
|
130
|
+
somewhere out in the world rather than on the screen, and `addChild(label, z)`
|
|
131
|
+
is what gives it its depth back.
|
|
132
|
+
|
|
133
|
+
`getPool` covers `vector2d`, `vector3d`, `point`, `matrix2d`, `matrix3d`,
|
|
134
|
+
`bounds`, `color`, `polygon`, `line`, `rectangle`, `roundedRectangle`,
|
|
135
|
+
`ellipse`, `tween`, `particle`, `text`, `colorLayer` and `bitmapTextData`.
|
|
136
|
+
|
|
137
|
+
`pool.register` still aliases every name you give it under an `me.` prefix,
|
|
138
|
+
pointing at the same entry, so `pool.pull("me.Bullet")` resolves identically —
|
|
139
|
+
and the same alias reaches the Tiled object factory, which is why a map
|
|
92
140
|
authored against melonJS 1.x still finds its classes. Prefer the unprefixed
|
|
93
141
|
name in new code; do not "correct" an `me.`-prefixed one, it is not broken.
|
|
94
142
|
|
|
95
143
|
## `scale()` is multiplicative
|
|
96
144
|
|
|
97
145
|
A pooled sprite arrives carrying its previous life's transform, and calling
|
|
98
|
-
`scale()` each frame compounds. For absolute
|
|
146
|
+
`scale()` each frame compounds. `rotate()` is the same. For absolute values,
|
|
147
|
+
reset first:
|
|
99
148
|
|
|
100
149
|
```js
|
|
101
150
|
sprite.currentTransform.identity();
|
|
102
151
|
sprite.currentTransform.scale(s, s, 1);
|
|
103
152
|
```
|
|
104
153
|
|
|
154
|
+
**Order matters, and it is the opposite of what reads naturally.**
|
|
155
|
+
`Matrix2d.scale` multiplies the matrix ROWS, so it applies AFTER whatever the
|
|
156
|
+
matrix already holds. Scale first and then rotate; rotate first and the scale
|
|
157
|
+
lands in the rotated frame and SHEARS anything non-uniform:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
// correct: a long thin quad, rotated
|
|
161
|
+
t.identity();
|
|
162
|
+
t.scale(length / texW, width / texH);
|
|
163
|
+
t.rotate(angle);
|
|
164
|
+
|
|
165
|
+
// wrong: the same quad arrives as a parallelogram
|
|
166
|
+
t.identity();
|
|
167
|
+
t.rotate(angle);
|
|
168
|
+
t.scale(length / texW, width / texH);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The symptom is a sprite that looks right at 0 and 90 degrees and skews in
|
|
172
|
+
between, which reads as a bad texture rather than as a transform bug.
|
|
173
|
+
|
|
105
174
|
## Culling and update cost
|
|
106
175
|
|
|
107
176
|
- `Container` recomputes `inViewport` for every child each frame against every
|
|
@@ -231,6 +300,45 @@ SwiftShader, where the absolute numbers are wildly pessimistic and the JS
|
|
|
231
300
|
profile shows the time as `(program)`, outside JS entirely. Ratios transfer;
|
|
232
301
|
milliseconds do not.
|
|
233
302
|
|
|
303
|
+
## Render targets, and what `hdr` costs
|
|
304
|
+
|
|
305
|
+
A render target is a canvas-sized GPU surface. The engine allocates them
|
|
306
|
+
lazily and only where a multi-pass chain actually needs one, which is worth
|
|
307
|
+
knowing before assuming an effect is expensive:
|
|
308
|
+
|
|
309
|
+
- a renderable with **no** post effects: none
|
|
310
|
+
- a renderable with **exactly one** post effect: still none. It takes the
|
|
311
|
+
`customShader` fast path and draws straight into whatever target is already
|
|
312
|
+
bound
|
|
313
|
+
- a **camera** with effects: a pair (one capture, one ping-pong intermediate)
|
|
314
|
+
- a sprite with **two or more** effects: its own pair
|
|
315
|
+
|
|
316
|
+
So the first lever is the count, not the cost of any one effect: going from
|
|
317
|
+
one effect to two on a sprite is what allocates, not going from a cheap shader
|
|
318
|
+
to an expensive one.
|
|
319
|
+
|
|
320
|
+
`hdr: true` changes the format of that camera pair from 8 bits per channel to
|
|
321
|
+
half-float, which is **8 bytes per pixel instead of 4**. At 1920×1080 that is
|
|
322
|
+
about 8 MB per target instead of 4 MB, so roughly +8 MB for the pair. With
|
|
323
|
+
`antiAlias` on, the capture half is also multisampled and its multisampled
|
|
324
|
+
surface takes the same format, so that part doubles too.
|
|
325
|
+
|
|
326
|
+
Nothing else moves. Sprite chains and the advanced-blend target stay 8-bit
|
|
327
|
+
whatever `hdr` says, for a correctness reason rather than a thrifty one: their
|
|
328
|
+
final blit composites with `ONE_MINUS_SRC_ALPHA`, and additive blending
|
|
329
|
+
accumulates alpha past 1 in a float target, which would turn that factor
|
|
330
|
+
negative and subtract the backdrop.
|
|
331
|
+
|
|
332
|
+
**With `hdr` off, nothing changes at all** — every target keeps the 8-bit
|
|
333
|
+
attachment it has always had, and a game that never adds a camera post-effect
|
|
334
|
+
allocates no render targets in the first place. The setting costs nothing
|
|
335
|
+
until something is actually using it.
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
// what you actually got, which is not always what you asked for
|
|
339
|
+
if (app.renderer.supportsHDR === false) { /* grade for a chain that clamps */ }
|
|
340
|
+
```
|
|
341
|
+
|
|
234
342
|
## Where the costs actually are
|
|
235
343
|
|
|
236
344
|
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
|
|
@@ -453,7 +500,8 @@ onCollisionStart(response, other) {
|
|
|
453
500
|
What actually happens per adapter:
|
|
454
501
|
|
|
455
502
|
- **built-in** — callbacks fire inline during `step()`. `Container.removeChild`
|
|
456
|
-
is deferred, so it is safe; `removeChildNow()
|
|
503
|
+
is deferred, so it is safe; `removeChildNow()`, and the internal `destroy()`
|
|
504
|
+
it calls, are immediate,
|
|
457
505
|
and the detector only survives them because it re-checks `body === undefined`
|
|
458
506
|
after every user callback.
|
|
459
507
|
- **planck** — `onCollisionStart` / `onCollisionEnd` are dispatched from
|
|
@@ -496,50 +544,76 @@ Two prerequisites that produce empty results rather than errors:
|
|
|
496
544
|
- On the **built-in** adapter, the broadphase is cleared and rebuilt inside
|
|
497
545
|
`world.update()`, so querying before the first update returns nothing.
|
|
498
546
|
|
|
499
|
-
## 3D collision: `Box3d` and the Z pushback
|
|
547
|
+
## 3D collision: `Box3d`, `Sphere` and the Z pushback
|
|
500
548
|
|
|
501
|
-
The narrowphase is not 2D-only.
|
|
502
|
-
and
|
|
503
|
-
|
|
549
|
+
The narrowphase is not 2D-only. Two shapes carry a depth extent — `Box3d` and
|
|
550
|
+
`Sphere` — and any pair of them is resolved in three dimensions, pushing back
|
|
551
|
+
along **Z** as well as X and Y.
|
|
504
552
|
|
|
505
553
|
```js
|
|
506
|
-
import { Box3d } from "melonjs";
|
|
554
|
+
import { Box3d, Sphere } from "melonjs";
|
|
507
555
|
|
|
508
|
-
// centre offset from the body, then half-extents on each axis
|
|
509
|
-
|
|
510
|
-
|
|
556
|
+
// a box: centre offset from the body, then half-extents on each axis
|
|
557
|
+
crate.body.addShape(new Box3d(0, 0, 0, 40, 60, 40));
|
|
558
|
+
// a sphere: centre offset, then one radius
|
|
559
|
+
rock.body.addShape(new Sphere(0, 0, 0, 30));
|
|
511
560
|
```
|
|
512
561
|
|
|
513
|
-
Reach for
|
|
562
|
+
Reach for these for anything moving in the XZ plane under a `Camera3d` — a
|
|
514
563
|
runner dodging obstacles, a 2.5D platformer, pickups on a course. Hand-rolling
|
|
515
564
|
a distance check there is the usual mistake, and it throws away the
|
|
516
565
|
penetration vector the response already carries.
|
|
517
566
|
|
|
518
|
-
**
|
|
519
|
-
|
|
520
|
-
|
|
567
|
+
**Which of the two.** A `Box3d` is world-axis-aligned and cannot be oriented:
|
|
568
|
+
it is right for walls, floors, crates and anything else that stays square to
|
|
569
|
+
the world, and wrong for anything that turns. A `Sphere` has no orientation to
|
|
570
|
+
get wrong, which makes it the shape for things that tumble, and for anything
|
|
571
|
+
laid out over a curved surface — on a globe every object's own "up" points a
|
|
572
|
+
different way, so no single axis-aligned box fits any of them. It is also the
|
|
573
|
+
cheaper test: one distance against a sum of radii.
|
|
574
|
+
|
|
575
|
+
**Z arrives beside `overlapN`, not inside it.** `overlapN` stays the 2D unit
|
|
576
|
+
vector it always was and `overlapNZ` carries the depth component, because
|
|
577
|
+
`Vector3d` does not extend `Vector2d` and widening the field would have broken
|
|
578
|
+
every existing 2D handler. The two together are a 3D unit vector, and
|
|
579
|
+
`overlapZ = overlapNZ * overlap` exactly as `overlapV = overlapN * overlap`.
|
|
580
|
+
|
|
581
|
+
**How much of the normal is in each half depends on the shapes.** A `Box3d`
|
|
582
|
+
pair separates along ONE world axis, so a depth contact leaves the 2D fields
|
|
583
|
+
at zero and a legacy 2D handler correctly applies no push. A `Sphere`
|
|
584
|
+
separates along the line between the two centres, which is generally diagonal:
|
|
585
|
+
all three components are non-zero at once. So subtract both halves rather than
|
|
586
|
+
branching on one:
|
|
521
587
|
|
|
522
588
|
```js
|
|
523
589
|
onCollision(response, other) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return
|
|
590
|
+
// works for either shape: the 2D half is zero when there is no 2D push
|
|
591
|
+
this.pos.x -= response.overlapV.x;
|
|
592
|
+
this.pos.y -= response.overlapV.y;
|
|
593
|
+
this.pos.z -= response.overlapZ;
|
|
594
|
+
return false;
|
|
529
595
|
}
|
|
530
596
|
```
|
|
531
597
|
|
|
532
|
-
|
|
533
|
-
|
|
598
|
+
Branching on `if (response.overlapNZ !== 0)` and then moving only `pos.z` is
|
|
599
|
+
the mistake to avoid: correct for a box pair, and for a sphere it throws away
|
|
600
|
+
the planar half of the contact.
|
|
534
601
|
|
|
535
|
-
**Mixed pairs degrade to 2D.** `Box3d` against a `Polygon`,
|
|
536
|
-
`RoundRect` or `Ellipse` tests the
|
|
537
|
-
shape as unbounded along Z
|
|
538
|
-
|
|
602
|
+
**Mixed pairs degrade to 2D.** A `Box3d` or a `Sphere` against a `Polygon`,
|
|
603
|
+
`Rectangle`, `RoundRect` or `Ellipse` tests the 3D shape's XY footprint and
|
|
604
|
+
treats the planar shape as unbounded along Z, which is what keeps a sphere or
|
|
605
|
+
box body colliding with an ordinary Tiled collision layer. `overlapZ` is zero
|
|
606
|
+
for those, since only one side has a depth to resolve. Give BOTH sides a 3D
|
|
607
|
+
shape when you want a depth result.
|
|
539
608
|
|
|
540
609
|
This is the narrowphase and is independent of the 3D broadphase queries above
|
|
541
|
-
— `querySphere` / `raycast3d` need `world.sortOn === "depth"`;
|
|
542
|
-
|
|
610
|
+
— `querySphere` / `raycast3d` need `world.sortOn === "depth"`; a 3D body shape
|
|
611
|
+
does not.
|
|
612
|
+
|
|
613
|
+
`raycast3d` measures a `Box3d` body as its box and a body that is one `Sphere`
|
|
614
|
+
as that sphere, both exactly. Anything else, including a body mixing the two,
|
|
615
|
+
is measured as the AABB containing its 3D shapes, and a body with none falls
|
|
616
|
+
back to a bounding sphere derived from the renderable's 2D bounds.
|
|
543
617
|
|
|
544
618
|
**Bounds are what the broadphase sorts by, and they come from the SHAPE.**
|
|
545
619
|
Every item is filed by its renderable's 2D `getBounds()`, so an object with no
|
|
@@ -698,6 +772,7 @@ use them. `adapter.capabilities` (`constraints`,
|
|
|
698
772
|
| 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
773
|
| 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
774
|
| 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 |
|
|
775
|
+
| 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
776
|
| a rotated renderable reports an unrotated bounding box | `autoTransform: false` opts `updateBounds()` out of the transform |
|
|
702
777
|
| 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
778
|
| forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
|