melonjs 20.6.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/build/application/defaultApplicationSettings.d.ts +5 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/settings.d.ts +88 -0
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/backend/engine.d.ts +0 -1
- package/build/audio/backend/sound.d.ts +0 -1
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +95 -8
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/geometries/box3d.d.ts +24 -6
- package/build/geometries/box3d.d.ts.map +1 -1
- package/build/geometries/ellipse.d.ts +14 -0
- package/build/geometries/ellipse.d.ts.map +1 -1
- package/build/geometries/polygon.d.ts +14 -0
- package/build/geometries/polygon.d.ts.map +1 -1
- package/build/geometries/sphere.d.ts +35 -6
- package/build/geometries/sphere.d.ts.map +1 -1
- package/build/index.d.ts +10 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23361 -20195
- package/build/index.js.map +4 -4
- package/build/input/pointerevent.d.ts +8 -0
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/lang/deprecated.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts +17 -31
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/gltf/GLTFScene.d.ts +40 -1
- package/build/level/gltf/GLTFScene.d.ts.map +1 -1
- package/build/level/level.d.ts +36 -0
- package/build/level/level.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +5 -2
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXObject.d.ts +0 -1
- package/build/level/tiled/TMXTile.d.ts +2 -1
- package/build/level/tiled/TMXTile.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +7 -4
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts +1 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/TMXTilesetGroup.d.ts +5 -0
- package/build/level/tiled/TMXTilesetGroup.d.ts.map +1 -1
- package/build/level/tiled/factories/shape.d.ts +0 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +6 -5
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +5 -0
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +8 -7
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +5 -0
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +19 -4
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/autodetect.d.ts +0 -4
- package/build/level/tiled/renderer/autodetect.d.ts.map +1 -1
- package/build/lighting/light2d.d.ts +68 -2
- package/build/lighting/light2d.d.ts.map +1 -1
- package/build/lighting/light3d.d.ts.map +1 -1
- package/build/loader/loader.d.ts +63 -0
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/loadingscreen.d.ts +1 -2
- package/build/loader/loadingscreen.d.ts.map +1 -1
- package/build/loader/parsers/fontface.d.ts +2 -0
- package/build/loader/parsers/fontface.d.ts.map +1 -1
- package/build/loader/parsers/mtl.d.ts.map +1 -1
- package/build/loader/parsers/shader.d.ts +0 -2
- package/build/math/color.d.ts +27 -4
- package/build/math/color.d.ts.map +1 -1
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +22 -1
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +69 -0
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +152 -3
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +9 -18
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts +0 -1
- package/build/physics/builtin/detector.d.ts.map +1 -1
- package/build/physics/builtin/sat.d.ts +0 -7
- package/build/physics/builtin/sat.d.ts.map +1 -1
- package/build/physics/builtin/sat3d.d.ts +46 -0
- package/build/physics/builtin/sat3d.d.ts.map +1 -1
- package/build/physics/physicseditor.d.ts +2 -0
- package/build/physics/physicseditor.d.ts.map +1 -0
- package/build/physics/response.d.ts +45 -62
- package/build/physics/response.d.ts.map +1 -1
- package/build/renderable/collectable.d.ts +6 -1
- package/build/renderable/collectable.d.ts.map +1 -1
- package/build/renderable/colorlayer.d.ts +4 -4
- package/build/renderable/colorlayer.d.ts.map +1 -1
- package/build/renderable/container.d.ts +23 -17
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/entity/entity.d.ts +14 -16
- package/build/renderable/entity/entity.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts +1 -1
- package/build/renderable/groundshadow.d.ts +1 -0
- package/build/renderable/groundshadow.d.ts.map +1 -1
- package/build/renderable/imagelayer.d.ts +12 -5
- package/build/renderable/imagelayer.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +2 -9
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +306 -5
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/nineslicesprite.d.ts +3 -13
- package/build/renderable/nineslicesprite.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +129 -28
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +77 -19
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/sprite3d.d.ts +27 -16
- package/build/renderable/sprite3d.d.ts.map +1 -1
- package/build/renderable/text/bitmaptext.d.ts +13 -3
- package/build/renderable/text/bitmaptext.d.ts.map +1 -1
- package/build/renderable/text/bitmaptextdata.d.ts +0 -4
- package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
- package/build/renderable/text/text.d.ts +5 -3
- package/build/renderable/text/text.d.ts.map +1 -1
- package/build/renderable/text/textmetrics.d.ts +2 -0
- package/build/renderable/text/textmetrics.d.ts.map +1 -1
- package/build/renderable/trail.d.ts +0 -1
- package/build/renderable/trail.d.ts.map +1 -1
- package/build/renderable/trigger.d.ts +39 -14
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/progressbar.d.ts +194 -0
- package/build/renderable/ui/progressbar.d.ts.map +1 -0
- package/build/renderable/ui/uibaseelement.d.ts +48 -3
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uispriteelement.d.ts +19 -3
- package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
- package/build/state/stage.d.ts +22 -0
- package/build/state/stage.d.ts.map +1 -1
- package/build/system/legacy_pool.d.ts +2 -2
- package/build/system/legacy_pool.d.ts.map +1 -1
- package/build/system/pool.d.ts +10 -0
- package/build/system/pool.d.ts.map +1 -1
- package/build/tweens/tween.d.ts +49 -2
- package/build/tweens/tween.d.ts.map +1 -1
- package/build/video/buffer/vertex.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +34 -23
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/blendEffect.d.ts +0 -1
- package/build/video/effects/bloom.d.ts +72 -0
- package/build/video/effects/bloom.d.ts.map +1 -0
- package/build/video/effects/blur.d.ts +3 -2
- package/build/video/effects/blur.d.ts.map +1 -1
- package/build/video/effects/chromaticAberration.d.ts +3 -2
- package/build/video/effects/chromaticAberration.d.ts.map +1 -1
- package/build/video/effects/colorMatrix.d.ts +3 -2
- package/build/video/effects/colorMatrix.d.ts.map +1 -1
- package/build/video/effects/desaturate.d.ts +10 -2
- package/build/video/effects/desaturate.d.ts.map +1 -1
- package/build/video/effects/dissolve.d.ts +3 -2
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +3 -2
- package/build/video/effects/dropShadow.d.ts.map +1 -1
- package/build/video/effects/flash.d.ts +3 -2
- package/build/video/effects/flash.d.ts.map +1 -1
- package/build/video/effects/glow.d.ts +3 -2
- package/build/video/effects/glow.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts +3 -2
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/invert.d.ts +10 -2
- package/build/video/effects/invert.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +3 -2
- package/build/video/effects/outline.d.ts.map +1 -1
- package/build/video/effects/pixelate.d.ts +3 -2
- package/build/video/effects/pixelate.d.ts.map +1 -1
- package/build/video/effects/radialGradient.d.ts +8 -3
- package/build/video/effects/radialGradient.d.ts.map +1 -1
- package/build/video/effects/scanline.d.ts +3 -2
- package/build/video/effects/scanline.d.ts.map +1 -1
- package/build/video/effects/sepia.d.ts +10 -2
- package/build/video/effects/sepia.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +10 -4
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +3 -2
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/tintPulse.d.ts +3 -2
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/tonemap.d.ts +121 -0
- package/build/video/effects/tonemap.d.ts.map +1 -0
- package/build/video/effects/vignette.d.ts +3 -2
- package/build/video/effects/vignette.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +3 -2
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/gpu/meshmaterial.d.ts +2 -0
- package/build/video/gpu/meshmaterial.d.ts.map +1 -0
- package/build/video/gpu/vertexformat.d.ts +26 -0
- package/build/video/gpu/vertexformat.d.ts.map +1 -1
- package/build/video/gradient.d.ts +0 -1
- package/build/video/gradient.d.ts.map +1 -1
- package/build/video/renderer.d.ts +111 -7
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/renderstate.d.ts +2 -0
- package/build/video/renderstate.d.ts.map +1 -1
- package/build/video/rendertarget/canvasrendertarget.d.ts +3 -4
- package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/render_target_pool.d.ts +0 -1
- package/build/video/rendertarget/webglrendertarget.d.ts +0 -1
- package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/webgpurendertarget.d.ts +18 -2
- package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +1 -0
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/texture/cache.d.ts +1 -3
- package/build/video/texture/frametexture.d.ts +0 -1
- package/build/video/texture/frametexture.d.ts.map +1 -1
- package/build/video/texture/noise_texture2d.d.ts +1 -0
- package/build/video/texture/noise_texture2d.d.ts.map +1 -1
- package/build/video/utils/autodetect.d.ts +0 -2
- package/build/video/webgl/batchers/batcher.d.ts +0 -9
- package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts +13 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/material_batcher.d.ts +4 -2
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/mesh_batcher.d.ts +2 -3
- package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts +4 -0
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/buffer/index.d.ts +0 -1
- package/build/video/webgl/buffer/retained_geometry.d.ts +0 -1
- package/build/video/webgl/glshader.d.ts +5 -3
- package/build/video/webgl/glshader.d.ts.map +1 -1
- package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts +0 -4
- package/build/video/webgl/shaders/multitexture-lit.d.ts.map +1 -1
- package/build/video/webgl/texture/store.d.ts +0 -1
- package/build/video/webgl/webgl_renderer.d.ts +63 -24
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts +17 -0
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -3
- package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts +0 -2
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/webgpu_batcher.d.ts +0 -2
- package/build/video/webgpu/batchers/webgpu_batcher.d.ts.map +1 -1
- package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
- package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts +0 -2
- package/build/video/webgpu/texture/frametexture.d.ts +0 -2
- package/build/video/webgpu/texture/frametexture.d.ts.map +1 -1
- package/build/video/webgpu/texture/store.d.ts +0 -1
- package/build/video/webgpu/texture/store.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +8 -75
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs-3d/SKILL.md +293 -19
- package/skills/melonjs-3d-assets/SKILL.md +123 -13
- package/skills/melonjs-camera-and-drawing/SKILL.md +57 -1
- package/skills/melonjs-effects-and-shaders/SKILL.md +341 -29
- package/skills/melonjs-input/SKILL.md +48 -4
- package/skills/melonjs-lighting/SKILL.md +45 -1
- package/skills/melonjs-loading-assets/SKILL.md +1 -1
- package/skills/melonjs-particles-and-trails/SKILL.md +106 -4
- package/skills/melonjs-performance/SKILL.md +79 -2
- package/skills/melonjs-physics/SKILL.md +420 -29
- package/skills/melonjs-renderables/SKILL.md +15 -1
- package/skills/melonjs-scenes-and-state/SKILL.md +121 -1
- package/skills/melonjs-tilemaps/SKILL.md +9 -0
- package/skills/melonjs-ui-and-text/SKILL.md +165 -4
- package/build/level/gltf/srgb.d.ts +0 -20
- package/build/level/gltf/srgb.d.ts.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-physics
|
|
3
|
-
description: "Use this skill for collision, physics bodies, movement, and spatial queries in melonJS — the built-in SAT world and the planck/matter adapters. Covers bodyDef vs Body, collision types and masks, the collision callback family and their firing rules, raycast/queryAABB/querySphere,
|
|
3
|
+
description: "Use this skill for collision, physics bodies, movement, and spatial queries in melonJS — the built-in SAT world and the planck/matter adapters. Covers bodyDef vs Body, collision types and masks, the collision callback family and their firing rules, raycast/queryAABB/querySphere, the behaviour differences between adapters, and which one to pick for a given game. Triggers on: which physics engine, choosing an adapter, rotation collision, rotating body, torque, joints, stacking, Body, bodyDef, collision, collisionType, collisionMask, onCollision, onCollisionStart, onCollisionActive, raycast, queryAABB, querySphere, PlanckAdapter, MatterAdapter, gravity, velocity, applyForce, isGrounded, SAT."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -19,6 +19,61 @@ license: MIT
|
|
|
19
19
|
| **`@melonjs/planck-adapter`** | Full rigid-body dynamics — stacking, joints, realistic restitution. |
|
|
20
20
|
| **`@melonjs/matter-adapter`** | Same class of thing, backed by matter-js. |
|
|
21
21
|
|
|
22
|
+
### Picking one
|
|
23
|
+
|
|
24
|
+
Start with the built-in. It is the default for a reason: no dependency, the
|
|
25
|
+
smallest bundle, and its position-based model is what most 2D games actually
|
|
26
|
+
want — a platformer's jump arc is a designed curve, not a simulated one, and
|
|
27
|
+
tuning gravity against a solver you are fighting is a bad afternoon.
|
|
28
|
+
|
|
29
|
+
**Move to an adapter when the game needs something the builtin cannot express**,
|
|
30
|
+
and these are the usual triggers:
|
|
31
|
+
|
|
32
|
+
- **Rotation that collides.** The clearest signal. The builtin's SAT never reads
|
|
33
|
+
`body.angle`, so a spinning shape collides as though it never turned. See
|
|
34
|
+
the note in *Built-in world quirks* below.
|
|
35
|
+
- **Torque, angular velocity, or spin from an impact.** Anything that should
|
|
36
|
+
start turning *because* it was hit.
|
|
37
|
+
- **Joints and constraints** — ragdolls, chains, ropes, vehicles, hinged doors.
|
|
38
|
+
The builtin has no concept of them.
|
|
39
|
+
- **Stacking and resting contacts.** Boxes that pile up and stay put need an
|
|
40
|
+
iterative solver. The builtin resolves each contact once per step with no
|
|
41
|
+
iteration pass, which splits three ways. A dynamic body resting on STATIC
|
|
42
|
+
geometry is exact, to the pixel, which is the platformer case it exists for.
|
|
43
|
+
A body squeezed between a dynamic body and static geometry is exact too: the
|
|
44
|
+
immovable side gets the casting vote and the other body absorbs the whole
|
|
45
|
+
correction, so a crate shoved against a wall stops at the wall. But a body
|
|
46
|
+
squeezed between two DYNAMIC bodies has no immovable side to defer to, so
|
|
47
|
+
those per-pair corrections still compete and it comes to rest slightly
|
|
48
|
+
overlapping. Three stacked 44px boxes rest flush on the floor with about 2px
|
|
49
|
+
of overlap inside the pile, and stay there: a stable equilibrium, not jitter
|
|
50
|
+
you can wait out or tune away.
|
|
51
|
+
- **Believable restitution and friction**, where a ball's bounce height and roll
|
|
52
|
+
should follow from its material rather than from code you wrote per case.
|
|
53
|
+
|
|
54
|
+
**Stay on the built-in when** the game is grid or tile based, movement is
|
|
55
|
+
authored rather than simulated, collision is "did these two boxes touch" plus a
|
|
56
|
+
push-out, or you are shipping to a tight bundle budget. Tiled maps, `Trigger`
|
|
57
|
+
and `Collectable` all work on every backend, so this is not a fork in the road
|
|
58
|
+
for level content.
|
|
59
|
+
|
|
60
|
+
**planck or matter?** Both are real rigid-body engines and either will do; the
|
|
61
|
+
adapters expose the same portable API, so a game can switch with an import
|
|
62
|
+
change and a gravity re-tune.
|
|
63
|
+
|
|
64
|
+
- **planck** (a Box2D port) is the more accurate and the more predictable under
|
|
65
|
+
stress: better stacking, better joint behaviour, a continuous-collision
|
|
66
|
+
`bullet` mode for fast movers. It works in **metres**, so it has a
|
|
67
|
+
`pixelsPerMeter` scale to think about, and its tuning vocabulary is Box2D's.
|
|
68
|
+
- **matter-js** works directly in **pixels**, which makes it the gentler
|
|
69
|
+
introduction, and its compound bodies and constraints are pleasant to author.
|
|
70
|
+
It is the softer solver: stacks settle less crisply and fast bodies need
|
|
71
|
+
`subSteps`.
|
|
72
|
+
|
|
73
|
+
If you have no preference and the game leans on stacking, joints or fast
|
|
74
|
+
projectiles, take planck. If you want to be up and running with the least
|
|
75
|
+
conversion to think about, take matter.
|
|
76
|
+
|
|
22
77
|
An adapter is an `Application` setting, not a plugin:
|
|
23
78
|
|
|
24
79
|
```js
|
|
@@ -44,6 +99,34 @@ body radius. Pick **one** adapter per game; `physic: "none"` disables physics
|
|
|
44
99
|
entirely — `world.step` skips the simulation and the world behaves as a pure
|
|
45
100
|
scene graph.
|
|
46
101
|
|
|
102
|
+
**Gravity is a different unit in every backend, and the defaults are NOT the
|
|
103
|
+
same speed.** Each is "Earth-like" in its own convention, but measured against
|
|
104
|
+
each other by timing a 200px free fall in one scene:
|
|
105
|
+
|
|
106
|
+
| backend | default | unit | measured fall |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| built-in | `0.98` | per FRAME, integrated against `timer.tick` | **~4450 px/s²** |
|
|
109
|
+
| matter | `{x: 0, y: 1}` | matter's own, after its internal `gravity.scale` | **~900 px/s²** |
|
|
110
|
+
| planck | `320` | pixels per second² | **~330 px/s²** |
|
|
111
|
+
|
|
112
|
+
That is a **thirteenfold spread**, so swapping the built-in world for an adapter
|
|
113
|
+
and keeping everything else the same makes the whole game feel different. It is
|
|
114
|
+
not a bug in either one: they simply do not share a unit.
|
|
115
|
+
|
|
116
|
+
To match a backend you are porting FROM, pick a target in px/s² and convert:
|
|
117
|
+
planck takes it directly; matter takes `target / 900`; the built-in takes
|
|
118
|
+
`target / 4450 * 0.98`, because it applies gravity per frame rather than per
|
|
119
|
+
second. Passing one number to two of them compares nothing: hand planck
|
|
120
|
+
matter's `{x: 0, y: 1}` and it runs at a three-hundredth of Earth gravity.
|
|
121
|
+
|
|
122
|
+
That mistake is quiet and it does not look like a gravity problem. Bodies fall
|
|
123
|
+
slowly enough that a spawner keeping a fixed interval starts constructing them
|
|
124
|
+
inside each other, and **a body born overlapping another is outside what a
|
|
125
|
+
solver promises to fix**: the contact normals of an interlocked pair can cancel
|
|
126
|
+
out, leaving the position solver with nowhere to push. The symptom is two
|
|
127
|
+
shapes permanently fused at rest, which reads as a broken collision shape
|
|
128
|
+
rather than as a bad gravity constant. Spawn clear of anything already there.
|
|
129
|
+
|
|
47
130
|
`world.physic` carries the active adapter's label (`"builtin"`, `"planck"`,
|
|
48
131
|
`"matter"`, `"none"`), so game code can branch without importing the class.
|
|
49
132
|
|
|
@@ -72,6 +155,19 @@ world; the broadphase picks the renderable up on the next `world.update()`.
|
|
|
72
155
|
Calling `adapter.addBody()` yourself registers the body but leaves the
|
|
73
156
|
renderable out of the scene graph, so it integrates but never collides.
|
|
74
157
|
|
|
158
|
+
**`fixedRotation` follows the engine, so bodies rotate.** Both matter and
|
|
159
|
+
planck default it to `false`, and so do the adapters from matter-adapter 1.4.0
|
|
160
|
+
and planck-adapter 1.5.0. Pass `fixedRotation: true` for anything that must
|
|
161
|
+
stay upright, such as a platformer actor. Earlier adapter versions locked
|
|
162
|
+
rotation unless told otherwise, which inverted the engine they wrap: bodies
|
|
163
|
+
landed holding whatever angle they were given and no collision could turn them,
|
|
164
|
+
while `setAngle()` still worked, so a body could look rotated while being
|
|
165
|
+
unable to rotate.
|
|
166
|
+
|
|
167
|
+
`frictionAir` damps **rotation as well as translation** on both adapters, so
|
|
168
|
+
it is also what settles a spin. Left at `0` a body that starts turning never
|
|
169
|
+
stops, which only became visible once rotation stopped being locked.
|
|
170
|
+
|
|
75
171
|
Other portable `bodyDef` fields: `density`, `frictionAir` (number or `{x, y}`),
|
|
76
172
|
`friction`, `restitution`, `gravityScale`, `maxVelocity`, `fixedRotation`,
|
|
77
173
|
`isSensor`, `userData`. Not every adapter honours every one — the built-in
|
|
@@ -106,6 +202,190 @@ For contacts reported per *shape pair* rather than per body pair, define
|
|
|
106
202
|
Declaring at least one of them opts into shape-pair enumeration; declaring none
|
|
107
203
|
costs nothing.
|
|
108
204
|
|
|
205
|
+
### Shapes drawn in a physics-shape editor
|
|
206
|
+
|
|
207
|
+
A visual collision-shape editor exports a JSON file holding a map of body name
|
|
208
|
+
to the fixtures authored for it. Preload it like any other JSON, then name it
|
|
209
|
+
from `bodyDef.shapes` with `id` naming the body to read:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
// in the preloader
|
|
213
|
+
{ name: "shapes", type: "json", src: "data/physics/shapes.json" }
|
|
214
|
+
|
|
215
|
+
// then, on the renderable
|
|
216
|
+
this.bodyDef = { type: "dynamic", shapes: "shapes", id: "hotdog" };
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This works identically on the built-in, matter and planck adapters — the file is
|
|
220
|
+
resolved into real shapes before any adapter is handed the definition. Circles
|
|
221
|
+
and polygons both load, and a concave outline (exported already decomposed into
|
|
222
|
+
convex pieces) becomes one `Polygon` per piece.
|
|
223
|
+
|
|
224
|
+
The density, friction and bounce the shapes were authored with are carried onto
|
|
225
|
+
the body definition too, each honoured by whichever backends support it — the
|
|
226
|
+
built-in adapter ignores contact `friction`, as it does for a hand-written
|
|
227
|
+
definition. The format carries these per fixture, but only planck could honour
|
|
228
|
+
that, so they reduce onto the body and a file whose fixtures disagree warns. An
|
|
229
|
+
explicit field on the definition always wins:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
// bounces at 0.9 whatever the file says
|
|
233
|
+
this.bodyDef = { type: "dynamic", shapes: "shapes", id: "hotdog", restitution: 0.9 };
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The exporting tool's own filter block is **not** read, in any spelling. Its
|
|
237
|
+
category and mask bits are numbers in that tool's namespace rather than
|
|
238
|
+
{@link collision.types}, and a template writes its defaults for every fixture
|
|
239
|
+
whether or not you set anything — importing them would silently tag a shape as
|
|
240
|
+
something it is not and stop it colliding. Set `collisionType` / `collisionMask`
|
|
241
|
+
on the body definition instead. A file written by hand may carry those two names
|
|
242
|
+
per fixture, and `isSensor`, which mean the same thing here as there.
|
|
243
|
+
|
|
244
|
+
Authored shapes and imported ones can sit in the same list.
|
|
245
|
+
|
|
246
|
+
Two bodies must never share one `shapes` array: a body **owns** its shapes and
|
|
247
|
+
rotating it mutates them in place. Naming the file sidesteps this — every body
|
|
248
|
+
resolved from it mints its own shapes.
|
|
249
|
+
|
|
250
|
+
`Body#fromJSON()` and passing an exported list to `addShape()` are the old,
|
|
251
|
+
built-in-only spelling of this and are deprecated since 20.7.0.
|
|
252
|
+
|
|
253
|
+
### Which shapes each backend can actually simulate
|
|
254
|
+
|
|
255
|
+
| shape | builtin | matter | planck |
|
|
256
|
+
|---|---|---|---|
|
|
257
|
+
| `Rect`, `Polygon` | exact | exact | exact, up to 12 vertices |
|
|
258
|
+
| `RoundRect` | exact | exact (decomposed) | sampled down to 12 vertices |
|
|
259
|
+
| `Ellipse` | exact | circle of the average radius | circle of the average radius |
|
|
260
|
+
| `Line` | exact | thin quad along the segment | thin quad along the segment |
|
|
261
|
+
| `Box3d`, `Sphere` | exact, and in 3D | **throws** | **throws** |
|
|
262
|
+
| `Point` | supported | **throws** | **throws** |
|
|
263
|
+
|
|
264
|
+
Two of those are worth knowing before you author geometry. Box2D caps a
|
|
265
|
+
polygon's vertex count and says nothing when you go over: measured, a
|
|
266
|
+
16-point outline comes back with 12 vertices and a 36-point one collapses
|
|
267
|
+
from 70x36 to 47x18. A `RoundRect` carries 36 points, four corners of nine
|
|
268
|
+
arc segments each, so it is the usual way to meet that cap; the adapter now
|
|
269
|
+
samples it down and keeps the extent. Anything the 2D backends cannot express
|
|
270
|
+
is refused loudly on both, rather than leaving a body with no collision
|
|
271
|
+
geometry that silently never collides.
|
|
272
|
+
|
|
273
|
+
Whatever a backend really simulates is what `getBodyShapes()` reports, so the
|
|
274
|
+
debug panel is the fastest way to see an approximation for what it is.
|
|
275
|
+
|
|
276
|
+
### `Line` shapes, and why slopes need a rigid-body adapter
|
|
277
|
+
|
|
278
|
+
A `Line` is two points and no area, and it is what Tiled emits for every
|
|
279
|
+
polyline, so it is the natural way to author a slope or a strip of ground.
|
|
280
|
+
Neither rigid-body engine has a segment primitive, so both simulate one as a
|
|
281
|
+
thin quad following the segment (2px), which is what `getBodyShapes()` reports
|
|
282
|
+
back. Before 20.7 each substituted something of its own instead: matter the
|
|
283
|
+
line's bounding box, planck a one-metre square wherever the body happened to
|
|
284
|
+
be. Neither logged anything.
|
|
285
|
+
|
|
286
|
+
The builtin solver resolves a sloped segment too, and correctly: a body lands
|
|
287
|
+
on it and SLIDES down it, holding a constant distance from the surface while
|
|
288
|
+
its x advances. What it has no notion of is SURFACE friction. `def.friction`
|
|
289
|
+
is ignored entirely and `body.friction` is a per-step velocity damping vector
|
|
290
|
+
fed from `def.frictionAir`, so nothing removes the tangential component of
|
|
291
|
+
gravity. A body on an incline therefore accelerates down it forever and leaves
|
|
292
|
+
at the end, and in a valley of segments it swings from arm to arm for a while
|
|
293
|
+
before coming to rest on the flat. (It used to escape through an arm instead;
|
|
294
|
+
that was a separate defect in how a body was separated from a shape list, fixed
|
|
295
|
+
in 20.7.)
|
|
296
|
+
|
|
297
|
+
matter and planck both have real surface friction, so the same valley holds a
|
|
298
|
+
body still.
|
|
299
|
+
|
|
300
|
+
**Several shapes on one body.** A Tiled polyline arrives as ONE body carrying
|
|
301
|
+
one shape per segment, and until 20.7 a body resting on such a ground could
|
|
302
|
+
sink through one of the segments and drop out. The narrowphase reports the
|
|
303
|
+
FIRST overlapping shape pair it finds, and the compound resolution pass
|
|
304
|
+
re-ran that same first-hit-wins scan, so it kept re-resolving the pair it had
|
|
305
|
+
already resolved while a neighbour was penetrated unmeasured. Every pair is
|
|
306
|
+
enumerated now. If you are ever diagnosing something similar, the experiment
|
|
307
|
+
that localises it is to re-run the same geometry as SEVERAL single-shape
|
|
308
|
+
bodies: if that holds and one multi-shape body does not, the fault is in the
|
|
309
|
+
compound path rather than in the shape type.
|
|
310
|
+
|
|
311
|
+
Do not diagnose this by dropping a body and reading its final `y`. A body that
|
|
312
|
+
slid off the end of a ramp and a body that fell straight through it both end up
|
|
313
|
+
far below, and they look identical in that one number. Track the body against
|
|
314
|
+
the surface height at its CURRENT x instead: riding the slope shows a constant
|
|
315
|
+
offset while x advances.
|
|
316
|
+
|
|
317
|
+
So segments collide on every backend. If you need a body to come to REST on a
|
|
318
|
+
sloped one, use matter or planck, or damp it yourself: the builtin will slide
|
|
319
|
+
it down the incline indefinitely. The `Line Collision` example draws the
|
|
320
|
+
authored segment against the geometry each backend reports, which is the
|
|
321
|
+
quickest way to see what a backend is really simulating.
|
|
322
|
+
|
|
323
|
+
### `anchorPoint` moves the collision shapes with the drawing
|
|
324
|
+
|
|
325
|
+
`anchorPoint` says where in its own bounds a renderable sits on its `pos`:
|
|
326
|
+
`updateBounds()` translates its bounds by `-width * anchorPoint.x, -height *
|
|
327
|
+
anchorPoint.y`, and `preDraw` shifts its rendering by the same amount. A
|
|
328
|
+
body's collision shapes are measured from that same frame, so a shape of
|
|
329
|
+
`new Rect(0, 0, width, height)` covers the sprite whatever the anchor is, and
|
|
330
|
+
a shape's own `pos` still offsets it inside that frame (a small hitbox at the
|
|
331
|
+
feet, a hurtbox at the head).
|
|
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
|
+
|
|
356
|
+
This was broken until 20.7: shapes were measured from `pos` regardless of the
|
|
357
|
+
anchor, so anything not anchored at its top-left corner collided where it was
|
|
358
|
+
not drawn, by half a body at the default centred anchor and by a full body
|
|
359
|
+
height at the bottom anchor a platformer actor uses. If you compensated by
|
|
360
|
+
offsetting your shapes by hand, remove those offsets. `Entity` and Tiled
|
|
361
|
+
objects set their own anchor to `(0, 0)`, so nothing built either of those
|
|
362
|
+
ways is affected.
|
|
363
|
+
|
|
364
|
+
The debug panel is the quickest check: green is the renderable's bounds, red
|
|
365
|
+
is what actually collides, and the two now agree.
|
|
366
|
+
|
|
367
|
+
One renderable does not take that offset at all: one that has cleared
|
|
368
|
+
`applyAnchorTransform`, the flag `preDraw` reads before shifting anything.
|
|
369
|
+
`GLTFModel` sets it `false` outright and `Mesh` clears it under a `Camera3d`,
|
|
370
|
+
because both emit world coordinates and pivot about their own model origin
|
|
371
|
+
rather than a bounds box, so they draw at `pos` whatever their `anchorPoint`
|
|
372
|
+
still holds. Their bodies are built there too, on every backend, and
|
|
373
|
+
`raycast3d` reports hits in that same frame. If you clear the flag on a
|
|
374
|
+
renderable of your own, the same applies: you place it, and your shapes are
|
|
375
|
+
measured from `pos`.
|
|
376
|
+
|
|
377
|
+
### `autoTransform: false` opts out of rotated bounds
|
|
378
|
+
|
|
379
|
+
`updateBounds()` only applies `currentTransform` when `autoTransform` is
|
|
380
|
+
`true`. Turn it off and the renderable's bounds stay the UNROTATED frame
|
|
381
|
+
while the sprite turns inside them, which quietly feeds a wrong box to
|
|
382
|
+
frustum culling and to the broadphase. Measured on a 60x20 box at 90
|
|
383
|
+
degrees: `20x60` with `autoTransform` on, `60x20` with it off.
|
|
384
|
+
|
|
385
|
+
That flag means "I place myself", so it is a fair contract, but reach for it
|
|
386
|
+
only when you really are drawing in your own frame, and remember the bounds
|
|
387
|
+
are yours to keep honest then.
|
|
388
|
+
|
|
109
389
|
## Move with forces, not by assigning position
|
|
110
390
|
|
|
111
391
|
```js
|
|
@@ -144,7 +424,30 @@ survives but fights collision resolution and jitters. To teleport, go through
|
|
|
144
424
|
The lifecycle is `onCollisionStart` (contact begins), `onCollisionActive` (every
|
|
145
425
|
step while it persists) and `onCollisionEnd` (contact breaks). None of them are
|
|
146
426
|
defined on `Renderable` — you just declare the ones you want and the dispatcher
|
|
147
|
-
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:
|
|
148
451
|
|
|
149
452
|
> On the built-in world, `onCollision` fires **twice per frame** for
|
|
150
453
|
> dynamic-dynamic pairs — once per outer-loop visit — and gets the raw SAT
|
|
@@ -240,50 +543,76 @@ Two prerequisites that produce empty results rather than errors:
|
|
|
240
543
|
- On the **built-in** adapter, the broadphase is cleared and rebuilt inside
|
|
241
544
|
`world.update()`, so querying before the first update returns nothing.
|
|
242
545
|
|
|
243
|
-
## 3D collision: `Box3d` and the Z pushback
|
|
546
|
+
## 3D collision: `Box3d`, `Sphere` and the Z pushback
|
|
244
547
|
|
|
245
|
-
The narrowphase is not 2D-only.
|
|
246
|
-
and
|
|
247
|
-
|
|
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.
|
|
248
551
|
|
|
249
552
|
```js
|
|
250
|
-
import { Box3d } from "melonjs";
|
|
553
|
+
import { Box3d, Sphere } from "melonjs";
|
|
251
554
|
|
|
252
|
-
// centre offset from the body, then half-extents on each axis
|
|
253
|
-
|
|
254
|
-
|
|
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));
|
|
255
559
|
```
|
|
256
560
|
|
|
257
|
-
Reach for
|
|
561
|
+
Reach for these for anything moving in the XZ plane under a `Camera3d` — a
|
|
258
562
|
runner dodging obstacles, a 2.5D platformer, pickups on a course. Hand-rolling
|
|
259
563
|
a distance check there is the usual mistake, and it throws away the
|
|
260
564
|
penetration vector the response already carries.
|
|
261
565
|
|
|
262
|
-
**
|
|
263
|
-
|
|
264
|
-
|
|
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:
|
|
265
586
|
|
|
266
587
|
```js
|
|
267
588
|
onCollision(response, other) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
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;
|
|
273
594
|
}
|
|
274
595
|
```
|
|
275
596
|
|
|
276
|
-
|
|
277
|
-
|
|
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.
|
|
278
600
|
|
|
279
|
-
**Mixed pairs degrade to 2D.** `Box3d` against a `Polygon`,
|
|
280
|
-
`RoundRect` or `Ellipse` tests the
|
|
281
|
-
shape as unbounded along Z
|
|
282
|
-
|
|
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.
|
|
283
607
|
|
|
284
608
|
This is the narrowphase and is independent of the 3D broadphase queries above
|
|
285
|
-
— `querySphere` / `raycast3d` need `world.sortOn === "depth"`;
|
|
286
|
-
|
|
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.
|
|
287
616
|
|
|
288
617
|
**Bounds are what the broadphase sorts by, and they come from the SHAPE.**
|
|
289
618
|
Every item is filed by its renderable's 2D `getBounds()`, so an object with no
|
|
@@ -311,6 +640,13 @@ real rigid-body engine:
|
|
|
311
640
|
- **Dynamic-dynamic collision is position-based, not Newtonian.** Separation is
|
|
312
641
|
mass-proportional but the velocity response is per-body cancellation — two
|
|
313
642
|
equal-mass bodies do not exchange momentum the way you would expect.
|
|
643
|
+
- **An immovable contact wins the step.** Each step records, per body, which
|
|
644
|
+
directions it has already been pushed out of, and a later dynamic-dynamic
|
|
645
|
+
contact will not push it back in one of those directions: the other body
|
|
646
|
+
takes the whole correction instead. A light body shoving a heavy one against
|
|
647
|
+
a wall therefore stops dead at the wall rather than driving it through, and
|
|
648
|
+
the shover absorbs all of the separation. The record lasts one step and is
|
|
649
|
+
not contact state — it says nothing about what a body is touching.
|
|
314
650
|
- **Gravity defaults to `(0, 0.98)`** — pixels per frame², not m/s². Mutate
|
|
315
651
|
`app.world.adapter.gravity` at runtime, or pass
|
|
316
652
|
`physic: new BuiltinAdapter({ gravity })` to override it. `body.gravityScale`
|
|
@@ -319,8 +655,7 @@ real rigid-body engine:
|
|
|
319
655
|
- **`applyForce(x, y)` is linear only** unless you pass the optional
|
|
320
656
|
`(pointX, pointY)` application point, which generates a torque
|
|
321
657
|
`τ = r × F` into `body.angularVelocity` via `body.pseudoInertia`.
|
|
322
|
-
`applyTorque(τ)` is the direct form.
|
|
323
|
-
collisions stay axis-aligned.
|
|
658
|
+
`applyTorque(τ)` is the direct form.
|
|
324
659
|
- **Force accumulators reset at end-of-step** — for every body, including
|
|
325
660
|
static, paused and off-screen ones — so forces must be applied every frame to
|
|
326
661
|
persist.
|
|
@@ -342,6 +677,50 @@ real rigid-body engine:
|
|
|
342
677
|
- **`addBody` throws on double-registration** for a renderable that is already
|
|
343
678
|
adapter-managed.
|
|
344
679
|
|
|
680
|
+
### Rotation collides on planck and matter, but not on the built-in solver
|
|
681
|
+
|
|
682
|
+
This is the sharpest behaviour difference between the adapters, and it is
|
|
683
|
+
silent — nothing warns you.
|
|
684
|
+
|
|
685
|
+
| adapter | a rotating body |
|
|
686
|
+
| --- | --- |
|
|
687
|
+
| **builtin** | rotates **visually only**. `body.angle`, `setAngle()` and angular integration write `renderable.currentTransform`; the collision shapes are never rotated, so a spinning sprite collides as if it were upright |
|
|
688
|
+
| **planck** / **matter** | rotates for real — the solver owns the body's transform and collision follows it |
|
|
689
|
+
|
|
690
|
+
So a turret, a swinging platform or a tumbling crate that must *collide*
|
|
691
|
+
rotated needs planck or matter. Under the builtin solver, approximate it with a
|
|
692
|
+
shape that is close enough at every angle (a circle, or a square for
|
|
693
|
+
90° steps), or rotate the shapes yourself with `body.rotate(angle)` — which
|
|
694
|
+
does rotate them, unlike `setAngle()`.
|
|
695
|
+
|
|
696
|
+
`body.rotate()` applies a **delta** and does not update `body.angle`; the two
|
|
697
|
+
are unlinked, so a body driven by `rotate()` still reports an angle of `0`.
|
|
698
|
+
Track it yourself if you need to read it back. Before 20.7 it also threw on a
|
|
699
|
+
`Box3d` or `Point` shape, and did not tell its owner that the body's bounds had
|
|
700
|
+
grown.
|
|
701
|
+
|
|
702
|
+
`adapter.getBodyShapes(renderable)` reports whichever is true: the geometry
|
|
703
|
+
the engine is actually simulating on planck and matter (matter-adapter 1.3.0 /
|
|
704
|
+
planck-adapter 1.4.0 and later), unrotated shapes on the builtin solver because
|
|
705
|
+
there they genuinely are. That is what the debug overlay draws, so the hitbox
|
|
706
|
+
you see is the hitbox that collides — a sprite spinning inside a stationary red
|
|
707
|
+
box on the builtin is the solver telling you the truth, not a drawing bug.
|
|
708
|
+
|
|
709
|
+
On planck and matter that report comes from the engine rather than from your
|
|
710
|
+
definitions, so it shows the approximations they make: an `Ellipse` appears as
|
|
711
|
+
the circle of average radius it is simulated as, a concave polygon as the
|
|
712
|
+
convex pieces it was decomposed into, and a `Rect` as a `Polygon`. Your
|
|
713
|
+
authored shapes are untouched and still readable on `renderable.bodyDef.shapes`.
|
|
714
|
+
|
|
715
|
+
**If you need rotation to affect collision, use planck or matter.** That is the
|
|
716
|
+
dividing line: the builtin is a position-based solver whose SAT never reads
|
|
717
|
+
`body.angle`, so rotation there is a visual property. `body.rotate(angle)` is
|
|
718
|
+
the workaround, not a rotating body — it permanently moves the shapes, does not
|
|
719
|
+
track an angle, and gives you no angular velocity, torque or rotational
|
|
720
|
+
response from a collision. A game whose collision genuinely turns (a spinning
|
|
721
|
+
hazard, a swinging bridge, a car, anything with torque) wants a real rigid-body
|
|
722
|
+
adapter rather than a workaround.
|
|
723
|
+
|
|
345
724
|
## Porting between adapters
|
|
346
725
|
|
|
347
726
|
Same call, different magnitude — these are the ones that waste an afternoon:
|
|
@@ -389,11 +768,23 @@ use them. `adapter.capabilities` (`constraints`,
|
|
|
389
768
|
| `response.depth` / `response.normal` are `undefined` | reading a legacy `onCollision` response — it carries `overlap` / `overlapN` |
|
|
390
769
|
| `onCollisionEnd` handler throws on `response` | built-in dispatches it with `undefined` |
|
|
391
770
|
| off-screen bodies stop simulating | built-in gating on `inViewport`; set `alwaysUpdate` |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
775
|
+
| a rotated renderable reports an unrotated bounding box | `autoTransform: false` opts `updateBounds()` out of the transform |
|
|
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 |
|
|
392
777
|
| forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
|
|
393
778
|
| `body.position` disagrees with `renderable.pos` on matter | matter stores the centroid, melonJS the top-left — the adapter offsets between them |
|
|
779
|
+
| `bodyDef.shapes: "…"` throws "is not loaded" | the name must match the `name` the JSON was preloaded under, not its filename |
|
|
780
|
+
| everything falls far too fast or too slow after swapping backends | gravity is a different unit per backend; see the table above |
|
|
781
|
+
| debris lands holding its spawn angle and never tumbles | `fixedRotation: true`, or matter-adapter < 1.4.0 / planck-adapter < 1.5.0 |
|
|
782
|
+
| a rotated sprite collides as if upright | the built-in solver rotates visually only — `body.angle` never rotates the shapes. Use planck/matter, or `body.rotate(angle)` |
|
|
783
|
+
| the debug hitbox does not follow a spinning body | pre-1.2.1 matter-adapter / pre-1.3.1 planck-adapter returned the authored shapes rather than the rotated ones |
|
|
394
784
|
| a Tiled polyline becomes a solid box on matter | matter cannot build a zero-area polygon; the adapter falls back to its AABB |
|
|
395
785
|
|
|
396
786
|
## Related skills
|
|
397
787
|
|
|
398
788
|
- `melonjs-renderables` — `isKinematic`, which also gates the broadphase
|
|
399
789
|
- `melonjs-tilemaps` — collision shapes authored in Tiled
|
|
790
|
+
- `melonjs-loading-assets` — preloading a shape editor's export as JSON
|
|
@@ -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
|
|
|
@@ -215,6 +215,18 @@ sprite.alpha = 0; // hides it
|
|
|
215
215
|
|
|
216
216
|
Use `alpha` for a blink, or remove the child for a long absence.
|
|
217
217
|
|
|
218
|
+
## A mask belongs to you, not to the renderable
|
|
219
|
+
|
|
220
|
+
`renderable.mask` takes a shape you created, and `destroy()` releases the
|
|
221
|
+
reference without recycling it: the same instance may still be masking
|
|
222
|
+
something else, so the engine has no claim on it. If you pool masks, release
|
|
223
|
+
them yourself.
|
|
224
|
+
|
|
225
|
+
Before 20.7.0 `destroy()` tried to recycle it and **threw** for every type
|
|
226
|
+
`mask` accepts, because it used the legacy name-keyed pool and no geometry
|
|
227
|
+
class is registered there. It failed mid-teardown, so a masked renderable
|
|
228
|
+
could not be destroyed or removed without keepalive at all.
|
|
229
|
+
|
|
218
230
|
## Update and draw
|
|
219
231
|
|
|
220
232
|
```js
|
|
@@ -260,6 +272,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
|
|
|
260
272
|
| object draws at its parent's origin, ignores its position | `draw()` not reading `this.pos` |
|
|
261
273
|
| pointer handler registered but never fires | `isKinematic` left at its `true` default |
|
|
262
274
|
| wrong draw order | anything other than `addChild(child, z)` — see section 3 |
|
|
275
|
+
| `me.pool: object ... cannot be recycled` while destroying | a mask on a renderable, before 20.7.0 |
|
|
263
276
|
| effect cannot be re-enabled after removal | `removePostEffect()` destroyed it |
|
|
264
277
|
| effect silently does nothing | running on the Canvas renderer |
|
|
265
278
|
| object appears frozen while its state changes | `update()` not returning `true` |
|
|
@@ -271,6 +284,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
|
|
|
271
284
|
| `isRenderable = false` did not hide the object | it gates bounds, not drawing — use `alpha` |
|
|
272
285
|
|
|
273
286
|
## Related skills
|
|
287
|
+
- `melonjs-scenes-and-state` — `Tween` and the easing families, for animating any of this
|
|
274
288
|
|
|
275
289
|
- `melonjs-getting-started` — Application lifecycle and the scene graph
|
|
276
290
|
- `melonjs-20-migration` — the deprecated `shader` property and other pre-20 APIs
|