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,273 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-display-and-scaling
|
|
3
|
+
description: "Use this skill when a melonJS game looks blurry, soft, shimmering or cropped, or when choosing how the canvas fills its page. Covers the design resolution, scale and scaleMethod, scaleTarget, scaleRounding for pixel-perfect integer scaling, antiAlias, textureFilter, subPixel, devicePixelRatio, and which setting does which job. Triggers on: blurry, soft, fuzzy, shimmering, pixel art, pixel perfect, crisp, nearest, linear, scale, scaleMethod, scaleTarget, scaleRounding, integer scaling, antiAlias, textureFilter, subPixel, devicePixelRatio, retina, hidpi, design resolution, canvas size, canvas cropped, letterbox, image-rendering, upscale, resolution."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Display and scaling
|
|
8
|
+
|
|
9
|
+
Seven settings decide how a game looks on screen, and they are easy to get
|
|
10
|
+
individually right and collectively wrong. This is what each one actually
|
|
11
|
+
does, and which one to reach for.
|
|
12
|
+
|
|
13
|
+
## The four jobs
|
|
14
|
+
|
|
15
|
+
A pixel travels from your source art to a physical pixel through four
|
|
16
|
+
separate decisions. Knowing which setting owns which is most of the work.
|
|
17
|
+
|
|
18
|
+
| job | setting | what it decides |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| how big the frame is drawn | `width`/`height` on `Application` | the design resolution everything is authored against |
|
|
21
|
+
| how it uses the space | `scale`, `scaleMethod`, `scaleTarget` | fit, fill, stretch, and what it measures to decide |
|
|
22
|
+
| may the factor be fractional | `scaleRounding` | whole device pixels per source pixel, or any factor |
|
|
23
|
+
| how a texture is sampled | `antiAlias`, `textureFilter` | nearest or linear, and the CSS upscale filter |
|
|
24
|
+
|
|
25
|
+
`subPixel` is a fifth, orthogonal to all of them: it decides whether a
|
|
26
|
+
renderable may sit on a fractional position WITHIN the frame.
|
|
27
|
+
|
|
28
|
+
## Design resolution
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const app = new Application(480, 270, { scale: "auto" });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The game is drawn at 480x270 whatever the window is, and the canvas is scaled
|
|
35
|
+
to the container. Everything in world coordinates is in those units. This is
|
|
36
|
+
the usual arrangement: author once, scale to whatever you are given.
|
|
37
|
+
|
|
38
|
+
## `scale` and `scaleMethod`
|
|
39
|
+
|
|
40
|
+
`scale: "auto"` turns on automatic scaling; a number is a fixed multiplier and
|
|
41
|
+
`scaleMethod` is then ignored.
|
|
42
|
+
|
|
43
|
+
`scaleMethod` decides how the available space is used:
|
|
44
|
+
|
|
45
|
+
- **`"fit"`** keeps the design resolution and the aspect ratio, scaling to the
|
|
46
|
+
largest factor that fits. Leftover space becomes margin. This is the one to
|
|
47
|
+
use when the game is authored for a fixed frame.
|
|
48
|
+
- **`"fill-min"` / `"fill-max"`** keep the aspect ratio but RESIZE the
|
|
49
|
+
renderer, so the design resolution changes with the window and the game sees
|
|
50
|
+
more or less of the world.
|
|
51
|
+
- **`"flex"`, `"flex-width"`, `"flex-height"`** resize the renderer on one or
|
|
52
|
+
both axes.
|
|
53
|
+
- **`"stretch"`** scales each axis independently, so the aspect ratio is not
|
|
54
|
+
preserved and circles become ovals.
|
|
55
|
+
- **`"manual"`** does nothing automatic.
|
|
56
|
+
|
|
57
|
+
The distinction that matters: **`fit` scales a fixed frame; everything else
|
|
58
|
+
changes how much of the world is visible.** A game whose layout assumes a
|
|
59
|
+
known frame wants `fit`.
|
|
60
|
+
|
|
61
|
+
## `scaleTarget`: what gets measured
|
|
62
|
+
|
|
63
|
+
Automatic scaling measures **the parent of the element the canvas is mounted
|
|
64
|
+
in**, not the element itself. If that grandparent is not the box you meant,
|
|
65
|
+
the canvas is sized wrongly, and the failure is quiet.
|
|
66
|
+
|
|
67
|
+
The case that bites: a canvas container that is `position: fixed` or
|
|
68
|
+
`absolute` contributes no height to its parent, so the parent measures only
|
|
69
|
+
whatever else is in it. If that parent is `<body>`, the engine falls back to
|
|
70
|
+
the window size and the canvas ends up larger than the box it lives in,
|
|
71
|
+
cropped by whatever sits above it.
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
new Application(1024, 576, {
|
|
75
|
+
parent: "screen",
|
|
76
|
+
scale: "auto",
|
|
77
|
+
// measure THIS, rather than guessing from the DOM shape
|
|
78
|
+
scaleTarget: document.getElementById("stage"),
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Either name the element with `scaleTarget`, or give the canvas container a
|
|
83
|
+
parent whose box is the area you want filled. If the canvas is cropped or
|
|
84
|
+
overflows on resize, this is the first thing to check.
|
|
85
|
+
|
|
86
|
+
## `scaleRounding`: whole device pixels
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
new Application(480, 270, {
|
|
90
|
+
scale: "auto",
|
|
91
|
+
scaleMethod: "fit",
|
|
92
|
+
scaleRounding: "integer", // default "fractional"
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`"fractional"` fills the container exactly, at whatever factor that takes. A
|
|
97
|
+
480 wide design in a 1280 wide container is scaled by 2.667, so some source
|
|
98
|
+
pixels cover three screen pixels and some cover two. Pixel art shows this as
|
|
99
|
+
uneven glyph stems and shimmering edges, and browsers resolve the unevenness
|
|
100
|
+
differently, so the same scene can look crisp in one and soft in another.
|
|
101
|
+
|
|
102
|
+
`"integer"` rounds the factor DOWN so every source pixel covers the same whole
|
|
103
|
+
number of device pixels, and letterboxes with the space left over. That trade,
|
|
104
|
+
crisp pixels for black bars, is the whole decision.
|
|
105
|
+
|
|
106
|
+
### What it costs
|
|
107
|
+
|
|
108
|
+
More than "some letterboxing", and the size of it is decided by
|
|
109
|
+
`devicePixelRatio` as much as by the window. A higher ratio has more device
|
|
110
|
+
pixels to round among, so it lands closer to the factor it wanted. For a
|
|
111
|
+
480x270 design, which is exactly a quarter of 1920x1080:
|
|
112
|
+
|
|
113
|
+
| container | ratio | fit | integer | presented | area lost |
|
|
114
|
+
|---|---|---|---|---|---|
|
|
115
|
+
| 1920x1080 | 1 or 2 | 4.000 | 4 / 8 | 1920x1080 | none |
|
|
116
|
+
| 1440x810 | 1 | 3.000 | 3 | 1440x810 | none |
|
|
117
|
+
| 960x540 | 1 | 2.000 | 2 | 960x540 | none |
|
|
118
|
+
| 1280x720 | 2 | 2.667 | 5 | 1200x675 | 12% |
|
|
119
|
+
| 2560x1440 | 2 | 5.333 | 10 | 2400x1350 | 12% |
|
|
120
|
+
| 1280x720 | 1 | 2.667 | 2 | 960x540 | **44%** |
|
|
121
|
+
| 1366x768 | 1 | 2.844 | 2 | 960x540 | **51%** |
|
|
122
|
+
|
|
123
|
+
So the decision is mostly made when the design resolution is chosen, not when
|
|
124
|
+
this setting is. One that divides the sizes you expect costs nothing; one that
|
|
125
|
+
does not can give up half the screen on a 1x display, and 1366x768 laptops are
|
|
126
|
+
not rare. A game that must fill the window on any machine should stay
|
|
127
|
+
fractional and accept the uneven pixels.
|
|
128
|
+
|
|
129
|
+
Two things to know:
|
|
130
|
+
|
|
131
|
+
- It applies to **`scaleMethod: "fit"` only.** Every other method resizes the
|
|
132
|
+
renderer to the container, so the design resolution moves and there is no
|
|
133
|
+
fixed factor to round.
|
|
134
|
+
- The rounding happens in **device** pixels. `devicePixelRatio` multiplies the
|
|
135
|
+
CSS size on the way to real pixels, so rounding the CSS factor would let the
|
|
136
|
+
fraction straight back in: a factor of 2 at a ratio of 1.25 is 2.5 device
|
|
137
|
+
pixels per source pixel.
|
|
138
|
+
|
|
139
|
+
## `antiAlias` does three things
|
|
140
|
+
|
|
141
|
+
This is the setting most likely to surprise, because it is not one switch:
|
|
142
|
+
|
|
143
|
+
1. the texture filter, but only while `textureFilter` is `"auto"` (the
|
|
144
|
+
default), which TRACKS it: linear when on, nearest when off. Setting
|
|
145
|
+
`textureFilter` explicitly decouples the two
|
|
146
|
+
2. `imageSmoothingEnabled` on the 2D context
|
|
147
|
+
3. the canvas CSS `image-rendering`: `pixelated` when off, `auto` when on
|
|
148
|
+
|
|
149
|
+
So `antiAlias: false` gives hard-edged textures AND a hard-edged upscale of
|
|
150
|
+
the whole frame. That is usually what a pixel-art game wants, and usually NOT
|
|
151
|
+
what a smooth 3D scene wants, because it also disables MSAA.
|
|
152
|
+
|
|
153
|
+
`textureFilter: "nearest" | "linear"` separates the texture half, so a scene
|
|
154
|
+
can have smooth model edges and nearest-sampled sprites. The presentation half
|
|
155
|
+
is not separable: there is no way to get MSAA silhouettes and a hard-edged
|
|
156
|
+
upscale at the same time.
|
|
157
|
+
|
|
158
|
+
## `subPixel` is about position, not scale
|
|
159
|
+
|
|
160
|
+
`subPixel: false` (the default) truncates a renderable's translation to whole
|
|
161
|
+
pixels inside the frame. It is `|0`, so it truncates **toward zero** rather
|
|
162
|
+
than rounding, which is worth knowing when something travels a short distance:
|
|
163
|
+
an offset that should swing between -3 and +3 lands on -2 and +2, and parks at
|
|
164
|
+
the extremes for longer than it should.
|
|
165
|
+
|
|
166
|
+
It does nothing about how the frame reaches the screen. A game that carefully
|
|
167
|
+
snaps positions and is then scaled by 2.667 has thrown that work away at the
|
|
168
|
+
last step, which is the contradiction `scaleRounding: "integer"` exists to
|
|
169
|
+
resolve.
|
|
170
|
+
|
|
171
|
+
## Soft text next to crisp sprites
|
|
172
|
+
|
|
173
|
+
If everything is crisp EXCEPT the text, the scale factor is not the cause: a
|
|
174
|
+
fractional factor softens the whole frame equally. The text is different
|
|
175
|
+
because of how it is made.
|
|
176
|
+
|
|
177
|
+
`Text` draws through the browser's `fillText`, which anti-aliases glyph edges
|
|
178
|
+
into partial-alpha pixels. Measured on one label at 16px: **171 distinct
|
|
179
|
+
partial alpha levels**. Nothing in the canvas API turns that off. `antiAlias`
|
|
180
|
+
and the `imageSmoothingEnabled` that `CanvasRenderTarget` sets govern IMAGE
|
|
181
|
+
drawing, not glyph rasterisation.
|
|
182
|
+
|
|
183
|
+
So the label arrives already soft, and the upscale then magnifies each grey
|
|
184
|
+
edge pixel into a grey block, sitting beside sprites that were hard-edged to
|
|
185
|
+
begin with and stay hard-edged under nearest sampling.
|
|
186
|
+
|
|
187
|
+
It also explains the same scene looking crisp in one browser and soft in
|
|
188
|
+
another at identical size and settings: engines use different font
|
|
189
|
+
rasterisers, with different hinting and anti-aliasing, so they bake DIFFERENT
|
|
190
|
+
textures from the same font at the same size.
|
|
191
|
+
|
|
192
|
+
**Use `BitmapText` for a game that upscales.** A pixel-font atlas is
|
|
193
|
+
hard-edged source art like every other texture, so it survives magnification
|
|
194
|
+
the way the sprites do. Raising the design resolution only shrinks the
|
|
195
|
+
magnification of anti-aliasing that is still there, so it softens the symptom
|
|
196
|
+
rather than removing it, and `scaleRounding` does nothing for this at all.
|
|
197
|
+
|
|
198
|
+
Keep `Text` where the frame is not upscaled much, or where the label is meant
|
|
199
|
+
to look smooth.
|
|
200
|
+
|
|
201
|
+
### Baking an atlas from a face you already use
|
|
202
|
+
|
|
203
|
+
Swapping in a bitmap face means generating one, and three things decide
|
|
204
|
+
whether it comes out looking like the original:
|
|
205
|
+
|
|
206
|
+
- **Threshold the coverage.** Hard edges are the whole point, so every pixel
|
|
207
|
+
has to end up fully on or fully off. Hardening only the ALPHA is not enough:
|
|
208
|
+
an outlined glyph keeps a soft stroke-to-fill blend INSIDE the silhouette,
|
|
209
|
+
which still reads as blur once magnified. Pick the colours from a mask.
|
|
210
|
+
- **Bake each size at that size, and draw it at a ratio of 1.** `size` on
|
|
211
|
+
`BitmapText` is a ratio of the authored size, and a bitmap face has the sizes
|
|
212
|
+
it was baked at. Doubling a smaller atlas halves the detail; a ratio below 1
|
|
213
|
+
drops whole stems; a fractional ratio resamples hard pixels unevenly. One
|
|
214
|
+
atlas per step of the type scale.
|
|
215
|
+
- **Grow the outline outward, but carve it INWARD around a counter.** This is
|
|
216
|
+
the one that costs the most time. A stroke drawn by the canvas is centred on
|
|
217
|
+
the contour, so around an enclosed hole it eats into the letter and the hole
|
|
218
|
+
keeps its size. Dilating a mask outward instead grows black INTO the hole
|
|
219
|
+
from every side, which closes anything under about 3px: the letter loses its
|
|
220
|
+
counter and reads as a blob. Take the rim from the fill side and the hole
|
|
221
|
+
survives at every size. Neither leaving holes unrimmed nor closing the small
|
|
222
|
+
ones is a substitute — both were tried and both look wrong.
|
|
223
|
+
|
|
224
|
+
Two traps in the generator itself:
|
|
225
|
+
|
|
226
|
+
- A face whose glyphs overshoot the `textBaseline` origin is clipped by a tight
|
|
227
|
+
cell, and the symptom is an outline that goes missing along one edge of some
|
|
228
|
+
glyphs. Pad the cell by a fraction of the size, and assert that no glyph's
|
|
229
|
+
ink reaches its border.
|
|
230
|
+
- The threshold decides the TOPOLOGY, not just the weight. A notch or a narrow
|
|
231
|
+
gap in a letterform closes over at a full threshold and becomes an isolated
|
|
232
|
+
hole, which then bakes as a black blob sitting on the glyph. Thinner, the
|
|
233
|
+
same feature stays joined to the background and reads as the face intends.
|
|
234
|
+
So a small size may want a thinner fill than a large one, and the way to see
|
|
235
|
+
it is to magnify one glyph, not a whole line.
|
|
236
|
+
|
|
237
|
+
## A pixel-art setup
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const app = new Application(480, 270, {
|
|
241
|
+
scale: "auto",
|
|
242
|
+
scaleMethod: "fit",
|
|
243
|
+
scaleRounding: "integer", // whole device pixels, letterbox the rest
|
|
244
|
+
antiAlias: false, // nearest textures, hard-edged upscale
|
|
245
|
+
// subPixel: false is already the default
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## A smooth 3D setup
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
const app = new Application(1024, 576, {
|
|
253
|
+
scale: "auto",
|
|
254
|
+
scaleMethod: "fit",
|
|
255
|
+
antiAlias: true, // MSAA and a filtered upscale
|
|
256
|
+
textureFilter: "nearest", // but keep blocky textures if the art is blocky
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Symptoms
|
|
261
|
+
|
|
262
|
+
| symptom | cause |
|
|
263
|
+
|---|---|
|
|
264
|
+
| pixel art is blurry or shimmers | the frame is upscaled by a fractional factor; set `scaleRounding: "integer"` |
|
|
265
|
+
| crisp in one browser, soft in another | the same fractional factor, which engines distribute differently under `pixelated` |
|
|
266
|
+
| the canvas is cropped or overflows its box | automatic scaling measured the wrong element; name it with `scaleTarget` |
|
|
267
|
+
| the canvas grows on every resize | the measured ancestor is auto-sized around the canvas itself |
|
|
268
|
+
| things shimmer as they move | fractional positions; check `subPixel`, and that the scale factor is whole |
|
|
269
|
+
| 3D model edges are jagged | `antiAlias: false` also disables MSAA; turn it on and use `textureFilter` for textures |
|
|
270
|
+
| textures are smoothed when they should be blocky | `antiAlias: true` makes the default texture filter linear; set `textureFilter: "nearest"` |
|
|
271
|
+
| `scaleRounding: "integer"` changes nothing | it applies to `scaleMethod: "fit"` only; the others resize the renderer |
|
|
272
|
+
| text is soft but sprites are crisp | the glyphs were anti-aliased when `Text` baked them; use `BitmapText` (see below) |
|
|
273
|
+
| the game shows more world on a bigger window | that is `fill-*` or `flex-*` doing their job; use `"fit"` to keep a fixed frame |
|
|
@@ -110,14 +110,15 @@ reused across renderables.
|
|
|
110
110
|
|
|
111
111
|
## Built-in effects
|
|
112
112
|
|
|
113
|
-
Twenty-
|
|
113
|
+
Twenty-two presets, every one constructed as `new XEffect(renderer, options)` and
|
|
114
114
|
every one shipping both a GLSL and a WGSL body, so they all run on either GPU
|
|
115
115
|
backend: `VignetteEffect`, `ScanlineEffect` (optional CRT curvature),
|
|
116
116
|
`BloomEffect`, `GlowEffect`, `BlurEffect`, `PixelateEffect`,
|
|
117
117
|
`ChromaticAberrationEffect`, `DropShadowEffect`, `FlashEffect`,
|
|
118
118
|
`OutlineEffect`, `ColorMatrixEffect`, `DesaturateEffect`, `DissolveEffect`,
|
|
119
119
|
`TintPulseEffect`, `WaveEffect`, `InvertEffect`, `SepiaEffect`,
|
|
120
|
-
`HologramEffect`, `ShineEffect`, `RadialGradientEffect`, `
|
|
120
|
+
`HologramEffect`, `ShineEffect`, `RadialGradientEffect`, `SquishEffect`,
|
|
121
|
+
`ToneMappingEffect`.
|
|
121
122
|
|
|
122
123
|
`RadialGradientEffect` is a procedural soft spot: solid at the centre of its
|
|
123
124
|
host quad, fading linearly to transparent at the edge, and elliptical for free
|
|
@@ -127,6 +128,40 @@ falloff assumes the quad samples a FULL texture rect, so pair it with a
|
|
|
127
128
|
standalone image or `Renderer.getWhitePixel()` rather than a sprite that draws
|
|
128
129
|
a sub-region of an atlas, where the centre would land off-quad.
|
|
129
130
|
|
|
131
|
+
`SquishEffect` bulges a sprite's middle out (`setBulge(0.3)`) or pinches it in
|
|
132
|
+
(`setBulge(-0.3)`), its top and bottom rows staying put. Bulging out widens the
|
|
133
|
+
sprite past its own frame, and the effect draws on a wider quad to make room,
|
|
134
|
+
so the art needs no transparent margin. On its own it is a jelly wobble; paired
|
|
135
|
+
with a change of height it is **squash and stretch that keeps the volume**:
|
|
136
|
+
scale the sprite to `scaleY` of its height about its feet and set the bulge to
|
|
137
|
+
`1 - scaleY`, so a stretched sprite pinches in and a squashed one bulges out.
|
|
138
|
+
Ease `scaleY` back to 1 with a short `Tween.Easing.Back.Out`; an elastic ease
|
|
139
|
+
keeps it trembling long after the landing.
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
const squish = new SquishEffect(app.renderer);
|
|
143
|
+
player.addPostEffect(squish);
|
|
144
|
+
// every frame, from the player's update()
|
|
145
|
+
const feet = player.height * (1 - player.anchorPoint.y);
|
|
146
|
+
player.currentTransform
|
|
147
|
+
.identity()
|
|
148
|
+
.translate(0, feet)
|
|
149
|
+
.scale(1, scaleY)
|
|
150
|
+
.translate(0, -feet);
|
|
151
|
+
squish.setBulge(1 - scaleY);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Apply that from the sprite's own `update()`, not from a tween's `onUpdate`. A
|
|
155
|
+
physics adapter that mirrors a body's angle (Matter does) rewrites
|
|
156
|
+
`currentTransform` on every step, before the renderables update, so a scale set
|
|
157
|
+
anywhere else is gone by the time the sprite is drawn. Let the tween animate a
|
|
158
|
+
plain `{ scaleY }` object and read it in `update()`.
|
|
159
|
+
|
|
160
|
+
It is made for a sprite carrying it as its only effect: chained with another,
|
|
161
|
+
or on the camera, the frame it measures against is the whole capture, so it
|
|
162
|
+
squishes the whole frame. The bulge is outside the sprite's bounds, so culling
|
|
163
|
+
and pointer events ignore it.
|
|
164
|
+
|
|
130
165
|
**`BloomEffect` and `GlowEffect` are not the same thing**, and reaching for the
|
|
131
166
|
wrong one is the usual disappointment here. `GlowEffect` draws a halo OUTSIDE a
|
|
132
167
|
sprite's silhouette: its body returns the colour untouched wherever alpha is
|
|
@@ -136,9 +171,9 @@ bright pixels bleed into their neighbours — and it is the one to put on the
|
|
|
136
171
|
camera:
|
|
137
172
|
|
|
138
173
|
```js
|
|
139
|
-
|
|
174
|
+
app.viewport.addPostEffect(new BloomEffect(app.renderer));
|
|
140
175
|
// only the brightest highlights, with a wider halo
|
|
141
|
-
|
|
176
|
+
app.viewport.addPostEffect(
|
|
142
177
|
new BloomEffect(app.renderer, { threshold: 0.85, radius: 4 }),
|
|
143
178
|
);
|
|
144
179
|
```
|
|
@@ -154,8 +189,8 @@ reads as a white sticker. `exposure` lifts the image into the curve, and the
|
|
|
154
189
|
curve (`aces`, `reinhard` or `exponential`) is baked at construction:
|
|
155
190
|
|
|
156
191
|
```js
|
|
157
|
-
|
|
158
|
-
|
|
192
|
+
app.viewport.addPostEffect(new BloomEffect(app.renderer));
|
|
193
|
+
app.viewport.addPostEffect(
|
|
159
194
|
new ToneMappingEffect(app.renderer, { exposure: 1.6 }),
|
|
160
195
|
);
|
|
161
196
|
```
|
|
@@ -453,8 +488,15 @@ With `prewarm` on (the default since 20.6), `loader.preload()` warms on its way
|
|
|
453
488
|
through — automatically, on every call — and compiles the GPU-side objects for
|
|
454
489
|
every shader it holds — on WebGPU that is the
|
|
455
490
|
WGSL module, which is otherwise built inside the draw path the first time the
|
|
456
|
-
effect is used.
|
|
457
|
-
|
|
491
|
+
effect is used.
|
|
492
|
+
|
|
493
|
+
Since 20.10 the rest is warmed when the effect is **attached**, inline effects
|
|
494
|
+
included: `addPostEffect` (or assigning `shader`) compiles the pipelines that
|
|
495
|
+
effect will draw with on that renderable — on WebGPU the costly part, keyed by
|
|
496
|
+
the renderable's blend mode and target as well as the shader — and on WebGL
|
|
497
|
+
links the spliced program when the host is a mesh. It finishes off the frame,
|
|
498
|
+
so attach when you build (a pool, a stage starting), not when you show: an
|
|
499
|
+
effect attached and drawn in the same frame still compiles on that frame.
|
|
458
500
|
|
|
459
501
|
One caveat if you are moving an inline effect to a file: a template literal can
|
|
460
502
|
interpolate constants into the source (`vec2(${ASPECT}, 1.0)`), and a static
|
|
@@ -470,8 +512,8 @@ uniform values — fine when the look is uniform, and the reason to clone (and
|
|
|
470
512
|
pay a link) when it is not.
|
|
471
513
|
|
|
472
514
|
Note this covers **your** effects only. The engine's own mesh/quad shader
|
|
473
|
-
variants are
|
|
474
|
-
|
|
515
|
+
variants are covered by the loading-screen warm-up and by the per-renderable one
|
|
516
|
+
— see `melonjs-loading-assets`.
|
|
475
517
|
|
|
476
518
|
### Screen-reading builtins
|
|
477
519
|
|
|
@@ -609,6 +651,8 @@ same question after construction.
|
|
|
609
651
|
| shadow/smear offset flips on some draws | vertical UV offset not multiplied by `uUVYDir` |
|
|
610
652
|
| frame rate collapses with many blended sprites | an *advanced* mode (overlay, darken, …) — each draw is a capture plus a composite |
|
|
611
653
|
| animated shader never moves | `setTime` fed milliseconds, or the shader declares no `uTime` |
|
|
654
|
+
| squash and stretch only bulges, the height never changes, under Matter | the adapter rewrites `currentTransform` every step; set the scale from the sprite's `update()` |
|
|
655
|
+
| `SquishEffect` squishes the whole screen instead of the sprite | it is chained with another effect, or on the camera; give the sprite it alone |
|
|
612
656
|
| syntax error in a file whose shader you just edited, pointing at a line of GLSL | a backtick in the shader body ended the template literal — most often one wrapped around an identifier in a comment |
|
|
613
657
|
|
|
614
658
|
## Related skills
|
|
@@ -112,9 +112,9 @@ onDeactivateEvent() {
|
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
**Pool-recycled objects never fire `onDestroyEvent` on removal** — `removeChildNow`
|
|
115
|
-
tries
|
|
116
|
-
|
|
117
|
-
instead. Anything you clean up there will not run. `destroy()` is also what calls
|
|
115
|
+
first tries to return the child to the pool that built it, and only calls
|
|
116
|
+
`destroy()` when nothing owns it. So anything taken from a `createPool` goes
|
|
117
|
+
back to that pool instead. Anything you clean up there will not run. `destroy()` is also what calls
|
|
118
118
|
`releaseAllPointerEvents(this)`, which is why a pooled object must release its
|
|
119
119
|
own pointer registrations in `onDeactivateEvent`.
|
|
120
120
|
|
|
@@ -134,8 +134,10 @@ Without this, a "children emptied" handler can reset the wrong stage.
|
|
|
134
134
|
Contact callbacks fire inline inside the physics step, mid-iteration over the
|
|
135
135
|
detector's pair list. `container.removeChild()` is safe there — it defers the
|
|
136
136
|
actual removal to the next tick, which is exactly why it defers. The immediate
|
|
137
|
-
forms are the dangerous ones: `removeChildNow()`
|
|
138
|
-
|
|
137
|
+
forms are the dangerous ones: `removeChildNow()` tears the object down under
|
|
138
|
+
the iteration, via the internal `destroy()` it calls, which nulls `pos` and
|
|
139
|
+
`body`. (`destroy()` is engine teardown and is not in the published types, so
|
|
140
|
+
`removeChildNow` is the call a game actually makes.) The builtin
|
|
139
141
|
detector guards the pair it is currently resolving, but nothing guarantees that
|
|
140
142
|
for the rest of the step or for another adapter.
|
|
141
143
|
|
|
@@ -149,7 +151,7 @@ Use `removeChild()`, or flag the work and drain it from `Stage.update`.
|
|
|
149
151
|
| handlers accumulate on pooled objects | subscribed in the constructor rather than `onActivateEvent` |
|
|
150
152
|
| cleanup never runs on a pooled object | pooled removal does not fire `onDestroyEvent` |
|
|
151
153
|
| crash during a stage switch | callback ran mid-teardown — guard with `state.isCurrent` |
|
|
152
|
-
| crash after a collision | `removeChildNow()`
|
|
154
|
+
| crash after a collision | `removeChildNow()` inside a contact callback — use `removeChild()` |
|
|
153
155
|
| progress callbacks never fire | assigned `loader.onProgress` (removed in 20.3) instead of subscribing to `LOADER_PROGRESS` |
|
|
154
156
|
| `onActivateEvent` never runs | the child was added to a container not yet attached to the world |
|
|
155
157
|
|
|
@@ -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, 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, registerPointerEvent, releasePointerEvent, pointerdown, pointerup, pointermove, POINTERMOVE, gamepad, bindGamepad, triggerKeyEvent, 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
|
|
|
@@ -29,6 +29,31 @@ wrong for jump or fire. A locked action stays consumed until the key is released
|
|
|
29
29
|
or you call `input.unlockKey("jump")`. Use `input.keyStatus(action)` when you
|
|
30
30
|
want the raw held/not-held state regardless of locking.
|
|
31
31
|
|
|
32
|
+
### A poll can miss a press entirely
|
|
33
|
+
|
|
34
|
+
`isKeyPressed` is sampled once per frame, and `keyup` decrements the key's
|
|
35
|
+
counter the moment it arrives — locked or not. A press that goes down and up
|
|
36
|
+
between two frames therefore leaves the counter back at zero and is never
|
|
37
|
+
seen. At 60fps that is anything under ~16ms: rare from a human finger, routine
|
|
38
|
+
from a synthetic event, and more likely the slower the frame rate.
|
|
39
|
+
|
|
40
|
+
For a screen where a missed press is a dead end — a splash, a "press any key",
|
|
41
|
+
a notice to acknowledge — listen for the event instead, which cannot be missed:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import { event } from "melonjs";
|
|
45
|
+
|
|
46
|
+
// action is the bound action name, or undefined for an unbound key
|
|
47
|
+
const onKey = (action) => { if (action === "menuSelect") continue(); };
|
|
48
|
+
event.on(event.KEYDOWN, onKey); // in onResetEvent
|
|
49
|
+
event.off(event.KEYDOWN, onKey); // in onDestroyEvent — it does not self-remove
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`KEYDOWN` fires for *every* key, bound or not, so matching on `action` is what
|
|
53
|
+
narrows it; it also repeats while the key is held, so guard anything that must
|
|
54
|
+
happen once. Keep polling for movement and for a menu cursor, where one step
|
|
55
|
+
per press and a key held across a state change are the things that matter.
|
|
56
|
+
|
|
32
57
|
The full signature is `bindKey(keyCode, action, lock?, preventDefault?)`;
|
|
33
58
|
`preventDefault` defaults to `input.preventDefault` (itself `true`). Pass the
|
|
34
59
|
fourth argument per binding, or change the global default with
|
|
@@ -172,11 +197,80 @@ input.bindGamepad(0,
|
|
|
172
197
|
```
|
|
173
198
|
|
|
174
199
|
**Order matters:** `bindGamepad` throws `no action defined for keycode N` if the
|
|
175
|
-
keycode has not already been passed to `bindKey`.
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
keycode has not already been passed to `bindKey`.
|
|
201
|
+
|
|
202
|
+
Axes use the same call with a threshold, and bind **one half of the axis each**
|
|
203
|
+
— the sign selects which half:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
input.bindGamepad(0, { type: "axes", code: input.GAMEPAD.AXES.LX, threshold: -0.15 }, input.KEY.LEFT);
|
|
207
|
+
input.bindGamepad(0, { type: "axes", code: input.GAMEPAD.AXES.LX, threshold: 0.15 }, input.KEY.RIGHT);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**`threshold` is added to the deadzone, it does not replace it.** The test is
|
|
211
|
+
`abs(value) >= deadzone + abs(threshold)`, so with the default deadzone of 0.1
|
|
212
|
+
a threshold of `0.5` does not fire until 0.6 of full travel, and `0.15` fires at
|
|
213
|
+
0.25. Pick the number you want the stick to break at and subtract the deadzone.
|
|
214
|
+
Raising `setGamepadDeadzone` moves every axis binding with it.
|
|
215
|
+
|
|
216
|
+
`unbindGamepad(index, button, type?)`, `setGamepadDeadzone(value)` and
|
|
178
217
|
`setGamepadMapping(id, mapping)` complete the surface.
|
|
179
218
|
|
|
219
|
+
### Which index to bind
|
|
220
|
+
|
|
221
|
+
Bindings are per pad **index**, and the index is plug order, not identity —
|
|
222
|
+
nothing reports which pad is "the" controller. Slot 0 is a guess: a wheel, a
|
|
223
|
+
spare pad or a keyboard-shaped device left plugged in can hold it for a whole
|
|
224
|
+
session while the player presses buttons on slot 1.
|
|
225
|
+
|
|
226
|
+
So a one-player game binds **every slot it will accept** to the same actions,
|
|
227
|
+
and lets `activeGamepad()` say which one is in use when something has to go
|
|
228
|
+
back the other way:
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
for (let pad = 0; pad < 4; pad++) {
|
|
232
|
+
input.bindGamepad(pad, { type: "buttons", code: input.GAMEPAD.BUTTONS.FACE_1 }, input.KEY.X);
|
|
233
|
+
}
|
|
234
|
+
input.activeGamepad(); // the last pad to produce a bound input, else the
|
|
235
|
+
// first connected one, else -1
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Binding spare slots costs nothing: the poll skips a bound index with no pad in
|
|
239
|
+
it. `activeGamepad()` follows **bound inputs only**, which is what makes it an
|
|
240
|
+
answer about the player rather than about electrical noise — a worn stick at
|
|
241
|
+
rest reports movement for ever and would otherwise claim to be the active pad.
|
|
242
|
+
|
|
243
|
+
### Rumble
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
input.vibrateGamepad(input.activeGamepad(), {
|
|
247
|
+
duration: 120, strongMagnitude: 0.6, weakMagnitude: 0.9,
|
|
248
|
+
});
|
|
249
|
+
input.stopGamepadVibration(index); // cut a long effect short
|
|
250
|
+
input.hasGamepadVibration(index); // for an options-screen toggle
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Safe to call blind: no pad, no motors, or no Gamepad API at all resolves
|
|
254
|
+
`false` and does nothing, so game code never branches on support. Magnitudes
|
|
255
|
+
default to **full** — the underlying web API defaults every parameter to zero,
|
|
256
|
+
which makes a bare `playEffect` a silent no-op. A second call preempts the
|
|
257
|
+
first on the same pad, so a rapid series of hits reads as a rapid series of
|
|
258
|
+
hits rather than as a queue. For a SUSTAINED rumble issue one effect with a
|
|
259
|
+
long `duration` instead of calling every frame.
|
|
260
|
+
|
|
261
|
+
Hot-plugging needs no polling of your own — the engine listens for the browser's
|
|
262
|
+
own events and republishes them:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
import { event } from "melonjs";
|
|
266
|
+
|
|
267
|
+
event.on(event.GAMEPAD_CONNECTED, (gamepad) => { /* gamepad.id, .index */ });
|
|
268
|
+
event.on(event.GAMEPAD_DISCONNECTED, (gamepad) => { /* pause, show a prompt */ });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Bindings survive a disconnect: they are attached to the index, not to the
|
|
272
|
+
device, so a pad unplugged and plugged back into the same slot keeps working.
|
|
273
|
+
|
|
180
274
|
## Virtual controls
|
|
181
275
|
|
|
182
276
|
On-screen buttons synthesise key events, again reaching the same actions:
|
|
@@ -199,6 +293,8 @@ input.triggerKeyEvent(input.KEY.LEFT, false); // release
|
|
|
199
293
|
| jump fires every frame while held | `bindKey` without its third `lock` argument |
|
|
200
294
|
| `invalid event type` thrown at registration | non-pointer name (`"click"`, `"mousedown"`, …) passed to `registerPointerEvent` |
|
|
201
295
|
| `no action defined for keycode N` | `bindGamepad` called before `bindKey` for that keycode |
|
|
296
|
+
| stick has to be pushed well past its `threshold` before it registers | `threshold` is ADDED to the deadzone, not used instead of it |
|
|
297
|
+
| one half of a stick does nothing | each half is a separate `bindGamepad`, chosen by the sign of `threshold` |
|
|
202
298
|
| a widget under an overlapping panel still reacts | the panel consumes nothing — return `false` from its `onClick` and `onOver` |
|
|
203
299
|
| 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 |
|
|
204
300
|
| 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 |
|
|
@@ -111,13 +111,31 @@ the assets are in and **before** `LOADER_COMPLETE` fires, so it happens while
|
|
|
111
111
|
the progress bar is still up. Every `preload()` warms, not just the first, so a
|
|
112
112
|
per-level preload warms whatever that level brought with it.
|
|
113
113
|
|
|
114
|
-
The one precondition: **a game that never calls
|
|
115
|
-
|
|
114
|
+
The one precondition for this part: **a game that never calls
|
|
115
|
+
`loader.preload()` does not get it**. If you load assets another way, call
|
|
116
116
|
`await app.renderer.prewarm()` yourself. On
|
|
117
117
|
WebGL that links the mesh batchers' variants, for the unlit tier and the lit
|
|
118
118
|
one that inherits from it; on WebGPU it compiles the
|
|
119
|
-
WGSL module of every shader the loader holds
|
|
120
|
-
|
|
119
|
+
WGSL module of every shader the loader holds, and the mip-chain blitter when
|
|
120
|
+
textures are filtered.
|
|
121
|
+
|
|
122
|
+
**4. Everything that depends on the renderable — warmed as you build, since
|
|
123
|
+
20.10.** The rest cannot be done at preload, because it depends on what draws:
|
|
124
|
+
on WebGPU a *pipeline* is keyed by blend mode, culling, transparency and the
|
|
125
|
+
format of the target as well as by shader, and that compile is the expensive
|
|
126
|
+
part (tens of milliseconds, up to 140ms measured, per effect). So, behind the
|
|
127
|
+
same `prewarm` setting, the engine warms a renderable when it gains an effect
|
|
128
|
+
(`addPostEffect`, or assigning `shader`) and when it is added to the world:
|
|
129
|
+
its effect pipelines, its own draw, the render targets its chain composites
|
|
130
|
+
through, the advanced-blend shader if it uses one. Inline effects are covered
|
|
131
|
+
too; on WebGL that means linking a mesh-hosted effect's spliced program.
|
|
132
|
+
|
|
133
|
+
That work starts after your code has run and finishes off the frame, so it
|
|
134
|
+
needs **at least a frame between setting a renderable up and drawing it**. The
|
|
135
|
+
pattern that gets it for free is the one you want anyway: build pools (and
|
|
136
|
+
attach their effects) when the stage starts, then show and hide them during
|
|
137
|
+
play. Something created and drawn in the same frame still compiles on that
|
|
138
|
+
frame.
|
|
121
139
|
|
|
122
140
|
Cost to a game that uses none of it is about 60ms of preload on a desktop GPU.
|
|
123
141
|
Set it to `false` only for a target where linking is slow enough that the
|