melonjs 20.2.0 → 20.3.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 +86 -8
- package/build/application/settings.d.ts +3 -2
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +2 -2
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/backend/buffer.d.ts +26 -0
- package/build/audio/backend/buffer.d.ts.map +1 -0
- package/build/audio/backend/cache.d.ts +2 -0
- package/build/audio/backend/cache.d.ts.map +1 -0
- package/build/audio/backend/core.d.ts +24 -0
- package/build/audio/backend/core.d.ts.map +1 -0
- package/build/audio/backend/engine.d.ts +54 -0
- package/build/audio/backend/engine.d.ts.map +1 -0
- package/build/audio/backend/sound.d.ts +79 -0
- package/build/audio/backend/sound.d.ts.map +1 -0
- package/build/audio/backend/spatial.d.ts +191 -0
- package/build/audio/backend/spatial.d.ts.map +1 -0
- package/build/audio/backend/types.d.ts +97 -0
- package/build/audio/backend/types.d.ts.map +1 -0
- package/build/audio/backend/voice.d.ts +33 -0
- package/build/audio/backend/voice.d.ts.map +1 -0
- package/build/audio/playback.d.ts +49 -4
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/{backend.d.ts → state.d.ts} +13 -11
- package/build/audio/state.d.ts.map +1 -0
- package/build/audio/types.d.ts +93 -1
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +5 -5
- package/build/const.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +8793 -10171
- package/build/index.js.map +4 -4
- package/build/input/input.d.ts +19 -0
- package/build/input/input.d.ts.map +1 -1
- package/build/lang/deprecated.d.ts +4 -2
- package/build/lang/deprecated.d.ts.map +1 -1
- package/build/level/gltf/GLTFScene.d.ts +12 -0
- package/build/level/gltf/GLTFScene.d.ts.map +1 -1
- package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
- package/build/loader/loader.d.ts +4 -40
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/parsers/audio.d.ts +31 -0
- package/build/loader/parsers/audio.d.ts.map +1 -0
- package/build/loader/parsers/shader.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +3 -3
- package/build/physics/builtin/body.d.ts +23 -24
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts +1 -1
- package/build/plugin/plugin.d.ts +9 -1
- package/build/plugin/plugin.d.ts.map +1 -1
- package/build/polyfill/index.d.ts +1 -4
- package/build/polyfill/index.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +16 -0
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +16 -10
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +7 -7
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/state/stage.d.ts +4 -2
- package/build/state/stage.d.ts.map +1 -1
- package/build/system/event.d.ts +1 -1
- package/build/system/platform.d.ts +21 -1
- package/build/system/platform.d.ts.map +1 -1
- package/build/utils/fetchdata.d.ts.map +1 -0
- package/build/video/blendmodes.d.ts +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +5 -2
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/blur.d.ts +4 -4
- package/build/video/effects/blur.d.ts.map +1 -1
- package/build/video/effects/chromaticAberration.d.ts +5 -5
- package/build/video/effects/chromaticAberration.d.ts.map +1 -1
- package/build/video/effects/colorMatrix.d.ts +5 -5
- package/build/video/effects/colorMatrix.d.ts.map +1 -1
- package/build/video/effects/desaturate.d.ts +5 -5
- package/build/video/effects/desaturate.d.ts.map +1 -1
- package/build/video/effects/dissolve.d.ts +4 -4
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +5 -5
- package/build/video/effects/dropShadow.d.ts.map +1 -1
- package/build/video/effects/flash.d.ts +4 -4
- package/build/video/effects/flash.d.ts.map +1 -1
- package/build/video/effects/glow.d.ts +5 -5
- package/build/video/effects/glow.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts +4 -4
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/invert.d.ts +5 -5
- package/build/video/effects/invert.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +6 -6
- package/build/video/effects/outline.d.ts.map +1 -1
- package/build/video/effects/pixelate.d.ts +4 -4
- package/build/video/effects/pixelate.d.ts.map +1 -1
- package/build/video/effects/radialGradient.d.ts +2 -2
- package/build/video/effects/scanline.d.ts +6 -6
- package/build/video/effects/scanline.d.ts.map +1 -1
- package/build/video/effects/sepia.d.ts +5 -5
- package/build/video/effects/sepia.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +12 -10
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +3 -3
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/tintPulse.d.ts +4 -4
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/vignette.d.ts +6 -6
- package/build/video/effects/vignette.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +4 -4
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +1 -1
- package/build/video/webgl/glshader.d.ts +2 -2
- package/build/video/webgl/webgl_renderer.d.ts +2 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +2 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +5 -9
- package/skills/AGENTS.md +67 -0
- package/skills/melonjs/SKILL.md +156 -0
- package/skills/melonjs-20-migration/SKILL.md +160 -0
- package/skills/melonjs-3d/SKILL.md +226 -0
- package/skills/melonjs-3d-assets/SKILL.md +239 -0
- package/skills/melonjs-audio/SKILL.md +215 -0
- package/skills/melonjs-camera-and-drawing/SKILL.md +171 -0
- package/skills/melonjs-deployment/SKILL.md +113 -0
- package/skills/melonjs-effects-and-shaders/SKILL.md +177 -0
- package/skills/melonjs-events/SKILL.md +159 -0
- package/skills/melonjs-getting-started/SKILL.md +183 -0
- package/skills/melonjs-input/SKILL.md +166 -0
- package/skills/melonjs-lighting/SKILL.md +139 -0
- package/skills/melonjs-loading-assets/SKILL.md +157 -0
- package/skills/melonjs-particles-and-trails/SKILL.md +136 -0
- package/skills/melonjs-performance/SKILL.md +178 -0
- package/skills/melonjs-physics/SKILL.md +321 -0
- package/skills/melonjs-plugins/SKILL.md +191 -0
- package/skills/melonjs-renderables/SKILL.md +214 -0
- package/skills/melonjs-renderer-backends/SKILL.md +173 -0
- package/skills/melonjs-scenes-and-state/SKILL.md +180 -0
- package/skills/melonjs-sprites-and-animation/SKILL.md +173 -0
- package/skills/melonjs-tilemaps/SKILL.md +193 -0
- package/skills/melonjs-ui-and-text/SKILL.md +175 -0
- package/build/audio/backend.d.ts.map +0 -1
- package/build/loader/parsers/fetchdata.d.ts.map +0 -1
- /package/build/{loader/parsers → utils}/fetchdata.d.ts +0 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
---
|
|
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, and the behaviour differences between adapters. Triggers on: Body, bodyDef, collision, collisionType, collisionMask, onCollision, onCollisionStart, onCollisionActive, raycast, queryAABB, querySphere, PlanckAdapter, MatterAdapter, gravity, velocity, applyForce, isGrounded, SAT."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Physics and collision
|
|
8
|
+
|
|
9
|
+
> melonJS has a built-in SAT collision world and two optional rigid-body
|
|
10
|
+
> adapters. Movement, collision callbacks and spatial queries all behave
|
|
11
|
+
> differently depending on which is active — and several of the differences fail
|
|
12
|
+
> silently.
|
|
13
|
+
|
|
14
|
+
## Choosing a world
|
|
15
|
+
|
|
16
|
+
| | when |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **built-in** (default) | Arcade-style: platformers, top-down games. Fast, position-based, not Newtonian. |
|
|
19
|
+
| **`@melonjs/planck-adapter`** | Full rigid-body dynamics — stacking, joints, realistic restitution. |
|
|
20
|
+
| **`@melonjs/matter-adapter`** | Same class of thing, backed by matter-js. |
|
|
21
|
+
|
|
22
|
+
An adapter is an `Application` setting, not a plugin:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { PlanckAdapter } from "@melonjs/planck-adapter";
|
|
26
|
+
|
|
27
|
+
const app = new Application(800, 600, {
|
|
28
|
+
parent: "screen",
|
|
29
|
+
physic: new PlanckAdapter({
|
|
30
|
+
gravity: { x: 0, y: 320 }, // pixels/s², default {x: 0, y: 320}
|
|
31
|
+
pixelsPerMeter: 32, // default 32
|
|
32
|
+
subSteps: 2, // default 1
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
await app.init();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`PlanckAdapter` also takes `velocityIterations` (default `8`) and
|
|
39
|
+
`positionIterations` (default `3`). `MatterAdapter` takes a different set —
|
|
40
|
+
`gravity` (default `{x: 0, y: 1}`, matter's own convention), `subSteps` and
|
|
41
|
+
`matterEngineOptions`; it has no `pixelsPerMeter` because it already works in
|
|
42
|
+
pixels. `subSteps` is the anti-tunnelling knob when per-frame motion exceeds a
|
|
43
|
+
body radius. Pick **one** adapter per game; `physic: "none"` disables physics
|
|
44
|
+
entirely — `world.step` skips the simulation and the world behaves as a pure
|
|
45
|
+
scene graph.
|
|
46
|
+
|
|
47
|
+
`world.physic` carries the active adapter's label (`"builtin"`, `"planck"`,
|
|
48
|
+
`"matter"`, `"none"`), so game code can branch without importing the class.
|
|
49
|
+
|
|
50
|
+
## Two ways to declare a body
|
|
51
|
+
|
|
52
|
+
**`bodyDef`** — declarative, portable, works on every adapter. Prefer it.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
class Player extends Sprite {
|
|
56
|
+
constructor(x, y, settings) {
|
|
57
|
+
super(x, y, settings);
|
|
58
|
+
this.bodyDef = {
|
|
59
|
+
type: "dynamic",
|
|
60
|
+
shapes: [ /* … */ ],
|
|
61
|
+
collisionType: collision.types.PLAYER_OBJECT,
|
|
62
|
+
collisionMask: collision.types.WORLD_SHAPE | collision.types.ENEMY_OBJECT,
|
|
63
|
+
restitution: 0.2,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The engine forwards it to the active adapter when the renderable is added with
|
|
70
|
+
`Container.addChild`, and only if that container is already attached to the root
|
|
71
|
+
world; the broadphase picks the renderable up on the next `world.update()`.
|
|
72
|
+
Calling `adapter.addBody()` yourself registers the body but leaves the
|
|
73
|
+
renderable out of the scene graph, so it integrates but never collides.
|
|
74
|
+
|
|
75
|
+
Other portable `bodyDef` fields: `density`, `frictionAir` (number or `{x, y}`),
|
|
76
|
+
`friction`, `restitution`, `gravityScale`, `maxVelocity`, `fixedRotation`,
|
|
77
|
+
`isSensor`, `userData`. Not every adapter honours every one — the built-in
|
|
78
|
+
adapter ignores `friction`, `fixedRotation` and `userData`, and maps
|
|
79
|
+
`frictionAir` onto its per-axis `body.friction` damping vector.
|
|
80
|
+
|
|
81
|
+
**`new Body(this, shape)`** — the built-in-native API. Fine, but ties the entity
|
|
82
|
+
to the built-in world.
|
|
83
|
+
|
|
84
|
+
### Per-shape filtering and triggers
|
|
85
|
+
|
|
86
|
+
A shape in `shapes` may carry its own `collisionType`, `collisionMask`,
|
|
87
|
+
`isTrigger` (default `false`) and `isActive` (default `true`). A shape can only
|
|
88
|
+
**narrow** what its body allows — the body's type/mask are checked first.
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
const feet = new Rect(0, 24, 32, 8);
|
|
92
|
+
feet.collisionMask = collision.types.WORLD_SHAPE;
|
|
93
|
+
const torso = new Rect(0, 0, 32, 24);
|
|
94
|
+
torso.collisionMask = collision.types.ENEMY_OBJECT;
|
|
95
|
+
torso.isTrigger = true; // detects hits, never gets pushed by them
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`isActive: false` removes the shape from the narrow phase *and* from raycasts
|
|
99
|
+
without shrinking the body's bounds. Honoured by the built-in and planck
|
|
100
|
+
adapters; the matter adapter filters per body and ignores them. Under planck the
|
|
101
|
+
body-wide setters (`setCollisionType` / `setCollisionMask` / `setSensor`) write
|
|
102
|
+
every fixture and so overwrite per-shape values.
|
|
103
|
+
|
|
104
|
+
For contacts reported per *shape pair* rather than per body pair, define
|
|
105
|
+
`onShapeCollisionStart` / `onShapeCollisionActive` / `onShapeCollisionEnd`.
|
|
106
|
+
Declaring at least one of them opts into shape-pair enumeration; declaring none
|
|
107
|
+
costs nothing.
|
|
108
|
+
|
|
109
|
+
## Move with forces, not by assigning position
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
// ✗ the adapters overwrite this from the engine body every step
|
|
113
|
+
this.pos.x += this.speed;
|
|
114
|
+
|
|
115
|
+
// ✓ portable — works on every adapter
|
|
116
|
+
this.body.applyForce(thrustX, 0);
|
|
117
|
+
this.body.setVelocity(vx, vy);
|
|
118
|
+
|
|
119
|
+
// ✓ built-in only — `force` and `maxVel` are BuiltinAdapter Body fields
|
|
120
|
+
this.body.force.x = this.body.maxVel.x;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Under matter and planck, `syncFromPhysics()` copies the engine body's position
|
|
124
|
+
back onto `renderable.pos` after every step, so a direct write is simply erased
|
|
125
|
+
— no error. The built-in world integrates `pos` in place instead, so a write
|
|
126
|
+
survives but fights collision resolution and jitters. To teleport, go through
|
|
127
|
+
`adapter.setPosition(renderable, p)` so the engine body moves with it.
|
|
128
|
+
|
|
129
|
+
## Collision callbacks — which one to use
|
|
130
|
+
|
|
131
|
+
The lifecycle is `onCollisionStart` (contact begins), `onCollisionActive` (every
|
|
132
|
+
step while it persists) and `onCollisionEnd` (contact breaks). None of them are
|
|
133
|
+
defined on `Renderable` — you just declare the ones you want and the dispatcher
|
|
134
|
+
checks `typeof`. The legacy `onCollision` still exists and has a trap:
|
|
135
|
+
|
|
136
|
+
> On the built-in world, `onCollision` fires **twice per frame** for
|
|
137
|
+
> dynamic-dynamic pairs — once per outer-loop visit — and gets the raw SAT
|
|
138
|
+
> response with a fixed `a`/`b` per pair. On the adapters it is routed to their
|
|
139
|
+
> "still touching" phase and fires once per side.
|
|
140
|
+
|
|
141
|
+
The legacy dedupe idiom is `if (this !== response.a) return;`. Defining
|
|
142
|
+
`onCollisionActive` suppresses the legacy callback on that renderable (the
|
|
143
|
+
"supersedes" rule, applied per side so `a` and `b` can migrate independently),
|
|
144
|
+
and is deduped to once per pair per side per frame on every adapter.
|
|
145
|
+
|
|
146
|
+
**Return values only work on the legacy handler.** `return false` from
|
|
147
|
+
`onCollision` skips the built-in SAT push-out; what `onCollisionStart` /
|
|
148
|
+
`onCollisionActive` return is discarded. To opt a body out of the physical
|
|
149
|
+
response, set `bodyDef.isSensor` / `body.setSensor(true)`, or make the shape a
|
|
150
|
+
trigger. With no `onCollision` defined at all, push-out happens by default for
|
|
151
|
+
dynamic non-sensor bodies.
|
|
152
|
+
|
|
153
|
+
The two handler families do **not** get the same response object:
|
|
154
|
+
|
|
155
|
+
| | `onCollision` (legacy) | `onCollisionStart` / `Active` / `End` |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `a` / `b` | fixed per pair | receiver-symmetric: `a === this`, `b === other` |
|
|
158
|
+
| overlap magnitude | `response.overlap` | `response.depth` |
|
|
159
|
+
| contact axis | `response.overlapN` / `overlapV` | `response.normal` |
|
|
160
|
+
| also carries | `aInB`, `bInA`, `indexShapeA/B`, `isTriggerContact` | `pair` (adapter-native, absent on built-in) |
|
|
161
|
+
|
|
162
|
+
On the modern response `overlap` / `overlapN` / `overlapV` are still present but
|
|
163
|
+
`@deprecated` (and `undefined` under the matter adapter) — use `depth` and
|
|
164
|
+
`normal`. `normal` is the direction the receiver must move to separate, in
|
|
165
|
+
canvas coordinates: `normal.y < -0.7` means "push me up", i.e. I landed on top
|
|
166
|
+
of `other`.
|
|
167
|
+
|
|
168
|
+
One portability wrinkle: the built-in adapter dispatches `onCollisionEnd` with
|
|
169
|
+
`undefined` as the response; the matter and planck adapters pass a real one.
|
|
170
|
+
Guard it.
|
|
171
|
+
|
|
172
|
+
## Never mutate the world during contact dispatch
|
|
173
|
+
|
|
174
|
+
Collision callbacks are dispatched from inside the physics step on every
|
|
175
|
+
adapter, and each engine has its own tolerance for being mutated there. Flag the
|
|
176
|
+
work and drain it from your `Stage.update`:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
onCollisionStart(response, other) {
|
|
180
|
+
this.pendingRemoval = true; // don't addChild / removeChildNow here
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
What actually happens per adapter:
|
|
185
|
+
|
|
186
|
+
- **built-in** — callbacks fire inline during `step()`. `Container.removeChild`
|
|
187
|
+
is deferred, so it is safe; `removeChildNow()` and `destroy()` are immediate,
|
|
188
|
+
and the detector only survives them because it re-checks `body === undefined`
|
|
189
|
+
after every user callback.
|
|
190
|
+
- **planck** — `onCollisionStart` / `onCollisionEnd` are dispatched from
|
|
191
|
+
planck's `begin-contact` / `end-contact` events, which fire *while the world
|
|
192
|
+
is locked*. Adding a renderable with a `bodyDef` there makes planck's
|
|
193
|
+
`createBody` return `null` and the adapter then throws on the null handle.
|
|
194
|
+
`onCollisionActive` is dispatched after the step and is the safe one.
|
|
195
|
+
- **matter** — callbacks come from matter's `collisionStart` / `collisionActive`
|
|
196
|
+
/ `collisionEnd` events during `Engine.update`.
|
|
197
|
+
|
|
198
|
+
## Spatial queries
|
|
199
|
+
|
|
200
|
+
`raycast` and `queryAABB` are implemented by all three adapters:
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
import { Rect, Vector2d } from "melonjs";
|
|
204
|
+
|
|
205
|
+
// nearest hit only — returns null when nothing is hit
|
|
206
|
+
const hit = app.world.adapter.raycast(new Vector2d(x0, y0), new Vector2d(x1, y1));
|
|
207
|
+
// hit → { renderable, point, normal, fraction }
|
|
208
|
+
|
|
209
|
+
// takes a Rect, not a Bounds
|
|
210
|
+
const inArea = app.world.adapter.queryAABB(new Rect(x, y, w, h));
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`querySphere(centre, radius)` (or `querySphere(sphere)`) is **built-in only**,
|
|
214
|
+
and only active under a 3D broadphase — i.e. `world.sortOn === "depth"`, which
|
|
215
|
+
`Camera3d` sets. So is `raycast3d`. Check
|
|
216
|
+
`world.adapter.capabilities.raycasts3d` before calling either. `raycast` itself
|
|
217
|
+
is capability-gated by `capabilities.raycasts`.
|
|
218
|
+
|
|
219
|
+
The legacy `collision.rayCast(line, result)` is a different API: it takes a
|
|
220
|
+
`Line`, returns an **array** of every intersecting renderable, and only exists
|
|
221
|
+
on the built-in path.
|
|
222
|
+
|
|
223
|
+
Two prerequisites that produce empty results rather than errors:
|
|
224
|
+
|
|
225
|
+
- **`isKinematic` must be `false`** for a renderable to be in the broadphase at
|
|
226
|
+
all (a `Body` sets this for you).
|
|
227
|
+
- On the **built-in** adapter, the broadphase is cleared and rebuilt inside
|
|
228
|
+
`world.update()`, so querying before the first update returns nothing.
|
|
229
|
+
|
|
230
|
+
## Built-in world quirks
|
|
231
|
+
|
|
232
|
+
These are specific to the default world and surprise people arriving from a
|
|
233
|
+
real rigid-body engine:
|
|
234
|
+
|
|
235
|
+
- **Dynamic-dynamic collision is position-based, not Newtonian.** Separation is
|
|
236
|
+
mass-proportional but the velocity response is per-body cancellation — two
|
|
237
|
+
equal-mass bodies do not exchange momentum the way you would expect.
|
|
238
|
+
- **Gravity defaults to `(0, 0.98)`** — pixels per frame², not m/s². Mutate
|
|
239
|
+
`app.world.adapter.gravity` at runtime, or pass
|
|
240
|
+
`physic: new BuiltinAdapter({ gravity })` to override it. `body.gravityScale`
|
|
241
|
+
(default `1`) scales it per body; `0` disables gravity for that body and is
|
|
242
|
+
the portable replacement for the deprecated `body.ignoreGravity`.
|
|
243
|
+
- **`applyForce(x, y)` is linear only** unless you pass the optional
|
|
244
|
+
`(pointX, pointY)` application point, which generates a torque
|
|
245
|
+
`τ = r × F` into `body.angularVelocity` via `body.pseudoInertia`.
|
|
246
|
+
`applyTorque(τ)` is the direct form. Note the rotation is visual: SAT
|
|
247
|
+
collisions stay axis-aligned.
|
|
248
|
+
- **Force accumulators reset at end-of-step** — for every body, including
|
|
249
|
+
static, paused and off-screen ones — so forces must be applied every frame to
|
|
250
|
+
persist.
|
|
251
|
+
- **`isGrounded` is flag-based, not contact-based** — the adapter returns
|
|
252
|
+
`!body.falling && !body.jumping`, flags updated by the last resolved
|
|
253
|
+
collision, not a live contact test.
|
|
254
|
+
- **`def.density` maps 1:1 to `body.mass`** (default `1`), and `def.friction` is
|
|
255
|
+
**ignored** entirely. `body.friction` is a per-axis `Vector2d` of per-step
|
|
256
|
+
velocity damping fed from `def.frictionAir`, not a surface coefficient.
|
|
257
|
+
- **`body.maxVel` defaults to `(490, 490)`** and hard-clamps velocity after
|
|
258
|
+
forces and friction. The canonical movement idiom is
|
|
259
|
+
`this.body.force.x = this.body.maxVel.x`.
|
|
260
|
+
- **Default `collisionType` is `ENEMY_OBJECT`**, and default `collisionMask` is
|
|
261
|
+
`ALL_OBJECT` — set both explicitly.
|
|
262
|
+
- **Integration is gated on `inViewport || alwaysUpdate`** — an off-screen body
|
|
263
|
+
stops simulating unless you set `alwaysUpdate = true`. It is also gated on
|
|
264
|
+
`updateWhenPaused` while the state manager is paused, and static bodies never
|
|
265
|
+
integrate.
|
|
266
|
+
- **`addBody` throws on double-registration** for a renderable that is already
|
|
267
|
+
adapter-managed.
|
|
268
|
+
|
|
269
|
+
## Porting between adapters
|
|
270
|
+
|
|
271
|
+
Same call, different magnitude — these are the ones that waste an afternoon:
|
|
272
|
+
|
|
273
|
+
- **The portable API converts for you, the native handle does not.**
|
|
274
|
+
`adapter.setVelocity` / `body.setVelocity(x, y)` are in pixels on every
|
|
275
|
+
adapter; reaching for the raw handle (`body.setLinearVelocity` on planck) puts
|
|
276
|
+
you in metres per second, scaled by `pixelsPerMeter`.
|
|
277
|
+
- **Time base differs.** Built-in `body.vel` is pixels *per frame*; planck and
|
|
278
|
+
matter integrate in seconds. The same number is ~60× off.
|
|
279
|
+
- **Force magnitudes are not comparable** between adapters; re-tune rather than
|
|
280
|
+
reuse. `applyForce` is a per-step accumulator everywhere (cleared each step,
|
|
281
|
+
so call it every frame) — `applyImpulse` is the one-shot. Matter has no native
|
|
282
|
+
impulse, so the adapter emulates it as `Δv = J / mass` and ignores the
|
|
283
|
+
application point.
|
|
284
|
+
- **`body.position` is the centroid in matter**, while `renderable.pos` is
|
|
285
|
+
top-left in melonJS. The adapter stores a per-body offset so `renderable.pos`
|
|
286
|
+
stays correct — but read `body.position` directly and you get the centroid.
|
|
287
|
+
- **Degenerate (zero-area / collinear) polygons — a Tiled polyline, a `Line` —
|
|
288
|
+
cannot be built by matter and fall back to their axis-aligned bounding box.**
|
|
289
|
+
Any other shape type throws `unsupported shape type`.
|
|
290
|
+
- **Ellipses become circles on matter**, using the average of the two radii.
|
|
291
|
+
- **`isGrounded` is capability-gated** (`capabilities.isGrounded`) and computed
|
|
292
|
+
differently per adapter: a flag read on built-in, a live scan of active
|
|
293
|
+
contacts on matter.
|
|
294
|
+
|
|
295
|
+
Adapter-specific escapes exist (`(this.body as MatterAdapter.Body).frictionAir`,
|
|
296
|
+
`adapter.matter.Constraint.create(...)`) but are not portable — flag them if you
|
|
297
|
+
use them. `adapter.capabilities` (`constraints`,
|
|
298
|
+
`continuousCollisionDetection`, `sleepingBodies`, `raycasts`, `raycasts3d`,
|
|
299
|
+
`velocityLimit`, `isGrounded`) is the portable way to branch.
|
|
300
|
+
|
|
301
|
+
## Symptom → cause
|
|
302
|
+
|
|
303
|
+
| symptom | cause |
|
|
304
|
+
|---|---|
|
|
305
|
+
| entity jitters or does not move | assigning `pos` instead of applying force |
|
|
306
|
+
| collision handler runs twice per frame | legacy `onCollision` on a dynamic pair (built-in) — use `onCollisionActive` |
|
|
307
|
+
| `return false` from a handler does nothing | return values are only honoured by legacy `onCollision` — use `isSensor` / `isTrigger` |
|
|
308
|
+
| crash or a null body when spawning from `onCollisionStart` | world mutated during contact dispatch (locked world under planck) |
|
|
309
|
+
| `raycast` / `queryAABB` finds nothing | `isKinematic` still `true`, or no `world.update` yet (built-in) |
|
|
310
|
+
| `querySphere` / `raycast3d` is not a function | 2D adapter, or built-in without a 3D broadphase (`sortOn !== "depth"`) |
|
|
311
|
+
| `response.depth` / `response.normal` are `undefined` | reading a legacy `onCollision` response — it carries `overlap` / `overlapN` |
|
|
312
|
+
| `onCollisionEnd` handler throws on `response` | built-in dispatches it with `undefined` |
|
|
313
|
+
| off-screen bodies stop simulating | built-in gating on `inViewport`; set `alwaysUpdate` |
|
|
314
|
+
| forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
|
|
315
|
+
| `body.position` disagrees with `renderable.pos` on matter | matter stores the centroid, melonJS the top-left — the adapter offsets between them |
|
|
316
|
+
| a Tiled polyline becomes a solid box on matter | matter cannot build a zero-area polygon; the adapter falls back to its AABB |
|
|
317
|
+
|
|
318
|
+
## Related skills
|
|
319
|
+
|
|
320
|
+
- `melonjs-renderables` — `isKinematic`, which also gates the broadphase
|
|
321
|
+
- `melonjs-tilemaps` — collision shapes authored in Tiled
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-plugins
|
|
3
|
+
description: "Use this skill when adding a melonJS plugin or adapter — the debug panel, Spine skeletal animation, compressed Tiled map support, Capacitor packaging — or when writing your own plugin. Covers plugin.register and its version gate, plugin.cache, plugin.patch, and why physics adapters are not plugins. Triggers on: plugin, plugin.register, plugin.cache, plugin.get, plugin.patch, BasePlugin, debug-plugin, DebugPanelPlugin, SpinePlugin, TiledInflatePlugin, CapacitorPlugin, adapter, custom plugin, plugin version mismatch, already registered."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Plugins and adapters
|
|
8
|
+
|
|
9
|
+
## Two different mechanisms
|
|
10
|
+
|
|
11
|
+
This trips people up, so it is worth stating first:
|
|
12
|
+
|
|
13
|
+
| | how it is installed |
|
|
14
|
+
|---|---|
|
|
15
|
+
| **Plugins** | `plugin.register(Class, name?, ...args)` — after `await app.init()` |
|
|
16
|
+
| **Physics adapters** | an `Application` **setting** (`physic:`), before `init()` |
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// the adapter is chosen at construction …
|
|
20
|
+
const app = new Application(800, 600, {
|
|
21
|
+
parent: "screen",
|
|
22
|
+
physic: new PlanckAdapter({ gravity, pixelsPerMeter }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// … the plugin is registered once the renderer exists
|
|
26
|
+
await app.init();
|
|
27
|
+
plugin.register(DebugPanelPlugin, "debugPanel");
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## `register` in detail
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
plugin.register(PluginClass, "name", ...constructorArgs);
|
|
34
|
+
plugin.cache.name; // the instance
|
|
35
|
+
plugin.get(PluginClass); // by class — or by registered name string
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **The name is optional** — it falls back to the class name.
|
|
39
|
+
- **The registry is global, not per-application.** It is a module-level object,
|
|
40
|
+
so registering the same name twice throws `plugin <name> already registered`.
|
|
41
|
+
A plugin *instance* does hold an `app` reference, but it defaults to `game` —
|
|
42
|
+
**whichever Application most recently finished `init()`**, since that is where
|
|
43
|
+
`game` is assigned. On a multi-application page, pass the app explicitly
|
|
44
|
+
through the constructor args:
|
|
45
|
+
`plugin.register(DebugPanelPlugin, "debugPanel", input.KEY.S, myApp)`.
|
|
46
|
+
- **`BasePlugin.version` is a floor, not an identity.** It declares the
|
|
47
|
+
*minimum melonJS version the plugin needs*, and `register` throws
|
|
48
|
+
`Plugin version mismatch, expected: <floor>, got: <engine>` when the running
|
|
49
|
+
engine is older. That gate is the real enforcement — a peer-dependency range
|
|
50
|
+
only warns at install time, and not at all for CDN or pre-bundled use. If you
|
|
51
|
+
see that error, upgrade the engine or pin an older plugin. The check runs
|
|
52
|
+
*after* the instance is constructed, so a plugin whose constructor has side
|
|
53
|
+
effects will already have run them when it throws.
|
|
54
|
+
|
|
55
|
+
Register after `await app.init()`: a plugin binds to an Application and reads
|
|
56
|
+
renderer and frame state that does not exist before init.
|
|
57
|
+
|
|
58
|
+
## The official plugins
|
|
59
|
+
|
|
60
|
+
### `@melonjs/debug-plugin`
|
|
61
|
+
|
|
62
|
+
An in-game panel — frame time, draw and object counts — plus toggles for
|
|
63
|
+
hitbox/bounding-box overlays, velocity vectors and quadtree visualisation. The
|
|
64
|
+
first thing to reach for when something is slow or a collision is not landing
|
|
65
|
+
where you expect.
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { DebugPanelPlugin } from "@melonjs/debug-plugin";
|
|
69
|
+
plugin.register(DebugPanelPlugin, "debugPanel"); // default toggle key: S
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `@melonjs/tiled-inflate-plugin`
|
|
73
|
+
|
|
74
|
+
**Required**, not optional, for Tiled maps using gzip, zlib or zstd layer
|
|
75
|
+
compression. It installs the inflate hook `TMXUtils` calls; without it a
|
|
76
|
+
compressed `.tmx` fails to parse — a map that opens fine in Tiled and refuses to
|
|
77
|
+
load in melonJS is nearly always this.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { TiledInflatePlugin } from "@melonjs/tiled-inflate-plugin";
|
|
81
|
+
plugin.register(TiledInflatePlugin);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `@melonjs/spine-plugin`
|
|
85
|
+
|
|
86
|
+
Spine 4.x skeletal animation as a melonJS renderable, on all three backends.
|
|
87
|
+
This is the answer for deforming animated characters — glTF import does not read
|
|
88
|
+
vertex skinning. It also registers a Tiled object class, so Spine skeletons can
|
|
89
|
+
be placed from a map.
|
|
90
|
+
|
|
91
|
+
### `@melonjs/capacitor-plugin`
|
|
92
|
+
|
|
93
|
+
Bridges Capacitor's native lifecycle and hardware-back events into the engine:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
import { CapacitorPlugin } from "@melonjs/capacitor-plugin";
|
|
97
|
+
plugin.register(CapacitorPlugin, "capacitor", options);
|
|
98
|
+
// plugin.cache.capacitor.teardown() — detach every listener
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It also exports helpers usable on their own: `onBackButton`, `bindStageBack`,
|
|
102
|
+
`lockOrientation` / `unlockOrientation`, `hideSplash`. See `melonjs-deployment`
|
|
103
|
+
for the wider packaging picture.
|
|
104
|
+
|
|
105
|
+
## Physics adapters
|
|
106
|
+
|
|
107
|
+
`@melonjs/planck-adapter` and `@melonjs/matter-adapter` swap the physics world
|
|
108
|
+
wholesale. Pick **one** per game. The portable surface — the declarative
|
|
109
|
+
`renderable.bodyDef`, `world.adapter.queryAABB(rect)` and
|
|
110
|
+
`world.adapter.raycast(from, to)` — works unchanged across the built-in world and
|
|
111
|
+
both adapters, so entity code does not have to branch.
|
|
112
|
+
|
|
113
|
+
Two queries are **not** portable: `querySphere` and `raycast3d` exist only on the
|
|
114
|
+
built-in adapter (planck and matter are 2D-only and deliberately omit them).
|
|
115
|
+
`queryAABB` is the one region query every adapter must implement, so call the
|
|
116
|
+
others optionally — `app.world.adapter.querySphere?.(center, radius)`. For rays
|
|
117
|
+
there is a real flag: `app.world.adapter.capabilities.raycasts3d`.
|
|
118
|
+
|
|
119
|
+
Behaviour genuinely differs: velocity units, force magnitudes and where a body's
|
|
120
|
+
position is measured from all change. See `melonjs-physics`.
|
|
121
|
+
|
|
122
|
+
## Writing your own
|
|
123
|
+
|
|
124
|
+
There is **no `init()` lifecycle hook** — the constructor is where setup goes,
|
|
125
|
+
and `register` instantiates it for you.
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
import { plugin, event } from "melonjs";
|
|
129
|
+
|
|
130
|
+
class MyPlugin extends plugin.BasePlugin {
|
|
131
|
+
constructor(app = undefined) {
|
|
132
|
+
super(app); // stores it as this.app
|
|
133
|
+
this.version = "20.0.0"; // MINIMUM engine version this needs
|
|
134
|
+
event.on(event.GAME_UPDATE, () => { /* … */ });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
plugin.register(MyPlugin, "myPlugin");
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`this.version` defaults to the running engine version, which means it always
|
|
142
|
+
passes — set it deliberately to the oldest version you actually support.
|
|
143
|
+
|
|
144
|
+
To wrap an existing engine method, `plugin.patch` keeps the original reachable
|
|
145
|
+
as `this._patched()`:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
plugin.patch(app, "update", function (time) {
|
|
149
|
+
const result = this._patched(time);
|
|
150
|
+
return result;
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`patch` takes a class *or* an instance: given a constructor it patches the
|
|
155
|
+
prototype (that is how the debug plugin decorates `Renderable.postDraw`), given a
|
|
156
|
+
plain object it patches that object. `this._patched` is only bound for the
|
|
157
|
+
synchronous duration of your replacement — it is set before the call and nulled
|
|
158
|
+
straight after — so call it inline, never after an `await`.
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
plugin.patch(Renderable, "postDraw", function (renderer) {
|
|
162
|
+
this._patched(renderer);
|
|
163
|
+
// draw an overlay on top of every renderable
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
A plugin is the right shape when you need to augment engine classes or hook the
|
|
168
|
+
engine lifecycle globally. For anything that is just game code, a `Renderable`
|
|
169
|
+
or a `Stage` is simpler and easier to test.
|
|
170
|
+
|
|
171
|
+
## Symptom → cause
|
|
172
|
+
|
|
173
|
+
| symptom | cause |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `plugin <name> already registered` | the registry is global; registered twice, often across hot reloads |
|
|
176
|
+
| `Plugin version mismatch` | the plugin's `version` floor is above the running engine |
|
|
177
|
+
| `Plugin should extend the BasePlugin Class !` | the class does not extend `plugin.BasePlugin` |
|
|
178
|
+
| the panel reports another app's numbers | `app` defaulted to `game`, the last Application to finish `init()` |
|
|
179
|
+
| `world.adapter.querySphere is not a function` | built-in adapter only; planck and matter are 2D-only |
|
|
180
|
+
| a Tiled map opens in Tiled but not melonJS | compressed layers, tiled-inflate plugin missing |
|
|
181
|
+
| plugin setup never runs | setup written in an `init()` method — there is no such hook |
|
|
182
|
+
| `plugin.register` for an adapter does nothing | adapters are a construction setting, not a plugin |
|
|
183
|
+
| physics behaves differently after adding an adapter | expected — units and force scales differ; re-tune |
|
|
184
|
+
|
|
185
|
+
## Related skills
|
|
186
|
+
|
|
187
|
+
- `melonjs-physics` — the adapters and their behavioural differences
|
|
188
|
+
- `melonjs-tilemaps` — where the inflate plugin becomes mandatory
|
|
189
|
+
- `melonjs-performance` — using the debug panel to measure
|
|
190
|
+
- `melonjs-deployment` — Capacitor and native packaging
|
|
191
|
+
- `melonjs-3d-assets` — why skeletal characters go through Spine
|