melonjs 20.13.0 → 20.14.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 +65 -22
- package/build/application/application.d.ts +6 -1
- package/build/application/application.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/resize.d.ts.map +1 -1
- package/build/application/settings.d.ts +13 -8
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +8 -4
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/backend/engine.d.ts.map +1 -1
- package/build/audio/playback.d.ts +2 -2
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/spatial.d.ts.map +1 -1
- package/build/audio/types.d.ts +15 -5
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts +10 -9
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +11 -8
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/geometries/path2d.d.ts +9 -8
- package/build/geometries/path2d.d.ts.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +40993 -39738
- package/build/index.js.map +4 -4
- package/build/input/keyboard.d.ts.map +1 -1
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/level.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +2 -1
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +13 -3
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.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 +1 -0
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +7 -3
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/lighting/light3d.d.ts +55 -0
- package/build/lighting/light3d.d.ts.map +1 -1
- package/build/loader/cache.d.ts +146 -97
- package/build/loader/cache.d.ts.map +1 -1
- package/build/loader/internal-types.d.ts +69 -0
- package/build/loader/internal-types.d.ts.map +1 -0
- package/build/loader/loader.d.ts +584 -292
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/parsers/audio.d.ts.map +1 -1
- package/build/loader/parsers/fontface.d.ts.map +1 -1
- package/build/loader/parsers/gltf.d.ts.map +1 -1
- package/build/loader/parsers/tmx.d.ts.map +1 -1
- package/build/math/color.d.ts +4 -0
- package/build/math/color.d.ts.map +1 -1
- package/build/math/noise.d.ts +16 -16
- package/build/math/noise.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +64 -25
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +9 -0
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/builtin-adapter.d.ts +25 -3
- 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 +40 -10
- package/build/physics/world.d.ts.map +1 -1
- package/build/renderable/container.d.ts +1 -0
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/draggable.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +16 -14
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +62 -5
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/trigger.d.ts +5 -0
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/uibaseelement.d.ts +8 -1
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uicheckbox.d.ts.map +1 -1
- package/build/system/device.d.ts +11 -2
- package/build/system/device.d.ts.map +1 -1
- package/build/system/fullscreen.d.ts.map +1 -1
- package/build/system/pool.d.ts.map +1 -1
- package/build/utils/utils.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +16 -14
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/canvas_factory.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +127 -0
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs/SKILL.md +38 -14
- package/skills/melonjs-20-migration/SKILL.md +50 -7
- package/skills/melonjs-3d/SKILL.md +138 -38
- package/skills/melonjs-3d-assets/SKILL.md +83 -33
- package/skills/melonjs-audio/SKILL.md +55 -14
- package/skills/melonjs-camera-and-drawing/SKILL.md +95 -8
- package/skills/melonjs-deployment/SKILL.md +92 -14
- package/skills/melonjs-display-and-scaling/SKILL.md +8 -3
- package/skills/melonjs-effects-and-shaders/SKILL.md +46 -15
- package/skills/melonjs-events/SKILL.md +3 -2
- package/skills/melonjs-getting-started/SKILL.md +5 -0
- package/skills/melonjs-input/SKILL.md +65 -15
- package/skills/melonjs-lighting/SKILL.md +13 -1
- package/skills/melonjs-loading-assets/SKILL.md +202 -27
- package/skills/melonjs-math/SKILL.md +12 -5
- package/skills/melonjs-particles-and-trails/SKILL.md +7 -3
- package/skills/melonjs-performance/SKILL.md +18 -8
- package/skills/melonjs-physics/SKILL.md +385 -127
- package/skills/melonjs-plugins/SKILL.md +45 -11
- package/skills/melonjs-renderables/SKILL.md +24 -7
- package/skills/melonjs-renderer-backends/SKILL.md +38 -17
- package/skills/melonjs-scenes-and-state/SKILL.md +30 -5
- package/skills/melonjs-sprites-and-animation/SKILL.md +23 -2
- package/skills/melonjs-tilemaps/SKILL.md +56 -18
- package/skills/melonjs-ui-and-text/SKILL.md +120 -25
|
@@ -144,6 +144,11 @@ await loader.preload([
|
|
|
144
144
|
]);
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
A flat list suits a single-screen game. A game with several levels exports a
|
|
148
|
+
record of bundles instead — `{ common: [...], level1: [...], level2: [...] }`,
|
|
149
|
+
passed to the same `loader.preload()` — so each level's assets can later be
|
|
150
|
+
freed and loaded again by id; see `melonjs-loading-assets`.
|
|
151
|
+
|
|
147
152
|
Two ordering rules that produce confusing failures when missed:
|
|
148
153
|
|
|
149
154
|
- **`audio.init()` must run before any audio asset is preloaded**, and names the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-input
|
|
3
|
-
description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, virtual gamepad, touch controls, joystick, gamepad-plugin, floating stick, floating button, floating joystick, two thumbs, tap to fire, hideAtRest, mobile controls, KEYDOWN, KEYUP, press any key, splash screen, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
|
|
3
|
+
description: "Use this skill for keyboard, pointer, mouse, touch and gamepad input in melonJS. Covers the bindKey action indirection, registerPointerEvent on regions, the isKinematic requirement that silently blocks pointer events, which of several overlapping objects receives an event and how to consume it, world vs screen pointer coordinates, and gamepad mapping onto the same actions. Triggers on: input, bindKey, isKeyPressed, KEY, bindPointer, mouse button, vibrateGamepad, rumble, activeGamepad, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, virtual gamepad, touch controls, joystick, gamepad-plugin, floating stick, floating button, floating joystick, two thumbs, tap to fire, hideAtRest, mobile controls, KEYDOWN, KEYUP, press any key, splash screen, click, touch, drag, hit test, event propagation, stop propagating, overlapping, which object gets the click."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -54,12 +54,37 @@ narrows it; it also repeats while the key is held, so guard anything that must
|
|
|
54
54
|
happen once. Keep polling for movement and for a menu cursor, where one step
|
|
55
55
|
per press and a key held across a state change are the things that matter.
|
|
56
56
|
|
|
57
|
+
When the window loses the focus or the page is hidden, every key and
|
|
58
|
+
`bindPointer` button still held is released, since its real release goes to
|
|
59
|
+
another window: polling sees it up when the player comes back, and a key still
|
|
60
|
+
held then is pressed again by its next `keydown`. No `KEYUP` is sent for it,
|
|
61
|
+
so code moved by `KEYDOWN` / `KEYUP` rather than by polling should also listen
|
|
62
|
+
to `event.BLUR`. A gamepad's presses are left to the gamepad, which is read
|
|
63
|
+
every frame.
|
|
64
|
+
|
|
57
65
|
The full signature is `bindKey(keyCode, action, lock?, preventDefault?)`;
|
|
58
66
|
`preventDefault` defaults to `input.preventDefault` (itself `true`). Pass the
|
|
59
67
|
fourth argument per binding, or change the global default with
|
|
60
68
|
`input.setPreventDefault(false)` — **assigning `input.preventDefault = false`
|
|
61
69
|
does not work**, it is a read-only module binding.
|
|
62
70
|
|
|
71
|
+
### A mouse button as a key
|
|
72
|
+
|
|
73
|
+
`bindPointer` makes a mouse button press a bound key, so a click fires the same
|
|
74
|
+
action and `isKeyPressed` sees it:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
input.bindKey(input.KEY.X, "fire");
|
|
78
|
+
input.bindPointer(input.KEY.X); // the left button
|
|
79
|
+
input.bindPointer(input.pointer.RIGHT, input.KEY.X); // or another button
|
|
80
|
+
input.unbindPointer(input.pointer.RIGHT); // LEFT when omitted
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
As with `bindGamepad`, the key needs its `bindKey` first, or `bindPointer`
|
|
84
|
+
throws `no action defined for keycode N`. Turning the wheel neither presses
|
|
85
|
+
nor releases a bound button's key (before 20.14 it released the left
|
|
86
|
+
button's).
|
|
87
|
+
|
|
63
88
|
## Pointer events need `isKinematic = false`
|
|
64
89
|
|
|
65
90
|
**This is the trap.** `Renderable.isKinematic` defaults to `true`, and both the
|
|
@@ -82,9 +107,11 @@ class Button extends Renderable {
|
|
|
82
107
|
```
|
|
83
108
|
|
|
84
109
|
`Container`, `Camera2d`, `Draggable`, `DropTarget` and the `UI*` elements set it
|
|
85
|
-
to `false` for you, and so does
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
to `false` for you, and so does giving it a physics body (a `bodyDef` on any
|
|
111
|
+
adapter, or a `Body`) — so plain `Renderable` and `Sprite` subclasses with no
|
|
112
|
+
body are the ones that catch people. Before melonJS 20.14, matter-adapter
|
|
113
|
+
1.6.0 and planck-adapter 1.7.0, a `bodyDef` under matter or planck left
|
|
114
|
+
`isKinematic` at `true`, so set it yourself there.
|
|
88
115
|
|
|
89
116
|
Register in `onActivateEvent` and release in `onDeactivateEvent`, not in the
|
|
90
117
|
constructor — pooled objects are reused and would otherwise accumulate handlers.
|
|
@@ -103,6 +130,11 @@ throw `invalid event type`. melonJS maps the canonical name onto whichever
|
|
|
103
130
|
mouse/touch events the device actually supports, so you always register the
|
|
104
131
|
pointer name.
|
|
105
132
|
|
|
133
|
+
A region can listen to `"pointerup"` alone, to act on release: it gets the
|
|
134
|
+
release of a press made on it. With several fingers on one region, each finger
|
|
135
|
+
that pressed it gets its own `pointerup` / `pointercancel`; `pointerenter` and
|
|
136
|
+
`pointerleave` still follow one pointer per region.
|
|
137
|
+
|
|
106
138
|
## Which object gets the event
|
|
107
139
|
|
|
108
140
|
The dispatcher collects the regions near the pointer from the broadphase,
|
|
@@ -182,9 +214,10 @@ globalThis.addEventListener("pointerup", onRelease);
|
|
|
182
214
|
|
|
183
215
|
For plain drag-and-drop, `Draggable` / `DropTarget` already wire all of this up
|
|
184
216
|
(`pointerdown` / `pointerup` / `pointercancel` regions plus a global
|
|
185
|
-
`POINTERMOVE` listener) and emit `event.DRAGSTART` / `event.DRAGEND
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
`POINTERMOVE` listener) and emit `event.DRAGSTART` / `event.DRAGEND`, and
|
|
218
|
+
follow the pointer under any camera, scrolled or zoomed. `DRAGEND` still
|
|
219
|
+
originates from a region handler, so it only fires when the release lands on
|
|
220
|
+
the draggable itself.
|
|
188
221
|
|
|
189
222
|
## Gamepad
|
|
190
223
|
|
|
@@ -252,10 +285,10 @@ input.hasGamepadVibration(index); // for an options-screen toggle
|
|
|
252
285
|
|
|
253
286
|
Safe to call blind: no pad, no motors, or no Gamepad API at all resolves
|
|
254
287
|
`false` and does nothing, so game code never branches on support. Magnitudes
|
|
255
|
-
default to **full** — the underlying web API defaults
|
|
256
|
-
which makes a bare `playEffect` a silent no-op. A
|
|
257
|
-
first on the same pad, so a rapid series of hits
|
|
258
|
-
hits rather than as a queue. For a SUSTAINED rumble issue one effect with a
|
|
288
|
+
default to **full** and `duration` to 200 ms — the underlying web API defaults
|
|
289
|
+
every parameter to zero, which makes a bare `playEffect` a silent no-op. A
|
|
290
|
+
second call preempts the first on the same pad, so a rapid series of hits
|
|
291
|
+
reads as a rapid series of hits rather than as a queue. For a SUSTAINED rumble issue one effect with a
|
|
259
292
|
long `duration` instead of calling every frame.
|
|
260
293
|
|
|
261
294
|
Hot-plugging needs no polling of your own — the engine listens for the browser's
|
|
@@ -270,12 +303,15 @@ event.on(event.GAMEPAD_DISCONNECTED, (gamepad) => { /* pause, show a prompt */ }
|
|
|
270
303
|
|
|
271
304
|
Bindings survive a disconnect: they are attached to the index, not to the
|
|
272
305
|
device, so a pad unplugged and plugged back into the same slot keeps working.
|
|
306
|
+
A pad unplugged with a button or the stick held lets go of the keys bound to
|
|
307
|
+
them, as releasing them would.
|
|
273
308
|
|
|
274
309
|
## Virtual controls
|
|
275
310
|
|
|
276
|
-
For on-screen touch controls, use `@melonjs/gamepad-plugin`
|
|
277
|
-
writing them: sticks and buttons drawn over the
|
|
278
|
-
a gamepad slot, so the `bindGamepad` bindings
|
|
311
|
+
For on-screen touch controls, use `@melonjs/gamepad-plugin` (it needs melonJS
|
|
312
|
+
20.12 or later) rather than writing them: sticks and buttons drawn over the
|
|
313
|
+
game and reported as a pad in a gamepad slot, so the `bindGamepad` bindings
|
|
314
|
+
above answer to them unchanged.
|
|
279
315
|
|
|
280
316
|
```js
|
|
281
317
|
import { GamepadPlugin } from "@melonjs/gamepad-plugin";
|
|
@@ -326,6 +362,12 @@ Every press lasts at least one frame, so a 15 ms thumb tap still reaches
|
|
|
326
362
|
`isKeyPressed`, and Safari's pinch gesture is cancelled while the controls
|
|
327
363
|
show, so a second thumb works on iOS.
|
|
328
364
|
|
|
365
|
+
To match the game's artwork (1.2), pass an already-loaded `TextureAtlas` as
|
|
366
|
+
`skin` to `GamepadPlugin`, with `base` / `thumb` region names on sticks
|
|
367
|
+
and `region` / optional `pressed` names on buttons. Omitted parts keep
|
|
368
|
+
their shapes. Regions scale to the controls' sizes without changing hit areas;
|
|
369
|
+
labels, floating controls, opacity, hiding and dimming keep working.
|
|
370
|
+
|
|
329
371
|
A single key can also be pressed directly, reaching the same actions:
|
|
330
372
|
|
|
331
373
|
```js
|
|
@@ -338,19 +380,27 @@ input.triggerKeyEvent(input.KEY.LEFT, false); // release
|
|
|
338
380
|
| symptom | cause |
|
|
339
381
|
|---|---|
|
|
340
382
|
| pointer handler never fires | `isKinematic` left at its `true` default |
|
|
383
|
+
| touches never arrive where there is no DOM (nx.js, a Web Worker) | melonJS older than 20.14 listened for mouse events there; update. Touch is then read from `TouchEvent`s dispatched on the canvas given as `settings.canvas` |
|
|
341
384
|
| handlers fire twice, or on dead objects | registered in the constructor instead of `onActivateEvent` |
|
|
342
385
|
| camera drag jumps around | using world coordinates instead of `gameScreenX`/`gameScreenY` |
|
|
343
386
|
| `gameX` holds screen coords where world coords were expected | read on `event.POINTERMOVE`, or on a floating region — use `gameWorldX`/`gameWorldY` |
|
|
344
387
|
| drag stops when leaving the object | region events end at the region — use `event.POINTERMOVE` |
|
|
345
388
|
| release never detected outside the region | no global pointer-up event — add a raw listener |
|
|
346
389
|
| jump fires every frame while held | `bindKey` without its third `lock` argument |
|
|
390
|
+
| the hero keeps walking after an Alt-Tab, until the key is pressed again (before 20.14) | the key's release went to another window: update, held keys are now released on blur |
|
|
391
|
+
| a region with only a `"pointerup"` callback never gets it, or the first finger's release is lost with two on one region (before 20.14) | the release was only delivered to the last pointer to press the region: update |
|
|
392
|
+
| a key bound with `bindPointer` never fires once the mouse has entered the canvas (before 20.12) | the pointer's button bindings were replaced on each event: update |
|
|
393
|
+
| firing (a key bound with `bindPointer`) stops while the button is held and the wheel turns (before 20.14) | a wheel event released the main button's key: update |
|
|
394
|
+
| clicks land off target after browser zoom or a move to another screen (before 20.14) | the pointer was scaled by the live pixel ratio against a canvas sized with the old one: update |
|
|
395
|
+
| a `Draggable` jumps away when grabbed, and stays stuck dragging, once the camera has moved (before 20.14) | it mixed screen and world coordinates: update |
|
|
347
396
|
| `invalid event type` thrown at registration | non-pointer name (`"click"`, `"mousedown"`, …) passed to `registerPointerEvent` |
|
|
348
|
-
| `no action defined for keycode N` | `bindGamepad` called before `bindKey` for that keycode |
|
|
397
|
+
| `no action defined for keycode N` | `bindGamepad` or `bindPointer` called before `bindKey` for that keycode |
|
|
349
398
|
| floating stick ignores the right half of the screen | its `area` defaults to `"half"`, its own side — use `area: "screen"` |
|
|
350
399
|
| `area: "screen"` stick still ignores part of the screen | a floating button's area comes first there — the overlap order is fixed buttons, fixed sticks, floating buttons, floating sticks |
|
|
351
400
|
| a quick tap on a stick moves instead of acting | it leaned past `dpadThreshold` or outlasted `tap.time` before lifting |
|
|
352
401
|
| stick has to be pushed well past its `threshold` before it registers | `threshold` is ADDED to the deadzone, not used instead of it |
|
|
353
402
|
| one half of a stick does nothing | each half is a separate `bindGamepad`, chosen by the sign of `threshold` |
|
|
403
|
+
| a key stays pressed after its pad is unplugged, or a stick bound on one side holds its key after coming back to the centre (before 20.12) | update: an unplugged pad lets go of what it held, and a stick lets go when it returns to rest, even just past the centre |
|
|
354
404
|
| a widget under an overlapping panel still lights up | the panel consumes no hover: return `false` from its `onOver` (presses already stop at UI elements; `UIPanel` with `blocking` does both) |
|
|
355
405
|
| a widget stays highlighted after the pointer moves onto a panel over it | the panel consumes no `pointermove`, so nothing takes the pointer off the widget |
|
|
356
406
|
| a child's big `z` does not put it in front of another container | `pos.z` is container-local — use `ancestor.moveToTop()` on the container |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-lighting
|
|
3
|
-
description: "Use this skill for 2D dynamic lighting in melonJS — Light2d, ambient light on a Stage, multiple lights, and per-pixel normal-mapped sprites authored in SpriteIlluminator. Covers specular highlights via shininess, the ambient-cutout model, which parts need a GPU backend, and why Light2d does not survive a Camera3d. Triggers on: Light2d, specular, shininess, highlight, glint, lighting, ambientLight, ambientLightingColor, normalMap, SpriteIlluminator, per-pixel lighting, drawLight, torch, glow, darkness."
|
|
3
|
+
description: "Use this skill for 2D dynamic lighting in melonJS — Light2d, ambient light on a Stage, multiple lights, and per-pixel normal-mapped sprites authored in SpriteIlluminator. Covers specular highlights via shininess, the ambient-cutout model, which parts need a GPU backend, and why Light2d does not survive a Camera3d. Triggers on: Light2d, softness, soft light, lightHeight, setRadii, specular, shininess, highlight, glint, lighting, ambientLight, ambientLightingColor, normalMap, SpriteIlluminator, per-pixel lighting, drawLight, torch, glow, darkness."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -31,6 +31,11 @@ intensity = 0.7)`, and `pos` is the light's **centre** — its `anchorPoint` is
|
|
|
31
31
|
`(0.5, 0.5)`, and `centerX` / `centerY` are overridden to return `pos` directly.
|
|
32
32
|
Give `radiusY` a different value for a stretched, elliptical light.
|
|
33
33
|
|
|
34
|
+
Resize a light with `light.setRadii(radiusX, radiusY)`, which resizes its
|
|
35
|
+
bounds too. Writing `radiusX` alone changes nothing you can see: the glow, the
|
|
36
|
+
hole in the darkness and the normal-map shading are all sized from the bounds,
|
|
37
|
+
on every backend.
|
|
38
|
+
|
|
34
39
|
A `Light2d` added to the world **registers itself with the stage's ambient
|
|
35
40
|
overlay on activate**, so you add it like any other renderable and it takes part
|
|
36
41
|
in the lighting pass. It draws additively (`blendMode = "lighter"`); set
|
|
@@ -105,6 +110,11 @@ const hero = new Sprite(x, y, {
|
|
|
105
110
|
canvas, `ImageBitmap`, a `Texture2d`). Assigning anything else — including an
|
|
106
111
|
`HTMLVideoElement`, which would freeze on frame 0 — throws a `TypeError`.
|
|
107
112
|
|
|
113
|
+
How grazing the light is comes from `light.lightHeight`, its height above the
|
|
114
|
+
sprite plane in the same units as its radius (default `max(radiusX, radiusY) *
|
|
115
|
+
0.075`). Lower it for light raking across the surface, which shows the relief
|
|
116
|
+
off; raise it for flatter, more even shading.
|
|
117
|
+
|
|
108
118
|
Normal maps are typically authored in **SpriteIlluminator**. The sidecar pattern
|
|
109
119
|
above — one `_n` image beside each diffuse image — is the simplest setup, but an
|
|
110
120
|
atlas can carry one too: `new TextureAtlas(json, image, { normalMap })` pairs a
|
|
@@ -209,6 +219,8 @@ See the `melonjs-3d` skill.
|
|
|
209
219
|
| sprite not shaded by a light, only glowed over | no `normalMap` on the sprite, or the Canvas backend is active |
|
|
210
220
|
| normal-mapped sprite is pure black where unlit | `Stage.ambientLightingColor` left at black |
|
|
211
221
|
| the 33rd light shades nothing | `MAX_LIGHTS = 32` for the normal-map pipeline |
|
|
222
|
+
| changing `radiusX` / `radiusY` does nothing | everything is sized from the bounds, which `setRadii()` updates and a bare field write does not |
|
|
223
|
+
| normal-mapped relief looks flat under a light | `lightHeight` is high relative to the radius; lower it for a grazing light |
|
|
212
224
|
| `TypeError` assigning `normalMap` | not an image-like value (a video element is rejected outright) |
|
|
213
225
|
| lighting misaligned under a 3D camera | the 2D light pass is not projection-aware — use `Light3d` |
|
|
214
226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-loading-assets
|
|
3
|
-
description: "Use this skill for loading and managing game assets in melonJS — the resource descriptor format, the full asset type list, preload versus load, retrieving loaded assets, base URLs and cross-origin settings, and the loading screen. Triggers on: loader, loader.preload, loader.load, resources, getImage, getJSON, getTMX, getGLTF, getVideo, getFont, setBaseURL, setOptions, crossOrigin, withCredentials, credentials, cookies, authenticated assets, CORS, asset, preload, LOADER_COMPLETE, loading screen, unload, setParser, custom asset type, generated texture, procedural texture."
|
|
3
|
+
description: "Use this skill for loading and managing game assets in melonJS — the resource descriptor format, the full asset type list, organising a multi-level game's assets as bundles (an assets file exporting common plus one bundle per level, passed to loader.preload), loading and freeing a level's bundle, preload versus load, retrieving loaded assets, base URLs and cross-origin settings, and the loading screen. Triggers on: loader, loader.preload, loader.load, resources, resources.js, assets file, getImage, getJSON, getTMX, getGLTF, getVideo, getFont, setBaseURL, setOptions, crossOrigin, withCredentials, credentials, cookies, authenticated assets, CORS, asset, preload, LOADER_COMPLETE, loading screen, unload, unloadAll, bundle, asset bundle, setBundle, getBundle, isLoaded, several levels, per-level assets, free memory between levels, setParser, custom asset type, generated texture, procedural texture."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,26 +8,13 @@ license: MIT
|
|
|
8
8
|
|
|
9
9
|
## The resource descriptor
|
|
10
10
|
|
|
11
|
-
Every asset is `{ name, type, src }` plus type-specific extras
|
|
11
|
+
Every asset is `{ name, type, src }` plus type-specific extras. The name is
|
|
12
|
+
what the asset is read back by; the type picks the parser. Type and name
|
|
13
|
+
together identify an asset, so an atlas's image and its JSON share a name:
|
|
12
14
|
|
|
13
15
|
```js
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
await loader.preload([
|
|
17
|
-
{ name: "tiles", type: "image", src: "data/img/tiles.png" },
|
|
18
|
-
{ name: "map1", type: "tmx", src: "data/map/map1.tmx" },
|
|
19
|
-
{ name: "tileset", type: "tsx", src: "data/map/tileset.tsx" },
|
|
20
|
-
{ name: "sfx", type: "audio", src: "data/sfx/" },
|
|
21
|
-
{ name: "font", type: "fontface", src: "data/font/PressStart2P.ttf" },
|
|
22
|
-
{ name: "atlas", type: "json", src: "data/img/atlas.json" },
|
|
23
|
-
]);
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
`preload()` has been awaitable since 19.8. The callback form still works and
|
|
27
|
-
takes a third argument to suppress the built-in loading screen:
|
|
28
|
-
|
|
29
|
-
```js
|
|
30
|
-
loader.preload(resources, onLoaded, /* switchToLoadState */ false);
|
|
16
|
+
{ name: "texture", type: "image", src: "data/img/texture.png" }
|
|
17
|
+
{ name: "texture", type: "json", src: "data/img/texture.json" }
|
|
31
18
|
```
|
|
32
19
|
|
|
33
20
|
## The type list
|
|
@@ -36,7 +23,7 @@ The type strings are melonJS-specific — this is not a generic loader:
|
|
|
36
23
|
|
|
37
24
|
| type | for |
|
|
38
25
|
|---|---|
|
|
39
|
-
| `"image"` | textures, sprite sheets, normal maps |
|
|
26
|
+
| `"image"` | textures, sprite sheets, normal maps; `src` may be an array tried in order, such as compressed formats (`.ktx`, `.ktx2`, `.dds`, `.pvr`, `.pkm`) by priority with a PNG last |
|
|
40
27
|
| `"json"` | data, texture-atlas descriptors, collision shapes from a shape editor |
|
|
41
28
|
| `"tmx"` / `"tsx"` | Tiled maps and external tilesets |
|
|
42
29
|
| `"audio"` | sound — **`src` is a directory**, see below |
|
|
@@ -50,6 +37,128 @@ The type strings are melonJS-specific — this is not a generic loader:
|
|
|
50
37
|
| `"js"` | scripts |
|
|
51
38
|
| any type you register | your own formats, and things you generate rather than download — see [Custom asset types](#custom-asset-types) |
|
|
52
39
|
|
|
40
|
+
## Organise the assets: `common` plus one bundle per level
|
|
41
|
+
|
|
42
|
+
For any game with **more than one level or screen**, the assets file exports a
|
|
43
|
+
**record of bundles**: what every level uses under `common`, and each level's
|
|
44
|
+
own assets in a bundle of its own. The boot call takes the record as it is:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// resources.js
|
|
48
|
+
export const resources = {
|
|
49
|
+
common: [ // everything every level uses
|
|
50
|
+
{ name: "player", type: "image", src: "data/img/player.png" },
|
|
51
|
+
{ name: "tileset", type: "image", src: "data/img/tileset.png" },
|
|
52
|
+
{ name: "tileset", type: "tsx", src: "data/map/tileset.tsx" },
|
|
53
|
+
{ name: "music", type: "audio", src: "data/bgm/" },
|
|
54
|
+
{ name: "jump", type: "audio", src: "data/sfx/" },
|
|
55
|
+
],
|
|
56
|
+
level1: [
|
|
57
|
+
{ name: "level1", type: "tmx", src: "data/map/level1.tmx" },
|
|
58
|
+
{ name: "hills", type: "image", src: "data/img/hills.png" },
|
|
59
|
+
],
|
|
60
|
+
level2: [
|
|
61
|
+
{ name: "level2", type: "tmx", src: "data/map/level2.tmx" },
|
|
62
|
+
{ name: "hills", type: "image", src: "data/img/hills.png" }, // shared: listed again
|
|
63
|
+
{ name: "lava", type: "image", src: "data/img/lava.png" },
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
// game.js
|
|
70
|
+
import { audio, loader, state } from "melonjs";
|
|
71
|
+
import { resources } from "./resources.js";
|
|
72
|
+
|
|
73
|
+
audio.init("mp3,ogg"); // before any audio is loaded
|
|
74
|
+
await loader.preload(resources); // every bundle, behind the loading screen
|
|
75
|
+
state.change(state.PLAY);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`preload(record)` declares each key as a bundle and loads them all, with one
|
|
79
|
+
progress bar; an asset several bundles list is fetched once. Nothing else
|
|
80
|
+
changes: a bundle is a lifetime, not a namespace, so assets keep their own
|
|
81
|
+
names, and `getImage("hills")`, `new Sprite(x, y, { image: "player" })` and
|
|
82
|
+
`level.load("level1")` work as with a flat list. What bundles add is that a
|
|
83
|
+
level can later be freed and loaded again **by its id** (next section). Starting
|
|
84
|
+
with bundles costs nothing even if the game never unloads anything.
|
|
85
|
+
|
|
86
|
+
- **Name a level's bundle after its map.** Bundle ids and asset names are
|
|
87
|
+
separate, so `"level1"` can be both, and `level.load(id)` and
|
|
88
|
+
`loader.load(id)` then take the same string.
|
|
89
|
+
- **List a shared asset in every bundle that uses it.** It is still fetched
|
|
90
|
+
once, and it is what keeps the asset alive when one of those bundles is
|
|
91
|
+
unloaded.
|
|
92
|
+
- **A map's dependencies go with it**: its `.tsx` and tileset images in the
|
|
93
|
+
same bundle, or in `common`.
|
|
94
|
+
- **Effects that outlive a level go in `common`** (a HUD's or a camera's
|
|
95
|
+
shader): unloading the level's bundle would destroy them under whatever
|
|
96
|
+
still draws with them.
|
|
97
|
+
|
|
98
|
+
**A flat array is the simplest form**, and is fine for a single-screen game, a
|
|
99
|
+
prototype or a jam game. It works as it always has, and everything in it stays
|
|
100
|
+
in memory for the whole game:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
await loader.preload([
|
|
104
|
+
{ name: "tiles", type: "image", src: "data/img/tiles.png" },
|
|
105
|
+
{ name: "map1", type: "tmx", src: "data/map/map1.tmx" },
|
|
106
|
+
{ name: "sfx", type: "audio", src: "data/sfx/" },
|
|
107
|
+
]);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Moving to bundles later is a change to the assets file alone: wrap the list in
|
|
111
|
+
`{ common: [ … ] }` and split the levels out.
|
|
112
|
+
|
|
113
|
+
Either way, `preload()` can be awaited (since 19.8) or given a callback, and a
|
|
114
|
+
third argument suppresses the built-in loading screen:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
loader.preload(resources, onLoaded, /* switchToLoadState */ false);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Level by level: load before, unload after
|
|
121
|
+
|
|
122
|
+
With every bundle preloaded at boot, nothing more is needed to play. To keep
|
|
123
|
+
memory bounded in a game whose levels are many or large:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import { level, loader } from "melonjs";
|
|
127
|
+
|
|
128
|
+
// leaving level1 for good: load the next, switch, then free the previous
|
|
129
|
+
await loader.load("level2"); // fetches nothing if still in memory
|
|
130
|
+
await level.load("level2", { async: true });
|
|
131
|
+
loader.unload("level1"); // "hills" stays: level2 lists it too
|
|
132
|
+
|
|
133
|
+
// going back to a level that was unloaded
|
|
134
|
+
await loader.load("level1"); // fetches what unloading freed
|
|
135
|
+
await level.load("level1", { async: true });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- **`await loader.load(id)` before entering a level that may not be in
|
|
139
|
+
memory.** When the bundle is still loaded it resolves at once with no
|
|
140
|
+
request, so it is cheap to call every time. It does not show the loading
|
|
141
|
+
screen (the game keeps running); use `loader.preload([id])` for that, or
|
|
142
|
+
`loader.isLoaded(id)` to decide.
|
|
143
|
+
- **`loader.unload(id)` for a level the player will not come back to soon,
|
|
144
|
+
after the next level is in.** It frees what no other loaded bundle lists.
|
|
145
|
+
Unloading the level being played leaves its world as it is, but with no
|
|
146
|
+
current level, so `level.reload()` has nothing to reload.
|
|
147
|
+
|
|
148
|
+
**When not to unload:**
|
|
149
|
+
|
|
150
|
+
- **Levels that loop back** — a hub, a retry after game over, a last level
|
|
151
|
+
whose exit leads to the first. Unloading one means fetching it again, and a
|
|
152
|
+
hitch, every time the player returns.
|
|
153
|
+
- **A small game.** If every level fits comfortably in memory, keep
|
|
154
|
+
everything loaded. The platformer example does exactly this: `resources.ts`
|
|
155
|
+
exports `{ common, map1, map2 }`, `preload(resources)` loads it all at boot,
|
|
156
|
+
and since map2's exit leads back to map1, nothing is ever unloaded.
|
|
157
|
+
|
|
158
|
+
Unload when the levels are large (big tilesets and backgrounds, per-level
|
|
159
|
+
music, 3D scenes), numerous, or linear (a level behind the player for good),
|
|
160
|
+
and on memory-constrained targets such as phones and mini-game platforms.
|
|
161
|
+
|
|
53
162
|
## What belongs in the manifest — and what cannot
|
|
54
163
|
|
|
55
164
|
Preloading is not only for things with a URL. Three tiers, and the middle one
|
|
@@ -117,8 +226,8 @@ const app = new Application(1280, 720, {
|
|
|
117
226
|
|
|
118
227
|
**You do not call this yourself.** `preload()` calls `renderer.prewarm()` once
|
|
119
228
|
the assets are in and **before** `LOADER_COMPLETE` fires, so it happens while
|
|
120
|
-
the progress bar is still up. Every `preload()` warms, not just the first,
|
|
121
|
-
|
|
229
|
+
the progress bar is still up. Every `preload()` warms, not just the first, and
|
|
230
|
+
so does `load(id)`, so loading a level's bundle warms whatever it brought.
|
|
122
231
|
|
|
123
232
|
The one precondition for this part: **a game that never calls
|
|
124
233
|
`loader.preload()` does not get it**. If you load assets another way, call
|
|
@@ -361,15 +470,71 @@ still loading joins it, and the value covers both until everything in flight
|
|
|
361
470
|
has settled; the next preload starts from zero again. A failed asset keeps
|
|
362
471
|
its preload short of `1`, and only that preload.
|
|
363
472
|
|
|
364
|
-
##
|
|
473
|
+
## Bundle and unloading reference
|
|
474
|
+
|
|
475
|
+
Bundles can also be declared one by one, which is what `preload(record)` does
|
|
476
|
+
for each key:
|
|
365
477
|
|
|
366
478
|
```js
|
|
367
|
-
loader.
|
|
368
|
-
|
|
479
|
+
loader.setBundle("level3", [
|
|
480
|
+
{ name: "level3", type: "tmx", src: "data/map/level3.tmx" },
|
|
481
|
+
{ name: "hills", type: "image", src: "data/img/hills.png" },
|
|
482
|
+
]);
|
|
483
|
+
await loader.preload(["common", "level3"]); // ids and descriptors mix freely
|
|
369
484
|
```
|
|
370
485
|
|
|
371
|
-
|
|
372
|
-
|
|
486
|
+
- **Strings are bundles, objects are assets.** `loader.unload("map1")`
|
|
487
|
+
releases the bundle `"map1"`; `loader.unload({ name: "map1", type: "tmx" })`
|
|
488
|
+
frees that one asset at once, whoever holds it (the bundles listing it then
|
|
489
|
+
report `isLoaded(id) === false`, and loading one again fetches it again).
|
|
490
|
+
- **`load(id)`** goes through the same path as `preload()`: `LOADER_PROGRESS`
|
|
491
|
+
for the bundle's own assets, the shader warm-up, then `LOADER_COMPLETE`. It
|
|
492
|
+
does not switch to the loading screen, and always returns a promise
|
|
493
|
+
(`onload` / `onerror` callbacks are optional extras).
|
|
494
|
+
- **`load(id, assets)` declares and loads in one call** — `setBundle(id, assets)`
|
|
495
|
+
then `load(id)`. Called again with the same assets (type, name, src) it just
|
|
496
|
+
loads; with different ones it redefines the bundle, which throws while that
|
|
497
|
+
bundle is loaded. Handy where a level's assets are declared next to the code
|
|
498
|
+
that enters it.
|
|
499
|
+
- **`preload(record)` again** fetches nothing already in memory. A bundle
|
|
500
|
+
already declared with the same assets is left as it is. A single descriptor
|
|
501
|
+
passed by mistake throws: wrap it in an array.
|
|
502
|
+
- **Progress:** each asset of the bundle is one step; one already in memory
|
|
503
|
+
counts as a step done at once. An asset listed twice is one asset, and an
|
|
504
|
+
empty bundle is allowed (it is always loaded).
|
|
505
|
+
- **Nothing is fetched twice.** An asset already in memory is not requested
|
|
506
|
+
again, however it got there, and two bundles loading the same asset at once
|
|
507
|
+
share one request.
|
|
508
|
+
- **Ownership.** Each loaded bundle listing an asset holds it; `unload(id)`
|
|
509
|
+
frees the assets no other loaded bundle lists. An asset loaded the old way —
|
|
510
|
+
a flat `preload` list, or `load(asset)` — is **pinned**: no bundle unload
|
|
511
|
+
frees it, only `unload(asset)` or `unloadAll()`.
|
|
512
|
+
- **`isLoaded(id)`** is true when every asset of the bundle is in memory,
|
|
513
|
+
however it got there. `getBundle(id)` returns a copy of the list, or
|
|
514
|
+
`undefined` for an id never declared.
|
|
515
|
+
- **Freeing a map unregisters its level.** After `unload(id)`, `level.load` of
|
|
516
|
+
a map (or glTF scene) that bundle freed throws "level … not found" until the
|
|
517
|
+
bundle is loaded again, which registers it again.
|
|
518
|
+
- **Freeing releases the GPU side too:** an image's texture (since 20.14), a
|
|
519
|
+
shader's program, a font's place in `document.fonts`. `getShader()` hands out
|
|
520
|
+
the loader's shared instance and does not track who took it, hence
|
|
521
|
+
"effects that outlive a level go in `common`".
|
|
522
|
+
- **A failing asset rejects `load(id)`**, like `preload`; the assets that did
|
|
523
|
+
load stay, held by the bundle, and `loader.reload(src)` completes it.
|
|
524
|
+
- **Errors are synchronous.** An unknown id throws in `load`, `unload`,
|
|
525
|
+
`preload` and `isLoaded`. Redefining a bundle with `setBundle` throws while
|
|
526
|
+
it is loading or loaded: unload it first. A bundle lists descriptors only,
|
|
527
|
+
not other bundle ids.
|
|
528
|
+
- **`unloadAll()`** frees every asset, audio included, whoever holds it. The
|
|
529
|
+
bundles stay declared, all back to not loaded, ready to be loaded again.
|
|
530
|
+
Levels stay registered (unlike a bundle unload), so load a level's bundle
|
|
531
|
+
again before `level.load` of it.
|
|
532
|
+
- **`load(asset)`, with a descriptor**, loads one asset while the game runs:
|
|
533
|
+
no loading screen, progress or loader event, only its promise (or the
|
|
534
|
+
`onload` / `onerror` callbacks). It is pinned. One already in memory
|
|
535
|
+
resolves at once with no request (the callback form returns `0` and never
|
|
536
|
+
calls `onload`), except a `"binary"`, `"fontface"`, `"aseprite"` or `"js"`
|
|
537
|
+
asset, which is fetched again.
|
|
373
538
|
|
|
374
539
|
## Loading between scenes
|
|
375
540
|
|
|
@@ -459,6 +624,16 @@ throws, the same trap as the removed `loader.onload` / `onProgress`.
|
|
|
459
624
|
| `load : unknown or invalid resource type` | a custom type in the manifest before its `loader.setParser` call |
|
|
460
625
|
| black or frozen screen when a stage is first entered, the profile shows your own code (noise, a canvas bake) | a costly derived asset built on stage entry — bake it inside the preload as a [custom asset type](#custom-asset-types) |
|
|
461
626
|
| a custom asset preloads but the result is nowhere to be found | `onload` takes no value and the loader stores nothing for custom types — keep the result in your own module |
|
|
627
|
+
| `level X not found` after switching levels with bundles | that map's bundle was unloaded (it unregisters the level), or never loaded: `await loader.load(id)` before `level.load` |
|
|
628
|
+
| `loader: unknown bundle "…"` | a typo, or `setBundle` not called yet; ids and asset names are separate, a bundle is not found by an asset's name |
|
|
629
|
+
| `setBundle(…): the bundle is loaded` | redefining a loaded bundle, by `setBundle` or by `load(id, assets)` with a different list: `loader.unload(id)` first |
|
|
630
|
+
| a shared image disappears (blank layer) after unloading the previous level's bundle | the next level's bundle does not list it: list a shared asset in every bundle that uses it |
|
|
631
|
+
| an asset survives `unload(id)` and memory stays up | it was also loaded the old way (a flat `preload` list, or `load(asset)`), which pins it; load it only through bundles, or `unload(asset)` |
|
|
632
|
+
| a HUD or camera effect goes blank, or throws, after a level's bundle is unloaded | its shader came from that bundle; effects that outlive a level belong in a bundle that outlives it (`"common"`) |
|
|
633
|
+
| `level.reload()` throws `level undefined not found` | the current level's own bundle was unloaded; unload the previous level's bundle after the next level is in |
|
|
634
|
+
| a pause and new network requests every time the player goes back to an earlier level | that level's bundle is unloaded on leaving it although the game loops back to it; keep it loaded (see [When not to unload](#level-by-level-load-before-unload-after)) |
|
|
635
|
+
| memory grows level after level although assets are unloaded (before 20.14) | unloading an image left its GPU texture in the renderer; update |
|
|
636
|
+
| an asset given as a `data:` URI fails, or the default loading screen shows no logo, in a sandboxed embed or under a strict Content-Security-Policy (before 20.11) | the `data:` URI was fetched, which a `connect-src` without `data:` refuses; update, it is decoded in place |
|
|
462
637
|
|
|
463
638
|
## Related skills
|
|
464
639
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-math
|
|
3
|
-
description: "Use this skill for vector and matrix maths in melonJS — Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z
|
|
3
|
+
description: "Use this skill for vector and matrix maths in melonJS — Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z, moveTowards ignores z, setPosition, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix, Noise, getNoise1d, flicker, wobble."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -97,6 +97,9 @@ v.scale(2, 2, 2); // (1,2,3) -> (2,4,6) what you meant
|
|
|
97
97
|
v.set(10, 20); // z is now 0, whatever it was
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
To move a renderable, `setPosition(x, y)` (20.8) is the call that keeps its
|
|
101
|
+
depth: leave out `z` and it stays as it was, where `pos.set(x, y)` zeroes it.
|
|
102
|
+
|
|
100
103
|
**`Vector3d.pos` on a renderable is typed `Vector2d`.** `Renderable.pos` is an
|
|
101
104
|
`ObservableVector3d` at run time but is declared through `Polygon` as a
|
|
102
105
|
`Vector2d`, so `.z`, `.setMuted` and `.setCallback` need a cast from
|
|
@@ -117,7 +120,7 @@ The 2D one is the z component of the 3D cross, which is the useful scalar for
|
|
|
117
120
|
```js
|
|
118
121
|
a.lerp(b, t); // straight line, t in 0..1
|
|
119
122
|
a.slerp(b, t); // along the ARC, constant angular rate
|
|
120
|
-
a.damp(b, lambda, dt); // frame-rate independent ease toward b
|
|
123
|
+
a.damp(b, lambda, dt); // frame-rate independent ease toward b (dt in seconds)
|
|
121
124
|
a.moveTowards(b, step); // fixed distance per call, never overshoots
|
|
122
125
|
```
|
|
123
126
|
|
|
@@ -292,11 +295,13 @@ math.clamp(v, lo, hi);
|
|
|
292
295
|
math.lerp(a, b, t); // scalars
|
|
293
296
|
math.damp(a, b, lambda, dt); // frame-rate independent
|
|
294
297
|
math.degToRad(deg); math.radToDeg(rad);
|
|
295
|
-
math.random(lo, hi); // integer
|
|
298
|
+
math.random(lo, hi); // integer, hi EXCLUDED: random(1, 6) never gives 6
|
|
296
299
|
math.randomFloat(lo, hi);
|
|
297
|
-
math.weightedRandom(lo, hi); // integer, biased toward LO
|
|
300
|
+
math.weightedRandom(lo, hi); // integer, biased toward LO, hi excluded
|
|
298
301
|
math.round(n, decimals); // nearest; a tie goes up, as Math.round: -2.5 → -2
|
|
299
302
|
math.pow(n); // n squared
|
|
303
|
+
math.lerpArray([1, 0.5, 0], t); // a value along evenly spaced samples, t in 0..1
|
|
304
|
+
math.isPowerOfTwo(n); math.nextPowerOfTwo(n);
|
|
300
305
|
math.DEG_TO_RAD math.RAD_TO_DEG math.TAU math.ETA math.EPSILON
|
|
301
306
|
```
|
|
302
307
|
|
|
@@ -324,11 +329,13 @@ and `Math.random()` every frame jitters instead of drifting.
|
|
|
324
329
|
| symptom | cause |
|
|
325
330
|
|---|---|
|
|
326
331
|
| z unchanged after scaling | `scale(s)` leaves z alone; pass `scale(s, s, s)` |
|
|
327
|
-
| z became 0 for no reason | `set(x, y)` defaults z to 0 |
|
|
332
|
+
| z became 0 for no reason | `set(x, y)` defaults z to 0; move a renderable with `setPosition(x, y)` |
|
|
328
333
|
| the original vector changed | the method mutates; `copy()` into something first |
|
|
329
334
|
| a position callback fires twice and sees a half-updated vector, or `pos.floorSelf()` returns something that is not `pos` | before 20.13 the observable `negateSelf` / `floorSelf` / `ceilSelf` / `clampSelf` wrote component by component; upgrade |
|
|
330
335
|
| `math.round(-2.7)` is -2, or a large value comes back negative | before 20.13 `round` truncated toward zero through 32-bit integers; upgrade |
|
|
336
|
+
| `math.isPowerOfTwo(0)` is true, or a fraction passes | before 20.13 it accepted zero, negative and fractional values; upgrade |
|
|
331
337
|
| memory climbs in a loop | `clone()` or `negate()`/`floor()`/`ceil()` per frame |
|
|
338
|
+
| memory taken by dropped `clone()`s never comes back | before 20.9 the pool kept every instance it handed out; upgrade, and release what you clone |
|
|
332
339
|
| two helper results are the same object | a shared scratch vector was returned |
|
|
333
340
|
| `.z` is a type error | `Renderable.pos` is declared `Vector2d` (#1510) |
|
|
334
341
|
| rotation eases oddly between directions | `lerp` on directions; use `slerp` |
|
|
@@ -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, 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."
|
|
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, stopStream, autoDestroyOnComplete, onComplete, sprite sheet particles, framewidth, randomFrame, cone, sphere, 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
|
|
|
@@ -64,7 +64,8 @@ It covers every setting that changes how one emitter looks, but not:
|
|
|
64
64
|
the one drawn on top last (or with a higher `z`).
|
|
65
65
|
- **Trails.** `Trail` and `Trail3d` are separate renderables; see below.
|
|
66
66
|
- **Code-only settings.** `onComplete`, `autoDestroyOnComplete`,
|
|
67
|
-
`accurateBounds
|
|
67
|
+
`accurateBounds` (deprecated since 20.2, and does nothing), and a
|
|
68
|
+
`Container` as `referenceSpace` (it offers
|
|
68
69
|
`"local"` and `"world"`). `textureAdditive` is left out: `blendMode`
|
|
69
70
|
replaces it.
|
|
70
71
|
- **Changes at run time.** Calling `burstParticles()` when something
|
|
@@ -258,7 +259,9 @@ debris.
|
|
|
258
259
|
|
|
259
260
|
For one-shot effects, `autoDestroyOnComplete: true` removes the emitter once its
|
|
260
261
|
particles have finished. The `onComplete` callback fires at that same moment
|
|
261
|
-
either way.
|
|
262
|
+
either way. A stream finishes only once it stops emitting: one left on the
|
|
263
|
+
default `duration` (`Infinity`) never completes until you call `stopStream()`,
|
|
264
|
+
so give a stream that should clean up after itself a `duration`.
|
|
262
265
|
|
|
263
266
|
For repeated effects (muzzle flashes, impacts) keep **one** long-lived emitter
|
|
264
267
|
and re-aim it per shot, rather than allocating an emitter per event.
|
|
@@ -392,6 +395,7 @@ answer when you want discrete puffs rather than a continuous ribbon.
|
|
|
392
395
|
| the cloud follows a moving emitter | default `referenceSpace: "local"` — use `"world"` for trails |
|
|
393
396
|
| particles do not travel far enough | settings tuned pre-20.2, when drawn travel was doubled |
|
|
394
397
|
| one-shot emitters accumulate | missing `autoDestroyOnComplete: true` |
|
|
398
|
+
| a streaming emitter with `autoDestroyOnComplete` never goes away | the stream runs on the default `duration` of `Infinity`: give it a `duration`, or call `stopStream()` |
|
|
395
399
|
| every particle of an emitter is the same colour all its life | `tint` is one value for the emitter: use `tintOverLife` for a colour that changes as it ages |
|
|
396
400
|
| a `tintOverLife` colour's transparency has no effect | the list sets the colour only; opacity is the particle's fade |
|
|
397
401
|
| `tintOverLife` colours look muddy | the texture is coloured; author it white so the tints come through as given |
|