melonjs 20.8.0 → 20.10.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 +1 -0
- package/build/application/application.d.ts +66 -8
- package/build/application/application.d.ts.map +1 -1
- package/build/application/defaultApplicationSettings.d.ts +1 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/resize.d.ts +25 -0
- package/build/application/resize.d.ts.map +1 -1
- package/build/application/scaleMethods.d.ts +13 -3
- package/build/application/scaleMethods.d.ts.map +1 -1
- package/build/application/settings.d.ts +68 -13
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +29 -5
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/autopause.d.ts +36 -0
- package/build/audio/autopause.d.ts.map +1 -0
- package/build/audio/backend/engine.d.ts +10 -0
- package/build/audio/backend/engine.d.ts.map +1 -1
- package/build/audio/backend/sound.d.ts.map +1 -1
- package/build/audio/backend/spatial.d.ts.map +1 -1
- package/build/audio/context.d.ts +35 -0
- package/build/audio/context.d.ts.map +1 -0
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/procedural.d.ts.map +1 -1
- package/build/audio/spatial.d.ts +87 -0
- package/build/audio/spatial.d.ts.map +1 -0
- package/build/audio/state.d.ts +1 -0
- package/build/audio/state.d.ts.map +1 -1
- package/build/audio/types.d.ts +46 -6
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/effects/fade_effect.d.ts.map +1 -1
- package/build/camera/effects/mask_effect.d.ts.map +1 -1
- package/build/camera/effects/shake_effect.d.ts.map +1 -1
- package/build/geometries/box3d.d.ts +1 -1
- package/build/geometries/box3d.d.ts.map +1 -1
- package/build/geometries/ellipse.d.ts +1 -1
- package/build/geometries/ellipse.d.ts.map +1 -1
- package/build/geometries/line.d.ts +1 -1
- package/build/geometries/line.d.ts.map +1 -1
- package/build/geometries/point.d.ts +1 -1
- package/build/geometries/point.d.ts.map +1 -1
- package/build/geometries/polygon.d.ts +1 -1
- package/build/geometries/polygon.d.ts.map +1 -1
- package/build/geometries/rectangle.d.ts +1 -1
- package/build/geometries/rectangle.d.ts.map +1 -1
- package/build/geometries/roundrect.d.ts +1 -1
- package/build/geometries/roundrect.d.ts.map +1 -1
- package/build/geometries/sphere.d.ts +1 -1
- package/build/geometries/sphere.d.ts.map +1 -1
- package/build/index.d.ts +9 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +38737 -35039
- package/build/index.js.map +4 -4
- package/build/input/gamepad.d.ts +129 -3
- package/build/input/gamepad.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts +5 -4
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/gltf/GLTFScene.d.ts.map +1 -1
- package/build/level/tiled/TMXObjectFactory.d.ts +3 -2
- package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +1 -4
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/loader/cache.d.ts +220 -0
- package/build/loader/cache.d.ts.map +1 -1
- package/build/loader/loader.d.ts +20 -349
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/parsers/gltf.d.ts +115 -0
- package/build/loader/parsers/gltf.d.ts.map +1 -1
- package/build/math/color.d.ts +1 -1
- package/build/math/color.d.ts.map +1 -1
- package/build/math/matrix2d.d.ts +1 -1
- package/build/math/matrix2d.d.ts.map +1 -1
- package/build/math/matrix3d.d.ts +1 -1
- package/build/math/matrix3d.d.ts.map +1 -1
- package/build/math/observableVector2d.d.ts +14 -2
- package/build/math/observableVector2d.d.ts.map +1 -1
- package/build/math/observableVector3d.d.ts +15 -1
- package/build/math/observableVector3d.d.ts.map +1 -1
- package/build/math/vector2d.d.ts +31 -1
- package/build/math/vector2d.d.ts.map +1 -1
- package/build/math/vector3d.d.ts +38 -1
- package/build/math/vector3d.d.ts.map +1 -1
- package/build/math/vertex.d.ts.map +1 -1
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +7 -1
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +35 -0
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/bounds.d.ts +1 -1
- package/build/physics/bounds.d.ts.map +1 -1
- package/build/physics/broadphase/octree.d.ts +24 -0
- package/build/physics/broadphase/octree.d.ts.map +1 -1
- package/build/physics/broadphase/quadtree.d.ts +31 -0
- package/build/physics/broadphase/quadtree.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +14 -0
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/pool.d.ts +4 -0
- package/build/pool.d.ts.map +1 -1
- package/build/renderable/colorlayer.d.ts +13 -0
- package/build/renderable/colorlayer.d.ts.map +1 -1
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/groundshadow.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +56 -0
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +97 -11
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +47 -3
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +2 -1
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/sprite3d.d.ts +6 -2
- package/build/renderable/sprite3d.d.ts.map +1 -1
- package/build/renderable/text/bitmaptext.d.ts +63 -2
- package/build/renderable/text/bitmaptext.d.ts.map +1 -1
- package/build/renderable/text/bitmaptextdata.d.ts +1 -1
- package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
- package/build/renderable/text/glypheffect.d.ts +44 -0
- package/build/renderable/text/glypheffect.d.ts.map +1 -0
- package/build/renderable/text/text.d.ts +17 -0
- package/build/renderable/text/text.d.ts.map +1 -1
- package/build/renderable/trail.d.ts.map +1 -1
- package/build/renderable/trail3d.d.ts +174 -0
- package/build/renderable/trail3d.d.ts.map +1 -0
- package/build/renderable/trigger.d.ts +2 -1
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/panelpaint.d.ts +5 -0
- package/build/renderable/ui/panelpaint.d.ts.map +1 -0
- package/build/renderable/ui/progressbar.d.ts +2 -4
- package/build/renderable/ui/progressbar.d.ts.map +1 -1
- package/build/renderable/ui/uipanel.d.ts +245 -0
- package/build/renderable/ui/uipanel.d.ts.map +1 -0
- package/build/renderable/ui/uitextbutton.d.ts +30 -1
- package/build/renderable/ui/uitextbutton.d.ts.map +1 -1
- package/build/state/state.d.ts.map +1 -1
- package/build/system/bootstrap.d.ts.map +1 -1
- package/build/system/legacy_pool.d.ts +22 -2
- package/build/system/legacy_pool.d.ts.map +1 -1
- package/build/system/pool.d.ts +5 -8
- package/build/system/pool.d.ts.map +1 -1
- package/build/tweens/tween.d.ts +1 -1
- package/build/tweens/tween.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/squish.d.ts +74 -0
- package/build/video/effects/squish.d.ts.map +1 -0
- package/build/video/effects/tintPulse.d.ts +0 -5
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +0 -5
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/gpu/batcher.d.ts +8 -2
- package/build/video/gpu/batcher.d.ts.map +1 -1
- package/build/video/gradient.d.ts +1 -0
- package/build/video/gradient.d.ts.map +1 -1
- package/build/video/renderer.d.ts +16 -3
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/render_target_pool.d.ts.map +1 -1
- package/build/video/rendertarget/rendertarget.d.ts +12 -5
- package/build/video/rendertarget/rendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/webgpurendertarget.d.ts +6 -12
- package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +1 -1
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/texture/texture2d.d.ts +6 -0
- package/build/video/texture/texture2d.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts +2 -11
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/material_batcher.d.ts +5 -0
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/mesh_batcher.d.ts +21 -0
- package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/buffer/deletable.d.ts +15 -0
- package/build/video/webgl/buffer/deletable.d.ts.map +1 -0
- package/build/video/webgl/buffer/instance_buffer.d.ts.map +1 -1
- package/build/video/webgl/buffer/retained_geometry.d.ts.map +1 -1
- package/build/video/webgl/buffer/uniformblock.d.ts.map +1 -1
- package/build/video/webgl/buffer/vertexstate.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
- package/build/video/webgpu/texture/compressed.d.ts.map +1 -1
- package/build/video/webgpu/texture/store.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +2 -2
- package/skills/AGENTS.md +1 -1
- package/skills/melonjs/SKILL.md +2 -0
- package/skills/melonjs-3d/SKILL.md +192 -18
- package/skills/melonjs-3d-assets/SKILL.md +36 -2
- package/skills/melonjs-audio/SKILL.md +229 -8
- package/skills/melonjs-camera-and-drawing/SKILL.md +73 -3
- package/skills/melonjs-display-and-scaling/SKILL.md +273 -0
- package/skills/melonjs-effects-and-shaders/SKILL.md +54 -10
- package/skills/melonjs-events/SKILL.md +8 -6
- package/skills/melonjs-input/SKILL.md +100 -4
- package/skills/melonjs-loading-assets/SKILL.md +22 -4
- package/skills/melonjs-math/SKILL.md +313 -0
- package/skills/melonjs-particles-and-trails/SKILL.md +88 -14
- package/skills/melonjs-performance/SKILL.md +57 -22
- package/skills/melonjs-physics/SKILL.md +2 -1
- package/skills/melonjs-renderables/SKILL.md +6 -1
- package/skills/melonjs-scenes-and-state/SKILL.md +14 -3
- package/skills/melonjs-sprites-and-animation/SKILL.md +9 -4
- package/skills/melonjs-tilemaps/SKILL.md +40 -12
- package/skills/melonjs-ui-and-text/SKILL.md +382 -4
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
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., moveTowards ignores z, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Vectors, matrices and maths
|
|
8
|
+
|
|
9
|
+
## The one rule that explains most surprises
|
|
10
|
+
|
|
11
|
+
**Methods mutate `this` and return it. They do not return a new vector.**
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const a = new Vector3d(1, 0, 0);
|
|
15
|
+
const b = new Vector3d(0, 1, 0);
|
|
16
|
+
|
|
17
|
+
a.cross(b); // a IS NOW the cross product; the original a is gone
|
|
18
|
+
a.add(b).normalize().scale(3, 3, 3); // chains, all in place
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That is what makes the API allocation-free, and it is the opposite of what a
|
|
22
|
+
functional style expects. If you need to keep the original, copy into
|
|
23
|
+
something you own first:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
out.copy(a).cross(b); // a untouched, nothing allocated
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**The exceptions are named.** A handful of methods return a NEW vector, and
|
|
30
|
+
each has a `*Self` sibling that does not:
|
|
31
|
+
|
|
32
|
+
| allocates a new vector | mutates in place |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `negate()` | `negateSelf()` |
|
|
35
|
+
| `floor()` | `floorSelf()` |
|
|
36
|
+
| `ceil()` | `ceilSelf()` |
|
|
37
|
+
| `clamp(low, high)` | `clampSelf(low, high)` |
|
|
38
|
+
| `clone()` | `copy(v)` |
|
|
39
|
+
|
|
40
|
+
So `v.negate()` in a per-frame loop allocates every frame and `v.negateSelf()`
|
|
41
|
+
does not. The names are the only clue; nothing in the call site shows it.
|
|
42
|
+
|
|
43
|
+
## `clone()` comes from a pool, and only pays off if you give it back
|
|
44
|
+
|
|
45
|
+
`clone()` is `vector3dPool.get(...)`. A pool is fast when instances come back:
|
|
46
|
+
`get()` takes a free one and resets it. Nothing comes back on its own, so a
|
|
47
|
+
clone that is simply dropped makes `get()` miss, construct, and write two
|
|
48
|
+
registry entries.
|
|
49
|
+
|
|
50
|
+
Measured, 3M operations:
|
|
51
|
+
|
|
52
|
+
| | |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `out.copy(a).cross(b)` | 39ms |
|
|
55
|
+
| `new Vector3d(ax, ay, az).cross(b)` | 37ms |
|
|
56
|
+
| `a.clone().cross(b)` | **2919ms** |
|
|
57
|
+
|
|
58
|
+
So:
|
|
59
|
+
|
|
60
|
+
- **Transient value inside a function?** Use a module-level scratch vector, or
|
|
61
|
+
`new Vector3d()`. Not `clone()`.
|
|
62
|
+
- **Keeping it, then done with it?** `clone()` and `vector3dPool.release(v)`
|
|
63
|
+
when finished. Releasing is what makes the pool a pool.
|
|
64
|
+
- **Writing a helper that returns a vector?** Take an optional `out`:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const cross = (a, b, out = new Vector3d()) => out.copy(a).cross(b);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That is the engine's own convention, and it lets a hot caller supply scratch
|
|
71
|
+
while a casual one ignores it.
|
|
72
|
+
|
|
73
|
+
**A returned value can never be shared scratch.** The next call overwrites
|
|
74
|
+
something the caller is still holding, and nothing fails loudly:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const _t = new Vector3d();
|
|
78
|
+
const bad = (v) => _t.copy(v).normalize(); // WRONG: two calls alias
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Argument defaults that drop a component
|
|
82
|
+
|
|
83
|
+
Three signatures quietly discard what you did not pass. All three are
|
|
84
|
+
reachable by writing the obvious thing.
|
|
85
|
+
|
|
86
|
+
**`scale(x, y = x, z = 1)`** — `z` defaults to **1**, not to `x`. A uniform
|
|
87
|
+
scale has to say so three times:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
v.scale(2); // (1,2,3) -> (2,4,3) z UNTOUCHED
|
|
91
|
+
v.scale(2, 2, 2); // (1,2,3) -> (2,4,6) what you meant
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**`set(x, y, z = 0)`** — setting two components **zeroes the third**:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
v.set(10, 20); // z is now 0, whatever it was
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**`Vector3d.pos` on a renderable is typed `Vector2d`.** `Renderable.pos` is an
|
|
101
|
+
`ObservableVector3d` at run time but is declared through `Polygon` as a
|
|
102
|
+
`Vector2d`, so `.z`, `.setMuted` and `.setCallback` need a cast from
|
|
103
|
+
TypeScript. See issue #1510.
|
|
104
|
+
|
|
105
|
+
## `cross` is two different functions
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
v3.cross(other); // Vector3d: MUTATES v3, returns this
|
|
109
|
+
v2.cross(other); // Vector2d: returns a NUMBER, mutates nothing
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The 2D one is the z component of the 3D cross, which is the useful scalar for
|
|
113
|
+
"which side is this on". Same name, different shape, different return.
|
|
114
|
+
|
|
115
|
+
## Interpolation: four of them, and they are not interchangeable
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
a.lerp(b, t); // straight line, t in 0..1
|
|
119
|
+
a.slerp(b, t); // along the ARC, constant angular rate
|
|
120
|
+
a.damp(b, lambda, dt); // frame-rate independent ease toward b
|
|
121
|
+
a.moveTowards(b, step); // fixed distance per call, never overshoots
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**`moveTowards` is X/Y ONLY, on `Vector3d` as well as `Vector2d`.** It is the
|
|
125
|
+
one of the four that is not 3D: it measures the distance from `dx` and `dy`,
|
|
126
|
+
steps along `atan2(dy, dx)`, and leaves `z` exactly where it was. The source
|
|
127
|
+
says so in a comment and the JSDoc says "on the x and y axis", but the name
|
|
128
|
+
gives no hint, and in a list like the one above it reads as the sibling of the
|
|
129
|
+
other three. Anything moving in three dimensions wants `damp` or an explicit
|
|
130
|
+
step; reach for `moveTowards` only when the motion really is planar.
|
|
131
|
+
|
|
132
|
+
**`lerp` between two directions is uneven**: it slows at the ends, runs fast
|
|
133
|
+
through the middle, and shortens the vector as it passes halfway. Anything
|
|
134
|
+
whose direction matters more than its components wants `slerp` — a camera
|
|
135
|
+
swinging to look somewhere, a turret tracking, a body moving over a sphere.
|
|
136
|
+
`slerp` carries length linearly, so two unit vectors give a unit vector and
|
|
137
|
+
nothing needs normalising afterwards.
|
|
138
|
+
|
|
139
|
+
**`damp` is the one to use for "ease toward a target every frame"**, because
|
|
140
|
+
`lerp(current, target, 0.1)` per frame is frame-rate dependent: it converges
|
|
141
|
+
twice as fast at 120fps as at 60.
|
|
142
|
+
|
|
143
|
+
## `project` scales THIS, and `projectN` is the one without the divide
|
|
144
|
+
|
|
145
|
+
`a.project(b)` does not return the component of `a` along `b`. It scales **a**
|
|
146
|
+
by `a.b / |b|^2` and returns `a`, so the result points along `a`, not along
|
|
147
|
+
`b`. Worth knowing before you reach for it expecting the textbook projection.
|
|
148
|
+
|
|
149
|
+
`projectN(v)` is the same thing for a `v` that is already unit, so it skips
|
|
150
|
+
the `/ v.length2()`:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
a.project(b); // a scaled by (a.b / |b|^2)
|
|
154
|
+
a.projectN(b); // a scaled by (a.b) -- only correct when |b| = 1
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Handing `projectN` a target that is NOT unit does not normalise anything, it
|
|
158
|
+
just scales by the raw dot product. That is the price of it being cheaper, and
|
|
159
|
+
it is the caller's job to meet the contract.
|
|
160
|
+
|
|
161
|
+
Until 20.9 both 3D classes got this wrong: `Vector3d.projectN` and
|
|
162
|
+
`ObservableVector3d.projectN` were copies of `project`, division and all,
|
|
163
|
+
under `projectN`'s name and its "slightly more efficient" doc comment. Nothing
|
|
164
|
+
in the engine called either one and nothing tested them, which is how it
|
|
165
|
+
survived. Both are fixed and all four classes are now pinned by tests.
|
|
166
|
+
|
|
167
|
+
## `length()` is `sqrt(x*x + y*y + z*z)`
|
|
168
|
+
|
|
169
|
+
Fast and correct to 1 ULP for anything a game holds. It is NOT robust at
|
|
170
|
+
extremes: components above ~1e154 overflow to `Infinity` and below ~1e-162
|
|
171
|
+
underflow to `0`, because the squares are formed before the root. `Math.hypot`
|
|
172
|
+
avoids that and costs 35 to 65% more. The engine takes the speed, which is the
|
|
173
|
+
right trade for coordinates between 1e-3 and 1e6; nothing in a game is 148
|
|
174
|
+
orders of magnitude from unit scale.
|
|
175
|
+
|
|
176
|
+
`length2()` skips the square root. Use it for comparisons:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
if (a.distance(b) < r) { } // a square root you did not need
|
|
180
|
+
if (a.sub(b).length2() < r * r) { } // same answer, cheaper
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Observable vectors
|
|
184
|
+
|
|
185
|
+
`ObservableVector2d` / `ObservableVector3d` call a callback on every write,
|
|
186
|
+
which is how a renderable knows its bounds went stale.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
v.setCallback(() => { ... });
|
|
190
|
+
v.setMuted(true); // write without firing it
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**`setMuted` is what you want when writing several components**, so a
|
|
194
|
+
half-updated vector does not fire a callback that reads the other half.
|
|
195
|
+
`set()` already does this internally: it disables the callback, writes all
|
|
196
|
+
three, fires once, and re-enables.
|
|
197
|
+
|
|
198
|
+
Note the observable `set(x = 0, y = 0, z = 0)` defaults EVERY argument, so
|
|
199
|
+
`set(10, 20)` zeroes z exactly as the plain vector does.
|
|
200
|
+
|
|
201
|
+
## Matrices
|
|
202
|
+
|
|
203
|
+
`Matrix2d` is 3x3 for 2D affine transforms; `Matrix3d` is 4x4. Both mutate and
|
|
204
|
+
chain, same as vectors, and both come from pools.
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
m.identity().translate(x, y).rotate(angle).scale(sx, sy);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Order is the reverse of how it reads.** These post-multiply, so the call
|
|
211
|
+
made LAST reaches the vertex FIRST. To rotate a thing about its own centre,
|
|
212
|
+
translate to the centre last.
|
|
213
|
+
|
|
214
|
+
### Every renderable already HAS one
|
|
215
|
+
|
|
216
|
+
`Renderable.currentTransform` is a `Matrix3d`. If you are orienting something
|
|
217
|
+
on screen, the matrix exists: write into it rather than building a parallel
|
|
218
|
+
structure and copying.
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
// the whole transform of a renderable, written in place
|
|
222
|
+
mesh.currentTransform.identity().translate(x, y, z).rotate(angle, axis);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### The sixteen numbers are `val`, column-major
|
|
226
|
+
|
|
227
|
+
Composing with `translate`/`rotate`/`scale` is one way to fill a matrix. The
|
|
228
|
+
other is to write the numbers, which is what you want when the orientation
|
|
229
|
+
comes from a basis you have already computed — three orthonormal vectors, a
|
|
230
|
+
surface frame, a camera basis.
|
|
231
|
+
|
|
232
|
+
`val` is a `Float32Array(16)` in **column-major** order, the OpenGL
|
|
233
|
+
convention: each basis vector occupies a stride-4 run, and the translation is
|
|
234
|
+
the last column.
|
|
235
|
+
|
|
236
|
+
| | X column | Y column | Z column | translation |
|
|
237
|
+
|---|---|---|---|---|
|
|
238
|
+
| indices | `0,1,2` | `4,5,6` | `8,9,10` | `12,13,14` |
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
// an orientation from three axes, straight into the renderable's own matrix
|
|
242
|
+
const m = mesh.currentTransform.val;
|
|
243
|
+
m[0] = right.x; m[1] = right.y; m[2] = right.z; m[3] = 0;
|
|
244
|
+
m[4] = up.x; m[5] = up.y; m[6] = up.z; m[7] = 0;
|
|
245
|
+
m[8] = fwd.x; m[9] = fwd.y; m[10] = fwd.z; m[11] = 0;
|
|
246
|
+
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
A helper that builds one should take the destination, the same way the engine
|
|
250
|
+
takes an `out`, so it writes once into the matrix that will be used:
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
const orient = (frame, out) => { /* writes out.val */ return out; };
|
|
254
|
+
orient(frame, mesh.currentTransform);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Getting numbers IN from somewhere else
|
|
258
|
+
|
|
259
|
+
`setTransform(...)` takes the sixteen as arguments, and `copy(m)` takes
|
|
260
|
+
another matrix:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
m.setTransform(a, b, c, d, e, f, g, h, i, j, k, l, p, q, r, s);
|
|
264
|
+
m.copy(other); // Float32Array.set under the hood
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Both are fine occasionally. In a per-frame loop, prefer writing `val` or
|
|
268
|
+
`copy()` over spreading sixteen arguments, and note that **one matrix built
|
|
269
|
+
once and applied to many renderables** is a `copy()` per renderable, not a
|
|
270
|
+
rebuild per renderable.
|
|
271
|
+
|
|
272
|
+
**`val` is `Float32Array`, so it holds float32.** Numbers you put in are
|
|
273
|
+
rounded to single precision. That is where they were always heading, since
|
|
274
|
+
this is what reaches the GPU, but it means a value read back out is not
|
|
275
|
+
bit-identical to the double you wrote.
|
|
276
|
+
|
|
277
|
+
## Utilities
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
import { math } from "melonjs";
|
|
281
|
+
|
|
282
|
+
math.clamp(v, lo, hi);
|
|
283
|
+
math.lerp(a, b, t); // scalars
|
|
284
|
+
math.damp(a, b, lambda, dt); // frame-rate independent
|
|
285
|
+
math.degToRad(deg); math.radToDeg(rad);
|
|
286
|
+
math.random(lo, hi); // integer
|
|
287
|
+
math.randomFloat(lo, hi);
|
|
288
|
+
math.weightedRandom(lo, hi); // integer, biased toward LO
|
|
289
|
+
math.round(n, decimals);
|
|
290
|
+
math.pow(n); // n squared
|
|
291
|
+
math.DEG_TO_RAD math.RAD_TO_DEG math.TAU math.ETA math.EPSILON
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Symptoms
|
|
295
|
+
|
|
296
|
+
| symptom | cause |
|
|
297
|
+
|---|---|
|
|
298
|
+
| z unchanged after scaling | `scale(s)` leaves z alone; pass `scale(s, s, s)` |
|
|
299
|
+
| z became 0 for no reason | `set(x, y)` defaults z to 0 |
|
|
300
|
+
| the original vector changed | the method mutates; `copy()` into something first |
|
|
301
|
+
| memory climbs in a loop | `clone()` or `negate()`/`floor()`/`ceil()` per frame |
|
|
302
|
+
| two helper results are the same object | a shared scratch vector was returned |
|
|
303
|
+
| `.z` is a type error | `Renderable.pos` is declared `Vector2d` (#1510) |
|
|
304
|
+
| rotation eases oddly between directions | `lerp` on directions; use `slerp` |
|
|
305
|
+
| you built your own 16-number array | `Renderable.currentTransform` is already a `Matrix3d`; write its `val` |
|
|
306
|
+
| a value read back from a matrix differs | `val` is `Float32Array`; it holds float32 |
|
|
307
|
+
| easing differs at 120fps | per-frame `lerp`; use `damp` |
|
|
308
|
+
|
|
309
|
+
## Related skills
|
|
310
|
+
|
|
311
|
+
- `melonjs-3d` — `Camera3d`, meshes, and where these vectors are used in 3D
|
|
312
|
+
- `melonjs-physics` — bodies, velocities and collision response
|
|
313
|
+
- `melonjs-performance` — pooling in general, and what release() is for
|
|
@@ -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: ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter."
|
|
3
|
+
description: "Use this skill for particle effects and motion trails in melonJS — ParticleEmitter configuration, burst versus stream emission, the referenceSpace frame of reference introduced in 20.2, 3D particle motion through elevation and minSpread/maxSpread in 20.8, and the Trail renderable. Covers the emitter that silently does nothing until you call an emit method. Triggers on: Trail3d, ribbon, exhaust trail, speed streak, ski track, tyre mark, ribbonNormal, ParticleEmitter, particles, burstParticles, streamParticles, referenceSpace, ParticleEmitterSettings, elevation, minSpread, maxSpread, spawn area, Trail, explosion, smoke, sparks, debris, ring, exhaust, emitter."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -28,6 +28,47 @@ emitter.burstParticles(); // one shot
|
|
|
28
28
|
`duration` setting, which defaults to `Infinity` — a stream runs until
|
|
29
29
|
`stopStream()`. `burstParticles(n)` launches `n`, or `totalParticles`.
|
|
30
30
|
|
|
31
|
+
## Sprite-sheet particles
|
|
32
|
+
|
|
33
|
+
Use `framewidth` and `frameheight` (the same names as on `Sprite`) to divide
|
|
34
|
+
`image` into a regular grid. Each omitted dimension defaults to that of the
|
|
35
|
+
image. Frames are numbered from zero left-to-right, then top-to-bottom; only
|
|
36
|
+
whole cells count, so partial cells along the right/bottom edges are ignored.
|
|
37
|
+
Margins and spacing are not supported.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const explosion = new ParticleEmitter(x, y, {
|
|
41
|
+
image: loader.getImage("explosion-sheet"),
|
|
42
|
+
framewidth: 32, frameheight: 32,
|
|
43
|
+
frames: [0, 1, 2, 3],
|
|
44
|
+
minLife: 400, maxLife: 800,
|
|
45
|
+
});
|
|
46
|
+
world.addChild(explosion);
|
|
47
|
+
explosion.burstParticles();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Omit `frames` to use every cell. Each particle traverses the sequence once
|
|
51
|
+
over its own lifetime, with equal time for each entry; repeated indices hold
|
|
52
|
+
a frame longer. Existing scale, opacity and rotation still apply. Bounds and
|
|
53
|
+
the rotation pivot use one frame's dimensions, rather than the whole sheet.
|
|
54
|
+
|
|
55
|
+
Set `randomFrame: true` for debris: a particle chooses one entry at birth and
|
|
56
|
+
keeps it until it dies. Different frames share the same image and GPU batch.
|
|
57
|
+
Without sheet settings, particles still draw the whole image as before, and
|
|
58
|
+
that includes `randomFrame` on its own: it needs a grid to choose from, so
|
|
59
|
+
pair it with `framewidth`/`frameheight` or it does nothing.
|
|
60
|
+
|
|
61
|
+
Note the variety is in SHAPE, not colour. `tint` belongs to the emitter, so
|
|
62
|
+
every particle from one emitter is tinted alike however many frames it picks
|
|
63
|
+
between.
|
|
64
|
+
|
|
65
|
+
Frame dimensions must be positive integers that fit inside the image, and an
|
|
66
|
+
explicit `frames` sequence must be nonempty and contain valid integer indices.
|
|
67
|
+
Invalid configurations throw `RangeError` before changing an emitter during
|
|
68
|
+
`reset()`. The frame sequence is copied on construction/reset; resetting changes
|
|
69
|
+
only future particles, so ones already alive finish with their original image,
|
|
70
|
+
frame size and sequence.
|
|
71
|
+
|
|
31
72
|
## Where particles are born
|
|
32
73
|
|
|
33
74
|
`width` and `height` are the spawn AREA, and both default to `1` — so by
|
|
@@ -184,19 +225,48 @@ Omit `target` and feed it yourself with `trail.addPoint(x, y)` — the mode for
|
|
|
184
225
|
sword slash, where the ribbon follows a weapon tip rather than an object. Points
|
|
185
226
|
closer together than `minDistance` (default 4 px) are dropped.
|
|
186
227
|
|
|
187
|
-
**`Trail` is 2D
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
228
|
+
**`Trail` is 2D. Use `Trail3d` in a 3D scene** (20.9.0 and later). `Trail`
|
|
229
|
+
stores points without a `z` and draws connected screen-space quads, which carry
|
|
230
|
+
one depth for the whole renderable, so under a `Camera3d` the ribbon stays flat
|
|
231
|
+
and does not narrow along its own length.
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
const exhaust = new Trail3d(0, 0, {
|
|
235
|
+
target: ship, // samples pos.x / pos.y / pos.z
|
|
236
|
+
width: 6, // world units, and free to change per frame
|
|
237
|
+
lifetime: 260,
|
|
238
|
+
gradient: ["#eaffff", "#2ea8ff", "#2ea8ff00"],
|
|
239
|
+
});
|
|
240
|
+
exhaust.blendMode = "additive";
|
|
241
|
+
app.world.addChild(exhaust); // no z: it derives its own from its points
|
|
242
|
+
```
|
|
191
243
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
244
|
+
`Trail3d` is a `Mesh`, which is most of the difference:
|
|
245
|
+
|
|
246
|
+
- `billboard` is spelled and valued exactly as on `Sprite3d` —
|
|
247
|
+
`"spherical"` (the default here) faces the camera on all axes,
|
|
248
|
+
`true` / `"cylindrical"` faces it but stays upright, `false` uses the
|
|
249
|
+
`ribbonNormal` you give it. `billboard: false, ribbonNormal: [0, -1, 0]`
|
|
250
|
+
lays the ribbon in the ground plane for a tyre mark or a wake.
|
|
251
|
+
- **`tint` works**, and is the per-frame colour lever. On `Trail` it does
|
|
252
|
+
nothing at all, because the primitive batcher reads the draw colour and never
|
|
253
|
+
the tint. `setColor()` / `setGradient()` rebuild the gradient for a change of
|
|
254
|
+
state; they allocate, so they are not per-frame.
|
|
255
|
+
|
|
256
|
+
**If the colour is going to change, author the gradient WHITE.** `tint`
|
|
257
|
+
multiplies, so tinting a cyan ribbon red gives muddy brown rather than red. A
|
|
258
|
+
neutral gradient hands the hue entirely to the tint, which is what lets one
|
|
259
|
+
lever take a trail from white through to red as something overheats.
|
|
260
|
+
- A post effect reaches it properly, where `Trail` has to force a screen
|
|
261
|
+
capture.
|
|
262
|
+
- It is **cheaper**: one indexed draw call, against one filled path per segment.
|
|
263
|
+
- `length` is fixed at construction, because the geometry is sized once.
|
|
264
|
+
Assigning `maxPoints` throws rather than silently truncating the ribbon, and
|
|
265
|
+
a `length` that is not a finite number throws at construction.
|
|
266
|
+
- It needs a `Camera3d`. Under a 2D camera it draws nothing and warns once.
|
|
267
|
+
|
|
268
|
+
A world-space `ParticleEmitter` (`referenceSpace: "world"`) is still the right
|
|
269
|
+
answer when you want discrete puffs rather than a continuous ribbon.
|
|
200
270
|
|
|
201
271
|
## Performance
|
|
202
272
|
|
|
@@ -219,7 +289,11 @@ tool for a trail behind an object moving *into* the scene. For that:
|
|
|
219
289
|
|
|
220
290
|
| symptom | cause |
|
|
221
291
|
|---|---|
|
|
222
|
-
| trail looks flat under a `Camera3d` | `Trail` is 2D
|
|
292
|
+
| trail looks flat under a `Camera3d` | `Trail` is 2D; use `Trail3d` |
|
|
293
|
+
| `Trail3d` draws nothing, with a console warning | it needs a `Camera3d`; the app's `cameraClass` is still the 2D default |
|
|
294
|
+
| `trail.tint` does nothing | `Trail` cannot be tinted at all; `Trail3d` can |
|
|
295
|
+
| assigning `maxPoints` throws | `Trail3d` sizes its geometry once; pass `length` to the constructor |
|
|
296
|
+
| `Trail3d: \`length\` must be a finite number` | a computed `length` came out `NaN`; it is refused rather than silently rendering nothing |
|
|
223
297
|
| emitter added but nothing appears | `streamParticles()` / `burstParticles()` never called |
|
|
224
298
|
| the cloud follows a moving emitter | default `referenceSpace: "local"` — use `"world"` for trails |
|
|
225
299
|
| particles do not travel far enough | settings tuned pre-20.2, when drawn travel was doubled |
|
|
@@ -59,16 +59,28 @@ Anything spawned frequently — bullets, particles, pickups, damage numbers —
|
|
|
59
59
|
should be pooled rather than allocated:
|
|
60
60
|
|
|
61
61
|
```js
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
import { createPool } from "melonjs";
|
|
63
|
+
|
|
64
|
+
const bulletPool = createPool((x, y) => {
|
|
65
|
+
const instance = new Bullet(x, y);
|
|
66
|
+
return { instance, reset: (x, y) => instance.onResetEvent(x, y) };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const b = bulletPool.get(x, y); // recycled: reset(x, y)
|
|
70
|
+
// fresh: new Bullet(x, y)
|
|
71
|
+
world.removeChild(b); // returns to bulletPool automatically
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
The container returns a child to whichever pool built it, so removal is enough;
|
|
75
|
+
you only call `release()` yourself for something that was never added to the
|
|
76
|
+
world.
|
|
77
|
+
|
|
78
|
+
**`me.pool` is the older, string-keyed version and is deprecated since
|
|
79
|
+
18.0.0.** `pool.register(name, Class, true)` / `pool.pull(name)` still work and
|
|
80
|
+
print a one-off console notice, but a recycled object is no longer returned on
|
|
81
|
+
removal, so pooling through it now costs an allocation per spawn. To register a
|
|
82
|
+
class so a Tiled map can name it, use `registerTiledObjectClass` — that is a
|
|
83
|
+
separate registry and is not deprecated.
|
|
72
84
|
|
|
73
85
|
Two rules that cause subtle bugs when missed:
|
|
74
86
|
|
|
@@ -76,10 +88,11 @@ Two rules that cause subtle bugs when missed:
|
|
|
76
88
|
alpha, tint, scale, animation, velocity. Anything you forget carries into the
|
|
77
89
|
next use, which looks like a random visual glitch.
|
|
78
90
|
- **Pooled objects do not fire `onDestroyEvent` on removal.** Removal always
|
|
79
|
-
calls `onDeactivateEvent`, then tries
|
|
80
|
-
it fall through to `destroy()`,
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
calls `onDeactivateEvent`, then tries to return the object to the pool that
|
|
92
|
+
built it; only when nothing owns it does it fall through to `destroy()`,
|
|
93
|
+
which is what calls `onDestroyEvent`. So a successfully recycled object never
|
|
94
|
+
sees it. Pair event subscriptions with `onActivateEvent` /
|
|
95
|
+
`onDeactivateEvent` instead, or you leak handlers.
|
|
83
96
|
|
|
84
97
|
- **`removeChild` is DEFERRED, which breaks a pool you re-lend in the same
|
|
85
98
|
frame.** The removal is queued and runs after the update and draw stack has
|
|
@@ -98,14 +111,32 @@ Two rules that cause subtle bugs when missed:
|
|
|
98
111
|
usual cause of a pooled effect that flickers out one frame after it is
|
|
99
112
|
recycled.
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
**The engine's own classes are NOT in this pool.** They were registered in it
|
|
115
|
+
once, which bought nothing: most had recycling off, so `pool.pull("Sprite")`
|
|
116
|
+
was a plain construction behind a string key. The ones worth recycling have
|
|
117
|
+
typed pools instead, reached by key and fully typed:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const tween = getPool("tween").get(target);
|
|
121
|
+
const label = getPool("text").get(x, y, { font: "Arial", size: 12 });
|
|
122
|
+
getPool("text").release(label); // yours to release, as with particles
|
|
123
|
+
```
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
A recycled label comes back with its **base-class** state reset as well as its
|
|
126
|
+
own: `floating`, `blendMode`, `alpha`, `tint`, the transform, the anchor, the
|
|
127
|
+
behaviour flags, any mask and any post effect, and `depth` back to 0. The
|
|
128
|
+
settings object names none of those, so set them again after `get()`. A pooled
|
|
129
|
+
HUD number that is not re-marked `floating` is drawn through the camera, i.e.
|
|
130
|
+
somewhere out in the world rather than on the screen, and `addChild(label, z)`
|
|
131
|
+
is what gives it its depth back.
|
|
132
|
+
|
|
133
|
+
`getPool` covers `vector2d`, `vector3d`, `point`, `matrix2d`, `matrix3d`,
|
|
134
|
+
`bounds`, `color`, `polygon`, `line`, `rectangle`, `roundedRectangle`,
|
|
135
|
+
`ellipse`, `tween`, `particle`, `text`, `colorLayer` and `bitmapTextData`.
|
|
136
|
+
|
|
137
|
+
`pool.register` still aliases every name you give it under an `me.` prefix,
|
|
138
|
+
pointing at the same entry, so `pool.pull("me.Bullet")` resolves identically —
|
|
139
|
+
and the same alias reaches the Tiled object factory, which is why a map
|
|
109
140
|
authored against melonJS 1.x still finds its classes. Prefer the unprefixed
|
|
110
141
|
name in new code; do not "correct" an `me.`-prefixed one, it is not broken.
|
|
111
142
|
|
|
@@ -214,9 +245,12 @@ Since 20.6 the engine's own programs are built during `preload()` instead —
|
|
|
214
245
|
`loader.preload()` calls `renderer.prewarm()` itself, on every call, when the
|
|
215
246
|
application's `prewarm` setting is on, which is the default. So on a current
|
|
216
247
|
version this is one cause you can rule out before looking further, **unless the
|
|
217
|
-
game does not use the preloader at all**, which is the one way to miss it — see `melonjs-loading-assets`.
|
|
218
|
-
|
|
219
|
-
|
|
248
|
+
game does not use the preloader at all**, which is the one way to miss it — see `melonjs-loading-assets`. Since 20.10 the
|
|
249
|
+
pipelines that depend on the renderable (effects, blend modes, render targets)
|
|
250
|
+
are warmed when an effect is attached or a renderable is added to the world, as
|
|
251
|
+
long as that happens at least a frame before it is drawn: a pool built at stage
|
|
252
|
+
start is covered, an object created and drawn in the same frame is not. None of
|
|
253
|
+
this covers the rest of what a first frame pays for, below.
|
|
220
254
|
|
|
221
255
|
How to tell them apart, all from the page:
|
|
222
256
|
|
|
@@ -333,6 +367,7 @@ In rough order for a typical 2D game:
|
|
|
333
367
|
| memory grows with long music | missing `stream: true` |
|
|
334
368
|
| slow only on some machines | Canvas fallback — no GPU tilemap path |
|
|
335
369
|
| a freeze the first time a level shows, never again | shader variants linking on first draw — warm them behind the loading screen |
|
|
370
|
+
| a freeze the first time an effect appears mid-play (a shield, a hit flash) | the effect attached in the same frame it is drawn — attach it when the pool is built, so the warm-up has a frame to finish |
|
|
336
371
|
| the same scene stutters once per entry | a derived texture rebuilt per stage: the texture cache keys on the image object, so each rebuild is a new upload |
|
|
337
372
|
|
|
338
373
|
## Related skills
|
|
@@ -500,7 +500,8 @@ onCollisionStart(response, other) {
|
|
|
500
500
|
What actually happens per adapter:
|
|
501
501
|
|
|
502
502
|
- **built-in** — callbacks fire inline during `step()`. `Container.removeChild`
|
|
503
|
-
is deferred, so it is safe; `removeChildNow()
|
|
503
|
+
is deferred, so it is safe; `removeChildNow()`, and the internal `destroy()`
|
|
504
|
+
it calls, are immediate,
|
|
504
505
|
and the detector only survives them because it re-checks `body === undefined`
|
|
505
506
|
after every user callback.
|
|
506
507
|
- **planck** — `onCollisionStart` / `onCollisionEnd` are dispatched from
|
|
@@ -184,6 +184,11 @@ v.set(10, 20, v.z); // keep it
|
|
|
184
184
|
`pos` is an `ObservableVector3d`, whose `set(x = 0, y = 0, z = 0)` does the same
|
|
185
185
|
— so `this.pos.set(x, y)` wipes the object's depth.
|
|
186
186
|
|
|
187
|
+
`Renderable#setPosition(x, y, z?)` is the renderable-level form and does not:
|
|
188
|
+
omit `z` and the depth is left exactly as it was. Reach for it whenever you
|
|
189
|
+
are moving an object rather than editing a vector, and keep `v.set(x, y, v.z)`
|
|
190
|
+
for the vector itself.
|
|
191
|
+
|
|
187
192
|
## 7. A settings key the class never reads is silently ignored
|
|
188
193
|
|
|
189
194
|
A settings object is a plain literal: nothing rejects a key, so a name that
|
|
@@ -279,7 +284,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
|
|
|
279
284
|
| off-screen object never ticks | `update()` is gated on `inViewport` — set `alwaysUpdate = true` |
|
|
280
285
|
| custom draw offset by half the size | centred `anchorPoint` default not zeroed |
|
|
281
286
|
| spatial query never finds an object | `isKinematic` left `true` — not in the broadphase |
|
|
282
|
-
| `z` unexpectedly 0 after a `set` | `Vector3d.set(x, y)` defaults `z` to 0 |
|
|
287
|
+
| `z` unexpectedly 0 after a `set` | `Vector3d.set(x, y)` defaults `z` to 0 — `setPosition(x, y)` on the renderable leaves the depth alone |
|
|
283
288
|
| a settings option appears to do nothing | the class never reads that key — see section 7 |
|
|
284
289
|
| `isRenderable = false` did not hide the object | it gates bounds, not drawing — use `alpha` |
|
|
285
290
|
|
|
@@ -194,6 +194,17 @@ timer.clearInterval(id); // in the Stage's onDestroyEvent
|
|
|
194
194
|
`onActivateEvent` / `onDeactivateEvent` on a stage. Those two are `Renderable`
|
|
195
195
|
hooks; put per-renderable teardown there instead.
|
|
196
196
|
|
|
197
|
+
**The outgoing stage is destroyed before the incoming one is reset.** That
|
|
198
|
+
ordering is what makes "each stage owns what it started" work: a scene can
|
|
199
|
+
release a resource in `onDestroyEvent` and the next scene can claim the same
|
|
200
|
+
one in `onResetEvent` without the teardown arriving late and undoing it.
|
|
201
|
+
|
|
202
|
+
What a stage starts, it must end: `input.registerPointerEvent` regions,
|
|
203
|
+
`event.on` subscriptions and running `Tween`s all outlive the stage that made
|
|
204
|
+
them. Music is the exception rather than the rule, since `audio.playTrack()`
|
|
205
|
+
stops the track it replaces, so `stopTrack()` in `onDestroyEvent` is only
|
|
206
|
+
needed where the next screen has no music of its own.
|
|
207
|
+
|
|
197
208
|
Engine timers are **pause-aware**. `window.setTimeout` keeps firing while the
|
|
198
209
|
game is paused, which produces enemies spawning behind a pause menu. Pass
|
|
199
210
|
`false` as the third argument — `timer.setInterval(fn, 500, false)` — for one
|
|
@@ -257,9 +268,9 @@ instances.** All four, or it is not a tween.
|
|
|
257
268
|
|
|
258
269
|
|
|
259
270
|
```js
|
|
260
|
-
import {
|
|
271
|
+
import { getPool, Tween } from "melonjs";
|
|
261
272
|
|
|
262
|
-
const t =
|
|
273
|
+
const t = getPool("tween").get(sprite.pos) // typed pool, release when done
|
|
263
274
|
.to({ x: 300 }, { duration: 500 }) // options object, not a number
|
|
264
275
|
.easing(Tween.Easing.Quadratic.Out)
|
|
265
276
|
.onComplete(() => { /* … */ })
|
|
@@ -289,7 +300,7 @@ callback is invoked with the tweened object as `this` and the eased progress
|
|
|
289
300
|
|
|
290
301
|
```js
|
|
291
302
|
const fade = { level: 1 };
|
|
292
|
-
|
|
303
|
+
getPool("tween").get(fade)
|
|
293
304
|
.to({ level: 0 }, { duration: 400 })
|
|
294
305
|
.easing(Tween.Easing.Quadratic.Out)
|
|
295
306
|
.onUpdate(function () {
|
|
@@ -103,7 +103,7 @@ Packing into one atlas keeps everything on a single binding.
|
|
|
103
103
|
## Visual properties
|
|
104
104
|
|
|
105
105
|
```js
|
|
106
|
-
sprite.
|
|
106
|
+
sprite.setTint("#ff8000"); // multiply tint
|
|
107
107
|
sprite.alpha = 0.5;
|
|
108
108
|
sprite.flipX(true); // mirror horizontally
|
|
109
109
|
sprite.flipY(true);
|
|
@@ -141,9 +141,14 @@ The insets default to a quarter of the frame.
|
|
|
141
141
|
Frequently spawned sprites should be pooled:
|
|
142
142
|
|
|
143
143
|
```js
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
import { createPool } from "melonjs";
|
|
145
|
+
|
|
146
|
+
const bulletPool = createPool((x, y) => { // me.pool is deprecated
|
|
147
|
+
const instance = new Bullet(x, y);
|
|
148
|
+
return { instance, reset: (x, y) => instance.onResetEvent(x, y) };
|
|
149
|
+
});
|
|
150
|
+
const b = bulletPool.get(x, y); // new Bullet(x, y), or reset(x, y) on a reused one
|
|
151
|
+
world.removeChild(b); // returns it to bulletPool automatically
|
|
147
152
|
```
|
|
148
153
|
|
|
149
154
|
Two consequences:
|