melonjs 20.11.0 → 20.13.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 +3 -1
- package/build/application/application.d.ts.map +1 -1
- package/build/application/settings.d.ts +6 -2
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/backend/sound.d.ts.map +1 -1
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/spatial.d.ts +13 -2
- package/build/audio/spatial.d.ts.map +1 -1
- package/build/audio/types.d.ts +25 -3
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts +2 -0
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/camera/effects/fade_effect.d.ts.map +1 -1
- package/build/camera/effects/pixelate_transition_effect.d.ts +67 -0
- package/build/camera/effects/pixelate_transition_effect.d.ts.map +1 -0
- package/build/geometries/ellipse.d.ts +11 -4
- package/build/geometries/ellipse.d.ts.map +1 -1
- package/build/geometries/line.d.ts +2 -2
- package/build/geometries/line.d.ts.map +1 -1
- package/build/geometries/polygon.d.ts +5 -4
- package/build/geometries/polygon.d.ts.map +1 -1
- package/build/geometries/rectangle.d.ts +2 -2
- package/build/geometries/rectangle.d.ts.map +1 -1
- package/build/geometries/roundrect.d.ts +2 -2
- package/build/geometries/roundrect.d.ts.map +1 -1
- package/build/index.d.ts +14 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +12716 -7233
- package/build/index.js.map +4 -4
- package/build/input/gamepad.d.ts.map +1 -1
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/lang/console.d.ts +16 -1
- package/build/lang/console.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +48 -14
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +1 -1
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/TMXUtils.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXStaggeredRenderer.d.ts.map +1 -1
- package/build/loader/loader.d.ts +55 -11
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/loadingscreen.d.ts.map +1 -1
- package/build/math/color.d.ts.map +1 -1
- package/build/math/math.d.ts +6 -1
- package/build/math/math.d.ts.map +1 -1
- package/build/math/matrix2d.d.ts +3 -2
- package/build/math/matrix2d.d.ts.map +1 -1
- package/build/math/matrix3d.d.ts +4 -1
- package/build/math/matrix3d.d.ts.map +1 -1
- package/build/math/observableVector2d.d.ts +6 -6
- package/build/math/observableVector2d.d.ts.map +1 -1
- package/build/math/observableVector3d.d.ts +8 -8
- package/build/math/observableVector3d.d.ts.map +1 -1
- package/build/particles/emitter.d.ts +15 -3
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +6 -0
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +22 -5
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/bounds.d.ts +5 -5
- package/build/physics/bounds.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +15 -4
- 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.map +1 -1
- package/build/physics/builtin/raycast.d.ts.map +1 -1
- package/build/physics/world.d.ts.map +1 -1
- package/build/renderable/animation.d.ts +6 -1
- package/build/renderable/animation.d.ts.map +1 -1
- package/build/renderable/container.d.ts +40 -6
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/entity/entity.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts.map +1 -1
- package/build/renderable/imagelayer.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +8 -2
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/text/bitmaptext.d.ts +12 -3
- package/build/renderable/text/bitmaptext.d.ts.map +1 -1
- package/build/renderable/text/text.d.ts +147 -4
- package/build/renderable/text/text.d.ts.map +1 -1
- package/build/renderable/text/textmetrics.d.ts +10 -2
- package/build/renderable/text/textmetrics.d.ts.map +1 -1
- package/build/renderable/text/textstyle.d.ts.map +1 -1
- package/build/renderable/trail3d.d.ts.map +1 -1
- package/build/renderable/ui/panelpaint.d.ts +5 -1
- package/build/renderable/ui/panelpaint.d.ts.map +1 -1
- package/build/renderable/ui/progressbar.d.ts +267 -95
- package/build/renderable/ui/progressbar.d.ts.map +1 -1
- package/build/renderable/ui/uibaseelement.d.ts +436 -50
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uicheckbox.d.ts +383 -0
- package/build/renderable/ui/uicheckbox.d.ts.map +1 -0
- package/build/renderable/ui/uifocus.d.ts +6 -0
- package/build/renderable/ui/uifocus.d.ts.map +1 -0
- package/build/renderable/ui/uilabel.d.ts +31 -0
- package/build/renderable/ui/uilabel.d.ts.map +1 -0
- package/build/renderable/ui/uipanel.d.ts +589 -116
- package/build/renderable/ui/uipanel.d.ts.map +1 -1
- package/build/renderable/ui/uiskin.d.ts +25 -0
- package/build/renderable/ui/uiskin.d.ts.map +1 -0
- package/build/renderable/ui/uislider.d.ts +518 -0
- package/build/renderable/ui/uislider.d.ts.map +1 -0
- package/build/renderable/ui/uispriteelement.d.ts +161 -85
- package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
- package/build/renderable/ui/uitextbutton.d.ts +369 -63
- package/build/renderable/ui/uitextbutton.d.ts.map +1 -1
- package/build/state/stage.d.ts +3 -2
- package/build/state/stage.d.ts.map +1 -1
- package/build/state/state.d.ts +31 -6
- package/build/state/state.d.ts.map +1 -1
- package/build/system/legacy_pool.d.ts.map +1 -1
- package/build/system/pool.d.ts.map +1 -1
- package/build/system/save.d.ts +2 -1
- package/build/system/save.d.ts.map +1 -1
- package/build/tweens/tween.d.ts +5 -2
- package/build/tweens/tween.d.ts.map +1 -1
- package/build/utils/fetchdata.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +1 -5
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/bloom.d.ts +3 -2
- package/build/video/effects/bloom.d.ts.map +1 -1
- package/build/video/effects/blur.d.ts +1 -1
- package/build/video/effects/chromaticAberration.d.ts +1 -1
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +1 -1
- package/build/video/effects/glow.d.ts +1 -1
- package/build/video/effects/glsl_realization.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/local_space.d.ts +19 -0
- package/build/video/effects/local_space.d.ts.map +1 -0
- package/build/video/effects/mesh_splice.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +1 -1
- package/build/video/effects/pixelate.d.ts +1 -1
- package/build/video/effects/radialGradient.d.ts +4 -12
- package/build/video/effects/radialGradient.d.ts.map +1 -1
- package/build/video/effects/scanline.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +5 -0
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/sway.d.ts +9 -4
- package/build/video/effects/sway.d.ts.map +1 -1
- package/build/video/effects/vignette.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +9 -7
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/effects/wgsl/parse.d.ts.map +1 -1
- package/build/video/effects/wgsl/scaffold.d.ts.map +1 -1
- package/build/video/effects/wgsl_realization.d.ts.map +1 -1
- package/build/video/renderer.d.ts +22 -5
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/renderstate.d.ts +26 -3
- package/build/video/renderstate.d.ts.map +1 -1
- package/build/video/rendertarget/webgpurendertarget.d.ts +1 -0
- package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +31 -31
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/utils/pixel_snap.d.ts +44 -0
- package/build/video/utils/pixel_snap.d.ts.map +1 -0
- package/build/video/utils/tessellation.d.ts +11 -0
- package/build/video/utils/tessellation.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts +2 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts +3 -6
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -1
- package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/primitive_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/effect_binding.d.ts.map +1 -1
- package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +6 -10
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/AGENTS.md +2 -1
- package/skills/melonjs/SKILL.md +3 -2
- package/skills/melonjs-audio/SKILL.md +31 -8
- package/skills/melonjs-camera-and-drawing/SKILL.md +41 -1
- package/skills/melonjs-display-and-scaling/SKILL.md +19 -5
- package/skills/melonjs-effects-and-shaders/SKILL.md +53 -17
- package/skills/melonjs-events/SKILL.md +3 -1
- package/skills/melonjs-getting-started/SKILL.md +1 -2
- package/skills/melonjs-input/SKILL.md +61 -3
- package/skills/melonjs-lighting/SKILL.md +4 -3
- package/skills/melonjs-loading-assets/SKILL.md +57 -15
- package/skills/melonjs-math/SKILL.md +14 -3
- package/skills/melonjs-particles-and-trails/SKILL.md +72 -8
- package/skills/melonjs-performance/SKILL.md +3 -0
- package/skills/melonjs-physics/SKILL.md +53 -4
- package/skills/melonjs-plugins/SKILL.md +25 -1
- package/skills/melonjs-renderables/SKILL.md +11 -3
- package/skills/melonjs-renderer-backends/SKILL.md +2 -2
- package/skills/melonjs-scenes-and-state/SKILL.md +32 -2
- package/skills/melonjs-sprites-and-animation/SKILL.md +14 -0
- package/skills/melonjs-tilemaps/SKILL.md +96 -2
- package/skills/melonjs-ui-and-text/SKILL.md +560 -23
|
@@ -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, 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, tintOverLife, colour over life, color over life, flame, fire."
|
|
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, particle editor, tune particles, spawn depth, volume, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter, tintOverLife, colour over life, color over life, flame, fire."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -28,6 +28,50 @@ 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
|
+
Without an `image`, particles are white squares of `textureSize` pixels (8 by
|
|
32
|
+
default), coloured by `tint`. Every such emitter draws the renderer's one
|
|
33
|
+
shared white pixel, stretched, so an emitter per explosion or a `reset()` on
|
|
34
|
+
every tweak costs no texture.
|
|
35
|
+
|
|
36
|
+
## Tuning an effect: the Particle Editor
|
|
37
|
+
|
|
38
|
+
Settings are easier to find by eye than by reasoning. The
|
|
39
|
+
[Particle Editor](https://melonjs.github.io/melonJS/examples/#/particle-editor)
|
|
40
|
+
runs one emitter live, with every setting on a slider, from a preset (fire,
|
|
41
|
+
smoke, rain, explosions, 3D bursts and rings…), a built-in texture or a sprite
|
|
42
|
+
(sheet) of your own, under a 2D camera or an orbiting 3D one. It writes out the
|
|
43
|
+
matching `new ParticleEmitter(...)` call, listing only what differs from the
|
|
44
|
+
defaults, and the same settings as JSON, in the emitter's own units, to load at
|
|
45
|
+
run time:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
// preloaded with { name: "smoke-fx", type: "json", src: "data/fx/smoke.json" }
|
|
49
|
+
const effect = loader.getJSON("smoke-fx");
|
|
50
|
+
const emitter = new ParticleEmitter(x, y, {
|
|
51
|
+
...effect.settings,
|
|
52
|
+
// no `image` for the plain square: `getImage` would give null
|
|
53
|
+
...(effect.image ? { image: loader.getImage(effect.image) } : {}),
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Point a user there when they are tuning numbers by trial and error.
|
|
58
|
+
|
|
59
|
+
It covers every setting that changes how one emitter looks, but not:
|
|
60
|
+
|
|
61
|
+
- **Layered effects.** It shows one emitter at a time. A campfire (flames,
|
|
62
|
+
sparks, smoke) or an explosion (flash, debris, smoke) is several emitters
|
|
63
|
+
at the same spot: tune each in the editor, then add them side by side,
|
|
64
|
+
the one drawn on top last (or with a higher `z`).
|
|
65
|
+
- **Trails.** `Trail` and `Trail3d` are separate renderables; see below.
|
|
66
|
+
- **Code-only settings.** `onComplete`, `autoDestroyOnComplete`,
|
|
67
|
+
`accurateBounds`, and a `Container` as `referenceSpace` (it offers
|
|
68
|
+
`"local"` and `"world"`). `textureAdditive` is left out: `blendMode`
|
|
69
|
+
replaces it.
|
|
70
|
+
- **Changes at run time.** Calling `burstParticles()` when something
|
|
71
|
+
happens, or assigning `emitter.blendMode` while particles are alive, is
|
|
72
|
+
game code. Its "moving emitter" option previews the common case: an
|
|
73
|
+
emitter attached to something that moves.
|
|
74
|
+
|
|
31
75
|
## Sprite-sheet particles
|
|
32
76
|
|
|
33
77
|
Use `framewidth` and `frameheight` (the same names as on `Sprite`) to divide
|
|
@@ -107,15 +151,33 @@ fountain and wrong for an explosion: additive sprites piled on one point clip
|
|
|
107
151
|
all three channels there however well they are tinted, so a burst reads as one
|
|
108
152
|
bright flash rather than as debris.
|
|
109
153
|
|
|
110
|
-
Give it the size of whatever broke. The
|
|
111
|
-
|
|
112
|
-
to the right of its source:
|
|
154
|
+
Give it the size of whatever broke. The constructor centres the area on the
|
|
155
|
+
point it is given, so pass the centre of the source:
|
|
113
156
|
|
|
114
157
|
```js
|
|
115
158
|
const area = radius * 2;
|
|
116
|
-
const emitter = new ParticleEmitter(x
|
|
117
|
-
|
|
118
|
-
|
|
159
|
+
const emitter = new ParticleEmitter(x, y, { width: area, height: area });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
After that, `pos` is the area's top-left corner, as on any renderable. To move
|
|
163
|
+
the emitter to a new source later, offset by half, or the burst sits down and
|
|
164
|
+
to the right of it:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
emitter.pos.set(x - area / 2, y - area / 2); // re-aimed at (x, y)
|
|
168
|
+
emitter.burstParticles();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Under a `Camera3d`, `depth` (20.13) gives the area its third side: each
|
|
172
|
+
particle is born at a random depth within it, centered on the emitter's own
|
|
173
|
+
depth, unlike `width` and `height`. Without it every particle starts on one
|
|
174
|
+
plane facing the camera, so rain or dust seen from the side is a flat sheet. A
|
|
175
|
+
2D camera does not show depth, though with an order-dependent blend the
|
|
176
|
+
particles are still sorted by it. It is the spawn box's size, not the
|
|
177
|
+
emitter's `depth` property, which is where the emitter sits along Z.
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
new ParticleEmitter(x, y, { width: 600, height: 20, depth: 400 }); // a volume of rain
|
|
119
181
|
```
|
|
120
182
|
|
|
121
183
|
## Particles in 3D
|
|
@@ -321,6 +383,7 @@ answer when you want discrete puffs rather than a continuous ribbon.
|
|
|
321
383
|
|---|---|
|
|
322
384
|
| trail looks flat under a `Camera3d` | `Trail` is 2D; use `Trail3d` |
|
|
323
385
|
| particles clutter a minimap or a second camera | every camera draws them: set the emitter's `visibilityLayer` to a layer that camera's `cullMask` leaves out (see `melonjs-camera-and-drawing`) |
|
|
386
|
+
| a camera that draws only the emitter's layer shows no particles | before 20.13 particles stayed on layer 1 whatever their emitter was on; update |
|
|
324
387
|
| `Trail3d` draws nothing, with a console warning | it needs a `Camera3d`; the app's `cameraClass` is still the 2D default |
|
|
325
388
|
| `trail.tint` does nothing | `Trail` cannot be tinted at all; `Trail3d` can |
|
|
326
389
|
| assigning `maxPoints` throws | `Trail3d` sizes its geometry once; pass `length` to the constructor |
|
|
@@ -335,7 +398,8 @@ answer when you want discrete puffs rather than a continuous ribbon.
|
|
|
335
398
|
| frame rate drops with many effects | an emitter per event instead of one re-aimed emitter |
|
|
336
399
|
| a 3D burst is invisible under a `Camera3d` | before 20.8 a particle was culled at twice its depth; update |
|
|
337
400
|
| the whole burst launches from one pixel | `width`/`height` are the spawn area and default to `1` |
|
|
338
|
-
| the burst sits down-right of its source | the
|
|
401
|
+
| the burst sits down-right of its source | the emitter was moved by setting `pos` to the source: `pos` is the area's top-left corner, so offset by half (only the constructor centres it) |
|
|
402
|
+
| the burst sits up-left of its source | the constructor was given the top-left corner: it centres the area on (x, y), so pass the centre |
|
|
339
403
|
| the middle of a burst goes white | additive sprites overlapping — `textureAdditive: false`, or spread them |
|
|
340
404
|
| debris chunks look like squares | the default sprite is a filled square; pass your own `image` |
|
|
341
405
|
| a ring tilts with the camera instead of staying on a surface | the launch axis is in WORLD axes, not the camera basis |
|
|
@@ -222,6 +222,9 @@ queries one at a time, usually not. See `melonjs-3d` for the decision table.
|
|
|
222
222
|
|
|
223
223
|
- `stream: true` on long music tracks plays through a streaming element instead
|
|
224
224
|
of decoding the whole file into memory.
|
|
225
|
+
- `preload: false` on music a player may never reach (a boss theme, the
|
|
226
|
+
ending) keeps it out of the initial download: it is fetched and streamed
|
|
227
|
+
when first played.
|
|
225
228
|
- `pool` on an audio clip sizes the reuse pool for finished instances — it is
|
|
226
229
|
**not** a concurrency cap.
|
|
227
230
|
- `loader.unloadAll()` between large levels drops every loaded asset and
|
|
@@ -152,6 +152,11 @@ class Player extends Sprite {
|
|
|
152
152
|
The engine forwards it to the active adapter when the renderable is added with
|
|
153
153
|
`Container.addChild`, and only if that container is already attached to the root
|
|
154
154
|
world; the broadphase picks the renderable up on the next `world.update()`.
|
|
155
|
+
Nesting is fine: adding a container registers the body of every renderable
|
|
156
|
+
below it, at any depth, and removing it — destroyed, pooled or kept alive —
|
|
157
|
+
takes them all out again, so moving a whole group with `other.addChild(group)`
|
|
158
|
+
keeps each body simulated, once. A persistent container's children keep their
|
|
159
|
+
bodies through `world.reset()` (a state change or `level.load`).
|
|
155
160
|
Calling `adapter.addBody()` yourself registers the body but leaves the
|
|
156
161
|
renderable out of the scene graph, so it integrates but never collides.
|
|
157
162
|
|
|
@@ -270,6 +275,13 @@ samples it down and keeps the extent. Anything the 2D backends cannot express
|
|
|
270
275
|
is refused loudly on both, rather than leaving a body with no collision
|
|
271
276
|
geometry that silently never collides.
|
|
272
277
|
|
|
278
|
+
A polygon may be wound either way: its edge normals are oriented outward from
|
|
279
|
+
its signed area. Before 20.13 they pointed inward on a counter-clockwise
|
|
280
|
+
polygon — a Tiled polygon drawn that way, anything mirrored with
|
|
281
|
+
`scale(-1, 1)` or a `Rect` of negative width — and the built-in circle test
|
|
282
|
+
then collided a circle with it from tens of pixels away, as soon as their
|
|
283
|
+
bounding boxes met. Polygon against polygon was never affected.
|
|
284
|
+
|
|
273
285
|
Whatever a backend really simulates is what `getBodyShapes()` reports, so the
|
|
274
286
|
debug panel is the fastest way to see an approximation for what it is.
|
|
275
287
|
|
|
@@ -533,6 +545,15 @@ and only active under a 3D broadphase — i.e. `world.sortOn === "depth"`, which
|
|
|
533
545
|
`world.adapter.capabilities.raycasts3d` before calling either. `raycast` itself
|
|
534
546
|
is capability-gated by `capabilities.raycasts`.
|
|
535
547
|
|
|
548
|
+
On the built-in adapter a 2D ray measures each shape the way the narrowphase
|
|
549
|
+
measures it against a planar shape: polygons, `Rect`, `RoundRect` and `Line`
|
|
550
|
+
by their edges, an `Ellipse` with its rotation, a `Box3d` by its XY footprint
|
|
551
|
+
and a `Sphere` as a circle of its radius (a 2D ray is unbounded along Z). A
|
|
552
|
+
`Point` has no area to cross and is skipped, as is an unknown shape type,
|
|
553
|
+
with a one-time console warning. Before 20.13 a body carrying a `Point`,
|
|
554
|
+
`Sphere` or `Box3d` made every ray that reached it throw, and a rotated
|
|
555
|
+
ellipse was hit where it would have been unrotated.
|
|
556
|
+
|
|
536
557
|
The legacy `collision.rayCast(line, result)` is a different API: it takes a
|
|
537
558
|
`Line`, returns an **array** of every intersecting renderable, and only exists
|
|
538
559
|
on the built-in path.
|
|
@@ -629,8 +650,9 @@ group.resize(width, height);
|
|
|
629
650
|
```
|
|
630
651
|
|
|
631
652
|
`enableChildBoundsUpdate` also gives it one, but it re-measures the whole child
|
|
632
|
-
list on every update
|
|
633
|
-
|
|
653
|
+
list on every update (after the children have moved, and before each culling
|
|
654
|
+
check while it is off screen) — reach for it only when the extent genuinely IS
|
|
655
|
+
the members' union and moves with them (a flock, a squad), not to give a fixed
|
|
634
656
|
group a size.
|
|
635
657
|
|
|
636
658
|
## Built-in world quirks
|
|
@@ -656,13 +678,25 @@ real rigid-body engine:
|
|
|
656
678
|
- **`applyForce(x, y)` is linear only** unless you pass the optional
|
|
657
679
|
`(pointX, pointY)` application point, which generates a torque
|
|
658
680
|
`τ = r × F` into `body.angularVelocity` via `body.pseudoInertia`.
|
|
659
|
-
`applyTorque(τ)` is the direct form.
|
|
681
|
+
`applyTorque(τ)` is the direct form. The point is in **world** coordinates,
|
|
682
|
+
as on matter and planck, and the lever arm is measured from the centre of
|
|
683
|
+
the shapes where they collide: the renderable's drawn frame (so the anchor
|
|
684
|
+
counts) plus any parent container's position. `renderable.getBounds()` is
|
|
685
|
+
in that space, so `b.centerX, b.top` is the top centre.
|
|
660
686
|
- **Force accumulators reset at end-of-step** — for every body, including
|
|
661
687
|
static, paused and off-screen ones — so forces must be applied every frame to
|
|
662
688
|
persist.
|
|
663
689
|
- **`isGrounded` is flag-based, not contact-based** — the adapter returns
|
|
664
690
|
`!body.falling && !body.jumping`, flags updated by the last resolved
|
|
665
|
-
collision, not a live contact test.
|
|
691
|
+
collision, not a live contact test. The DIRECTION of the push-out decides
|
|
692
|
+
them: pushed up out of a floor while not rising is a landing, pushed down
|
|
693
|
+
by a ceiling while rising (or `jumping`) ends the jump and falls, and a
|
|
694
|
+
side contact, a body touched from above while standing, or a body with
|
|
695
|
+
`gravityScale` 0 leaves them alone. Before 20.13 a magnitude test decided
|
|
696
|
+
instead, so anything that sank a pixel or more per step — gravity above
|
|
697
|
+
about `1.02`, mass above about `1.03`, `timer.tick > 1` at a low frame
|
|
698
|
+
rate, a floor moving up under it — stayed `falling` while sitting flush on
|
|
699
|
+
the floor, and could never jump.
|
|
666
700
|
- **`def.density` maps 1:1 to `body.mass`** (default `1`), and `def.friction` is
|
|
667
701
|
**ignored** entirely. `body.friction` is a per-axis `Vector2d` of per-step
|
|
668
702
|
velocity damping fed from `def.frictionAir`, not a surface coefficient.
|
|
@@ -694,6 +728,12 @@ shape that is close enough at every angle (a circle, or a square for
|
|
|
694
728
|
90° steps), or rotate the shapes yourself with `body.rotate(angle)` — which
|
|
695
729
|
does rotate them, unlike `setAngle()`.
|
|
696
730
|
|
|
731
|
+
The visual turn pivots on the centre of the body's shapes, where they are
|
|
732
|
+
drawn and collide, whatever the renderable's `anchorPoint`. Before 20.13 it
|
|
733
|
+
ignored the anchor, so a centre-anchored renderable turned about its
|
|
734
|
+
bottom-right corner and swung away from where it stood; only a `(0, 0)`
|
|
735
|
+
anchor turned in place.
|
|
736
|
+
|
|
697
737
|
`body.rotate()` applies a **delta** and does not update `body.angle`; the two
|
|
698
738
|
are unlinked, so a body driven by `rotate()` still reports an angle of `0`.
|
|
699
739
|
Track it yourself if you need to read it back. Before 20.7 it also threw on a
|
|
@@ -775,12 +815,21 @@ use them. `adapter.capabilities` (`constraints`,
|
|
|
775
815
|
| 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 |
|
|
776
816
|
| a rotated renderable reports an unrotated bounding box | `autoTransform: false` opts `updateBounds()` out of the transform |
|
|
777
817
|
| 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 |
|
|
818
|
+
| `applyForce` with a point spins a body pushed through its centre, or tips it the wrong way (built-in) | before 20.13 the point was measured against the body's LOCAL bounds, so only a body at the world origin got the right torque; upgrade, and drop any `pos` you subtracted to compensate |
|
|
778
819
|
| forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
|
|
779
820
|
| `body.position` disagrees with `renderable.pos` on matter | matter stores the centroid, melonJS the top-left — the adapter offsets between them |
|
|
821
|
+
| a `bodyDef` two containers down never falls nor collides (`body` stays `undefined`) | before 20.13 only the added container's own children were registered; upgrade |
|
|
822
|
+
| `other.addChild(group)` throws "renderable is already adapter-managed" | before 20.13 a group detached with keepalive (which moving it does) left its children's bodies registered, still simulated off-stage; upgrade |
|
|
823
|
+
| the children of a persistent container stop falling and colliding after a state change or `level.load` | before 20.13 `world.reset()` kept only the bodies whose own renderable was persistent; upgrade, or flag each child `isPersistent` |
|
|
780
824
|
| `bodyDef.shapes: "…"` throws "is not loaded" | the name must match the `name` the JSON was preloaded under, not its filename |
|
|
781
825
|
| everything falls far too fast or too slow after swapping backends | gravity is a different unit per backend; see the table above |
|
|
782
826
|
| debris lands holding its spawn angle and never tumbles | `fixedRotation: true`, or matter-adapter < 1.4.0 / planck-adapter < 1.5.0 |
|
|
783
827
|
| 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)` |
|
|
828
|
+
| `setAngle` swings a sprite away from where it stood (built-in) | before 20.13 the pivot ignored `anchorPoint`, so a centre-anchored renderable turned about its bottom-right corner; upgrade |
|
|
829
|
+
| `isGrounded()` stays `false` on the floor, so the player cannot jump (built-in) | before 20.13 a body sinking 1px or more per step (gravity or mass above ~1.02, `timer.tick > 1`, a rising floor) never landed; upgrade |
|
|
830
|
+
| a circle body is shoved by a polygon it is nowhere near (built-in) | before 20.13 a counter-clockwise polygon (Tiled, mirrored with `scale(-1, 1)`) had inward edge normals; upgrade |
|
|
831
|
+
| `raycast` throws "reading 'x'" / "reading 'length'" (built-in) | before 20.13 a body carrying a `Point`, `Sphere` or `Box3d` broke the ray walk; upgrade |
|
|
832
|
+
| a ray passes through a rotated ellipse, or stops short of it (built-in) | before 20.13 the raycast ignored `Ellipse#angle`; upgrade |
|
|
784
833
|
| 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 |
|
|
785
834
|
| a Tiled polyline becomes a solid box on matter | matter cannot build a zero-area polygon; the adapter falls back to its AABB |
|
|
786
835
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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."
|
|
3
|
+
description: "Use this skill when adding a melonJS plugin or adapter — the debug panel, Spine skeletal animation, compressed Tiled map support, on-screen touch controls, 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, GamepadPlugin, gamepad-plugin, touch controls, virtual joystick, floating joystick, floating button, CapacitorPlugin, adapter, custom plugin, plugin version mismatch, already registered."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -88,6 +88,30 @@ This is the answer for deforming animated characters — glTF import does not re
|
|
|
88
88
|
vertex skinning. It also registers a Tiled object class, so Spine skeletons can
|
|
89
89
|
be placed from a map.
|
|
90
90
|
|
|
91
|
+
### `@melonjs/gamepad-plugin`
|
|
92
|
+
|
|
93
|
+
On-screen touch controls, reported as a gamepad: one or two sticks and any
|
|
94
|
+
number of buttons, drawn over the game and read by the engine as a pad in a
|
|
95
|
+
gamepad slot. The game's `bindGamepad` bindings drive it unchanged, so one set
|
|
96
|
+
of bindings covers a real pad and a touch screen.
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
import { GamepadPlugin } from "@melonjs/gamepad-plugin";
|
|
100
|
+
plugin.register(GamepadPlugin, "gamepad", {
|
|
101
|
+
buttons: [{ label: "A" }, { label: "B" }],
|
|
102
|
+
});
|
|
103
|
+
// plugin.cache.gamepad.visible — showing, to word prompts ("tap" or a key)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Shown on touch screens by default (`enabled`), hidden while a real pad is in
|
|
107
|
+
use. The left stick also presses the D-pad; a button can press a key instead
|
|
108
|
+
of a pad button. Since 1.1 a stick or button can be `floating` (shows up
|
|
109
|
+
under the finger anywhere in its `area`), `hideAtRest` (drawn only while
|
|
110
|
+
held), and a stick can take a `tap` (a quick touch that does not steer presses
|
|
111
|
+
a key or button) — so the mobile layouts, two thumbs or one thumb with
|
|
112
|
+
tap-to-act, need no code of their own. See `melonjs-input` for the bindings,
|
|
113
|
+
the layouts and the overlap rules.
|
|
114
|
+
|
|
91
115
|
### `@melonjs/capacitor-plugin`
|
|
92
116
|
|
|
93
117
|
Bridges Capacitor's native lifecycle and hardware-back events into the engine:
|
|
@@ -174,9 +174,10 @@ Use `addPostEffect()` / `getPostEffect()` / `removePostEffect()`. Assigning
|
|
|
174
174
|
`renderable.shader = …` is deprecated since 19.2.0.
|
|
175
175
|
|
|
176
176
|
Post effects need a programmable pipeline. A `ShaderEffect` constructed against
|
|
177
|
-
the Canvas renderer (`renderer.shaderLanguage === null`)
|
|
178
|
-
|
|
179
|
-
`beginPostEffect` filters disabled effects out.
|
|
177
|
+
the Canvas renderer (`renderer.shaderLanguage === null`) stays at
|
|
178
|
+
`enabled === false` and no-ops every method rather than throwing — and
|
|
179
|
+
`beginPostEffect` filters disabled effects out. The first one logs a warning,
|
|
180
|
+
the rest stay quiet, so a decorative effect needs no renderer check. Since `video.AUTO` picks the
|
|
180
181
|
backend at runtime, an effect that works on your machine may do nothing on
|
|
181
182
|
another.
|
|
182
183
|
|
|
@@ -290,6 +291,12 @@ other correct ending.
|
|
|
290
291
|
`obj.alwaysUpdate === true`. An off-screen object with the default
|
|
291
292
|
`alwaysUpdate = false` does not tick at all.
|
|
292
293
|
|
|
294
|
+
A `Container` is culled by its own bounds, which do not follow its children
|
|
295
|
+
unless it has a size of its own (`resize`) or `enableChildBoundsUpdate` is on.
|
|
296
|
+
With the flag, the bounds are the children's union, re-measured on every update
|
|
297
|
+
and, while the group is off screen, before each culling check — so a squad that
|
|
298
|
+
walks back into view is drawn again.
|
|
299
|
+
|
|
293
300
|
## Anchors and floating
|
|
294
301
|
|
|
295
302
|
- `anchorPoint` defaults to centre `(0.5, 0.5)` on `Renderable`, but `Container`
|
|
@@ -321,6 +328,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
|
|
|
321
328
|
| effect silently does nothing | running on the Canvas renderer |
|
|
322
329
|
| object appears frozen while its state changes | `update()` not returning `true` |
|
|
323
330
|
| off-screen object never ticks | `update()` is gated on `inViewport` — set `alwaysUpdate = true` |
|
|
331
|
+
| a group of moving children vanishes once they leave where they started | a sizeless container's bounds did not follow its children: give it a size, or `enableChildBoundsUpdate` (which only followed them from 20.13.0) |
|
|
324
332
|
| custom draw offset by half the size | centred `anchorPoint` default not zeroed |
|
|
325
333
|
| spatial query never finds an object | `isKinematic` left `true` — not in the broadphase |
|
|
326
334
|
| `z` unexpectedly 0 after a `set` | `Vector3d.set(x, y)` defaults `z` to 0 — `setPosition(x, y)` on the renderable leaves the depth alone |
|
|
@@ -47,8 +47,8 @@ once and then degrades or no-ops, so the game runs and looks wrong.
|
|
|
47
47
|
Two Canvas rows are worth spelling out because they are easy to misread as total
|
|
48
48
|
failures. A `Light2d` still *draws* — the Canvas renderer bakes a radial gradient
|
|
49
49
|
per light and blits it — what disappears is per-pixel normal-map shading, which
|
|
50
|
-
warns once (`Light2d normal-map lighting requires
|
|
51
|
-
renders sprites unlit. A `Mesh` still draws too, sorted with the painter's
|
|
50
|
+
warns once (`Light2d normal-map lighting requires a GPU backend`), the first time
|
|
51
|
+
a normal-mapped sprite is drawn under a light, and then renders sprites unlit. A `Mesh` still draws too, sorted with the painter's
|
|
52
52
|
algorithm and no depth buffer, so convex models look right and anything
|
|
53
53
|
self-overlapping does not. `"none"` (replace, alpha included) is the one blend
|
|
54
54
|
mode Canvas cannot express — there is no `globalCompositeOperation` that
|
|
@@ -50,7 +50,10 @@ a transition's reveal follow once the shaders the stage queued are ready, which
|
|
|
50
50
|
is a few milliseconds once the browser has compiled them before, and at most
|
|
51
51
|
1.5s on a first visit. So build the stage in `onResetEvent`: an object a
|
|
52
52
|
`STATE_CHANGE` listener adds is not waited for, and its first draw may still
|
|
53
|
-
compile what it needs.
|
|
53
|
+
compile what it needs. That first frame steps the world once before drawing it,
|
|
54
|
+
however short it is, so everything `onResetEvent` added is in it (the same goes
|
|
55
|
+
for the frame after a `level.load` made while the game runs). Before 20.12 it
|
|
56
|
+
was drawn unstepped, and showed the stage's background alone for one frame.
|
|
54
57
|
|
|
55
58
|
`state.change` **throws** `Undefined Stage for state '<id>'` if nothing was
|
|
56
59
|
registered for that id, and `state.set` throws if the second argument is not a
|
|
@@ -104,11 +107,15 @@ this.HUD.floating = true; // screen coordinates, not world
|
|
|
104
107
|
app.world.addChild(this.HUD, 100); // explicit z — see melonjs-renderables
|
|
105
108
|
```
|
|
106
109
|
|
|
110
|
+
Everything inside a persistent container is kept with it, physics bodies
|
|
111
|
+
included (before 20.13 the children's bodies were dropped by the reset).
|
|
112
|
+
|
|
107
113
|
## Transitions and pausing
|
|
108
114
|
|
|
109
115
|
```js
|
|
110
116
|
state.transition("fade", "#FFFFFF", 250);
|
|
111
117
|
state.transition("mask", "#000", 500, new Ellipse(0, 0, 1, 1)); // shape required
|
|
118
|
+
state.transition("pixelate", "#000", 300); // blocks up to 32px; a 4th argument sets the size
|
|
112
119
|
state.pause(); // freeze update, keep drawing
|
|
113
120
|
state.resume();
|
|
114
121
|
app.freeze(150); // brief hit-stop (proxies state.freeze)
|
|
@@ -116,7 +123,18 @@ app.freeze(150); // brief hit-stop (proxies state.fr
|
|
|
116
123
|
|
|
117
124
|
`state.transition` is global and sticky — once set it applies to every later
|
|
118
125
|
`state.change`. The `"mask"` form needs its fourth argument, an `Ellipse` or
|
|
119
|
-
`Polygon`; without one it warns and falls back to a direct switch.
|
|
126
|
+
`Polygon`; without one it warns and falls back to a direct switch. The
|
|
127
|
+
`"pixelate"` form breaks the screen up into blocks (its fourth argument is the
|
|
128
|
+
largest block size in pixels). It is a shader effect, so on Canvas it does
|
|
129
|
+
nothing and the state changes directly.
|
|
130
|
+
|
|
131
|
+
A `state.change` asked for while a transition covers the screen (Enter pressed
|
|
132
|
+
twice on a menu, a timer firing as the player skips a screen) needs no guard:
|
|
133
|
+
the transition already under way makes it, switching to the last state asked
|
|
134
|
+
for with the last arguments, and a second cover never goes up. A change to a
|
|
135
|
+
state with no transition of its own (`state.setTransition(id, false)`) is made
|
|
136
|
+
at once, and the cover comes down with it. Before 20.12 the second change left
|
|
137
|
+
the first cover on the camera for good, the screen black over a running game.
|
|
120
138
|
|
|
121
139
|
### A Stage's own `update()` is NOT paused
|
|
122
140
|
|
|
@@ -299,6 +317,14 @@ Four traps:
|
|
|
299
317
|
- **Stop a tween before destroying its target**, or `onUpdate` fires against a
|
|
300
318
|
dead renderable.
|
|
301
319
|
|
|
320
|
+
**A target can be an array: a path.** `to({ x: [200, 120, 300] })` runs from
|
|
321
|
+
the current value through each entry in turn, and
|
|
322
|
+
`interpolation: Tween.Interpolation.CatmullRom` smooths the corners. Each
|
|
323
|
+
`start()` builds the path afresh from the current value, so a replay or a chain
|
|
324
|
+
that comes back round runs the same path, and with `yoyo` the return leg plays
|
|
325
|
+
it back the way it came. Before 20.13 a restart ran a longer path each time and
|
|
326
|
+
a `yoyo` path turned the property into `NaN`.
|
|
327
|
+
|
|
302
328
|
**To animate something that is not a property, tween a holder and push the
|
|
303
329
|
value in `onUpdate`.** A shader uniform and a light's intensity are both set
|
|
304
330
|
through a method, so there is nothing for `to()` to interpolate directly. The
|
|
@@ -372,6 +398,7 @@ import { save } from "melonjs";
|
|
|
372
398
|
|
|
373
399
|
save.add({ hiscore: 0 }); // once; idempotent, loads any stored value
|
|
374
400
|
save.hiscore = 1200; // plain assignment writes to localStorage
|
|
401
|
+
save.remove("hiscore"); // gone from localStorage; set it again to save it again
|
|
375
402
|
```
|
|
376
403
|
|
|
377
404
|
**In TypeScript, keep what `add()` returns.** The namespace carries a
|
|
@@ -401,9 +428,12 @@ both.
|
|
|
401
428
|
| the world freezes on blur but the game keeps advancing | simulation lives in `Stage.update`, which the pause does not gate — only `Container.update` checks `state.isPaused()` |
|
|
402
429
|
| the player character is left behind and scrolls off after a lost window | same cause: the stage advanced the camera while its world children were paused |
|
|
403
430
|
| a tween does nothing | `.start()` never called |
|
|
431
|
+
| an array (path) tween wanders further on each replay, or goes `NaN` with `yoyo` | before 20.13 `start()` re-prepended the start value to the path and `yoyo` swapped the array into the start value; upgrade |
|
|
432
|
+
| a repeating or `yoyo` tween runs a single cycle when started again (a pulse pulses once, a yoyo stays at the far end) | before 20.13 `start()` did not restore the `repeat` count the first run had used up; upgrade |
|
|
404
433
|
| effect stops during a hit-stop | tween needs `updateWhenPaused = true` |
|
|
405
434
|
| crash after a stage switch | callback ran during teardown — guard with `state.isCurrent` |
|
|
406
435
|
| HUD scrolls away with the camera | missing `floating = true` |
|
|
436
|
+
| a `save` key set again after `save.remove()` reads back fine, then is gone (or back at its default) after a reload (before 20.13) | the write skipped the list of keys restored at boot; update |
|
|
407
437
|
|
|
408
438
|
## Related skills
|
|
409
439
|
|
|
@@ -95,6 +95,15 @@ Re-selecting the animation that is already current is a no-op, so calling
|
|
|
95
95
|
`setCurrentAnimation` every frame is safe, and an `isCurrentAnimation` check
|
|
96
96
|
around it adds nothing. An unknown id throws.
|
|
97
97
|
|
|
98
|
+
`addAnimation` with an id that exists replaces it, which is how you change an
|
|
99
|
+
animation's frames or speed (`hero.addAnimation("walk", [4, 5, 6, 7], 200)`).
|
|
100
|
+
Redefining the one that is playing takes effect at once: the playing frame is
|
|
101
|
+
kept if the new strip still has it, playback restarts on frame 0 if not, a
|
|
102
|
+
finished `loop: false` run holds the new last frame, and the
|
|
103
|
+
`setCurrentAnimation` options stay. Before 20.13 it kept the old frame count, so
|
|
104
|
+
a shorter strip threw on the next `update()` and a longer one never reached its
|
|
105
|
+
new frames.
|
|
106
|
+
|
|
98
107
|
## Texture atlases
|
|
99
108
|
|
|
100
109
|
`TextureAtlas` reads several formats:
|
|
@@ -125,6 +134,9 @@ sprite.flipX(true); // mirror horizontally
|
|
|
125
134
|
sprite.flipY(true);
|
|
126
135
|
sprite.blendMode = "additive"; // see the effects skill for the full list
|
|
127
136
|
sprite.scale(2, 2); // ← multiplicative, see below
|
|
137
|
+
sprite.flicker(1000, function () { // blink for a second, then…
|
|
138
|
+
this.ancestor.removeChild(this); // …`this` is the sprite; the flicker has ended
|
|
139
|
+
});
|
|
128
140
|
```
|
|
129
141
|
|
|
130
142
|
**`scale()` is multiplicative, not absolute.** Calling it every frame compounds.
|
|
@@ -185,6 +197,8 @@ Two consequences:
|
|
|
185
197
|
| event handlers leak on pooled objects | bound in the constructor rather than `onActivateEvent` |
|
|
186
198
|
| animation never advances | no `framewidth`/`frameheight`, so the sheet is one frame |
|
|
187
199
|
| animation plays once and stops | `{ loop: false }`, or a callback that returned `false` |
|
|
200
|
+
| `TypeError` reading `offset` or `delay` in `update()` after re-adding the playing animation with fewer frames, or a longer one never shows its new frames | before 20.13 redefining the current animation kept its old frame count; upgrade |
|
|
201
|
+
| `this` is wrong in a `flicker(ms, callback)` callback, or a flicker started from it stops at once | before 20.13.0 the callback ran on an internal object and the flicker was stopped after it; now `this` is the sprite and the flicker has already ended |
|
|
188
202
|
| sprites do not batch | more distinct textures in one frame than `renderer.maxTextures` |
|
|
189
203
|
| nine-slice corners stretch anyway | `insetX`/`insetY` instead of `insetx`/`insety` |
|
|
190
204
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-tilemaps
|
|
3
|
-
description: "Use this skill for Tiled maps in melonJS — loading TMX/TSX levels, spawning entities from Tiled objects, collision shapes authored in Tiled, isometric and hexagonal maps, and image layers. Covers registerTiledObjectClass and the pool.register name contract, camera bounds, compressed maps needing the inflate plugin, and the level director API. Triggers on: Tiled, TMX, TSX, tilemap, level.load, level.load async, await level.load, tileset, ImageLayer, isometric, hexagonal, staggered, pool.register, Collectable, Trigger, object layer, collision layer, parallax, registerTiledObjectClass, registerTiledObjectFactory, TMXObject, resolvedProperty, resolvedProperties, tile class, tile properties."
|
|
3
|
+
description: "Use this skill for Tiled maps in melonJS — loading TMX/TSX levels, spawning entities from Tiled objects, collision shapes authored in Tiled, isometric and hexagonal maps, and image layers, and reading or changing tiles while the game runs. Covers registerTiledObjectClass and the pool.register name contract, camera bounds, compressed maps needing the inflate plugin, and the level director API. Triggers on: Tiled, TMX, TSX, tilemap, setTile, clearTile, cellAt, getTile, getTileById, change a tile, tile at run time, game board, grid, tile properties at run time, getTileProperties, map properties, level.load, level.load async, await level.load, tileset, ImageLayer, isometric, hexagonal, staggered, pool.register, Collectable, Trigger, object layer, collision layer, parallax, registerTiledObjectClass, registerTiledObjectFactory, TMXObject, resolvedProperty, resolvedProperties, tile class, tile properties."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -185,6 +185,90 @@ knowing before you spend an afternoon on a map that loads at the wrong scale.
|
|
|
185
185
|
`async` is excluded deliberately — the trigger needs to sequence its own
|
|
186
186
|
transition.
|
|
187
187
|
|
|
188
|
+
## Reading and changing tiles at run time
|
|
189
|
+
|
|
190
|
+
A tile layer is a live grid, not just a picture: read it as the game board,
|
|
191
|
+
and change cells while the game runs — a door opening, a floor panel lighting
|
|
192
|
+
up, a wall blown away. A loaded tile layer is a `TMXLayer` in the world, under
|
|
193
|
+
the name it has in Tiled:
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
await level.load("map1", { container: this.app.world, async: true });
|
|
197
|
+
const board = this.app.world.getChildByName("ground")[0]; // a TMXLayer
|
|
198
|
+
board.cols; board.rows; // the grid, in cells
|
|
199
|
+
board.tilewidth; board.tileheight; // a cell, in pixels
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Two coordinate systems, and the methods do not share one:
|
|
203
|
+
|
|
204
|
+
| method | takes | returns |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `cellAt(col, row)` | column and row | the `Tile` there, or `null` for an empty cell or outside the grid |
|
|
207
|
+
| `getTile(x, y)` | **world pixels** | the same `Tile` object `cellAt` gives, or `null` |
|
|
208
|
+
| `setTile(tile, col, row)` | column and row | puts `tile` there, and returns it |
|
|
209
|
+
| `clearTile(col, row)` | column and row | empties the cell: `cellAt` then returns `null` |
|
|
210
|
+
| `getTileById(gid, col, row)` | a global tile id, column and row | a new `Tile`, to pass to `setTile` |
|
|
211
|
+
|
|
212
|
+
Columns and rows are the map's tile coordinates, the ones Tiled shows. On an
|
|
213
|
+
**infinite** map a layer can hold tiles left of or above the map's origin:
|
|
214
|
+
there `board.startx` / `board.starty` are negative, its cells run from
|
|
215
|
+
`startx` to `startx + cols - 1` (and `starty` to `starty + rows - 1`), and
|
|
216
|
+
`cellAt(-3, -2)` is the tile Tiled shows at (-3, -2), drawn where Tiled draws
|
|
217
|
+
it. On a finite map both are 0:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
for (let row = board.starty; row < board.starty + board.rows; row++) {
|
|
221
|
+
for (let col = board.startx; col < board.startx + board.cols; col++) {
|
|
222
|
+
const tile = board.cellAt(col, row); // every cell, infinite maps included
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The viewport bounds still cover only the map's `width` x `height`: set them
|
|
228
|
+
yourself to scroll to tiles painted outside it.
|
|
229
|
+
|
|
230
|
+
A `Tile` carries `tileId` (its **global** id), `col`, `row` and `tileset`.
|
|
231
|
+
Tiled shows a tile's *local* id in the tileset editor; the global id is
|
|
232
|
+
`tileset.firstgid + localId`, and only the global one works with `setTile`.
|
|
233
|
+
The tile's custom properties and class, set in Tiled's tileset editor, come
|
|
234
|
+
from its tileset by global id:
|
|
235
|
+
|
|
236
|
+
```js
|
|
237
|
+
const tile = board.cellAt(col, row);
|
|
238
|
+
const props = tile && tile.tileset.getTileProperties(tile.tileId); // { kind: "floor" } or undefined
|
|
239
|
+
const cls = tile && tile.tileset.getTileClass(tile.tileId);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
To swap a cell for another tile of the tileset, build the tile and set it. Look
|
|
243
|
+
global ids up by property once, rather than hard-coding them, so a designer
|
|
244
|
+
can rearrange the tileset:
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
const tileset = board.tilesets.getTilesetByIndex(0); // the layer's first tileset
|
|
248
|
+
const gidByKind = new Map();
|
|
249
|
+
for (let gid = tileset.firstgid; gid <= tileset.lastgid; gid++) {
|
|
250
|
+
const kind = tileset.getTileProperties(gid)?.kind;
|
|
251
|
+
if (kind !== undefined) gidByKind.set(kind, gid);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// light a floor panel up
|
|
255
|
+
board.setTile(board.getTileById(gidByKind.get("floor-lit"), col, row), col, row);
|
|
256
|
+
// knock a wall down
|
|
257
|
+
board.clearTile(col, row);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The layer redraws the changed cell by itself. What it does **not** change is
|
|
261
|
+
collision: solid ground comes from collision object layers (below), so a tile
|
|
262
|
+
set or cleared changes what is drawn and what `cellAt` returns, not what a body
|
|
263
|
+
runs into. Keep a layer you edit often off `preRender` (see the symptom table).
|
|
264
|
+
|
|
265
|
+
The map's own custom properties (Map → Map Properties in Tiled) are set on the
|
|
266
|
+
map object, which `level.getCurrentLevel()` returns once it is loaded: a
|
|
267
|
+
`theme` string property is `level.getCurrentLevel().theme`. They are set
|
|
268
|
+
**over** the map's own fields, so a property named like one replaces it: a
|
|
269
|
+
custom `name` property becomes the level's `name`. Give level settings names of
|
|
270
|
+
their own (`title`, not `name`).
|
|
271
|
+
|
|
188
272
|
## Camera bounds
|
|
189
273
|
|
|
190
274
|
`level.load` sets the viewport bounds to the map size for you —
|
|
@@ -344,6 +428,7 @@ unanimated layer into the offscreen-bake path instead.
|
|
|
344
428
|
|
|
345
429
|
| symptom | cause |
|
|
346
430
|
|---|---|
|
|
431
|
+
| objects a screen added just before `level.load` draw in the order they were added (a background over everything) | before 20.13 the reset inside `level.load` dropped the sort still pending for the persistent children it kept; update, or call `sort()` on their container once the level is loaded |
|
|
347
432
|
| a Tiled object becomes a plain shape with no behaviour | its class/name does not match any registered factory, or it was registered after `level.load` |
|
|
348
433
|
| the world is still empty right after `level.load` | the load is deferred to a timer while the loop runs — `await level.load(id, { async: true })`, or use `onLoaded` / `LEVEL_LOADED` |
|
|
349
434
|
| `await level.load(id)` returned `true` rather than a promise | without `async: true` the call returns a boolean; `await true` resolves immediately and the load has not run yet — pass the flag when you mean to await |
|
|
@@ -361,7 +446,16 @@ unanimated layer into the offscreen-bake path instead.
|
|
|
361
446
|
| a property set on a tile is undefined on the object's `settings` | tile properties are not copied onto `settings`: read them with `settings.resolvedProperty(name)` |
|
|
362
447
|
| objects placed from a classed tile come out as plain sprites | that class is not registered, it is a built-in name, or the object has a class or a registered name of its own, which come first |
|
|
363
448
|
| missing tiles or a blank map | the `.tsx` or tileset image was not preloaded |
|
|
364
|
-
| a tileset's transparent color (`trans`) still shows (the console says it could not be applied) | the image cannot be read back:
|
|
449
|
+
| a tileset's transparent color (`trans`) still shows (the console says it could not be applied) | the image cannot be read back: an image the game made itself from another origin without CORS (an `<img>` with no `crossOrigin`) cannot be, so load it with the loader, which only loads an image another origin's server allows (its CORS headers) and can always read back; and a compressed texture cannot be changed: export it with an alpha channel instead |
|
|
450
|
+
| `cellAt` returns `null` (or the wrong tile) for a cell that has one | it was given pixels: `cellAt`, `setTile` and `clearTile` take column and row, only `getTile` takes world pixels |
|
|
451
|
+
| `setTile` shows the wrong tile, or nothing | it was given Tiled's local tile id: use the global id, `tileset.firstgid + localId`, through `getTileById` |
|
|
452
|
+
| an infinite map's tiles sit in the wrong row, or tiles left of or above the origin are missing | an older melonJS ignored where the chunks start (update); and loop from `layer.startx` / `layer.starty`, not 0, to reach cells left of or above the origin |
|
|
453
|
+
| on a staggered map `getTile` gives the neighbouring tile on every row but the first, or a scrolled view draws nothing | a bug of older melonJS versions in the staggered renderer: update |
|
|
454
|
+
| on an isometric (or staggered, oblique) map `getTile` gives an edge tile for a point just outside the tiles | an older melonJS rounded a fractional column or row toward 0: update |
|
|
455
|
+
| tiles of a second tileset draw from the first tileset's image (that first one has a `margin`) | an older melonJS let its gid range overrun into the next tileset (update): the range is the `tilecount` Tiled saved |
|
|
456
|
+
| a layer is `undefined` from `getChildByName(name)[0]` | the level is not in the world yet (await `level.load(id, { async: true })`), or the name differs from the layer's in Tiled |
|
|
457
|
+
| a tile set or cleared at run time does not block or free movement | tile layers carry no collision: collision comes from collision object layers |
|
|
458
|
+
| the level's `name` is not its id after loading | a map custom property named `name` replaced it: map properties are set over the map's own fields |
|
|
365
459
|
| changing tiles at runtime is slow on a pre-rendered layer | outside an orthogonal map whose tiles fit their cells, a changed tile re-bakes the whole layer before it next draws: keep layers you edit often off `preRender` |
|
|
366
460
|
| thin lines between tiles on the Canvas renderer, with smoothing on | filtering blends in the next tile of the tileset or its spacing, which a canvas cannot prevent: set `antiAlias: false`, or `preRender` the layer. WebGL and WebGPU keep each tile to itself |
|
|
367
461
|
| tilemap slow on some machines | layer fell back off the shader path — check the console warning for the reason |
|