melonjs 20.1.1 → 20.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -10
- package/build/application/settings.d.ts +4 -2
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +2 -2
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/backend/buffer.d.ts +26 -0
- package/build/audio/backend/buffer.d.ts.map +1 -0
- package/build/audio/backend/cache.d.ts +2 -0
- package/build/audio/backend/cache.d.ts.map +1 -0
- package/build/audio/backend/core.d.ts +24 -0
- package/build/audio/backend/core.d.ts.map +1 -0
- package/build/audio/backend/engine.d.ts +54 -0
- package/build/audio/backend/engine.d.ts.map +1 -0
- package/build/audio/backend/sound.d.ts +79 -0
- package/build/audio/backend/sound.d.ts.map +1 -0
- package/build/audio/backend/spatial.d.ts +191 -0
- package/build/audio/backend/spatial.d.ts.map +1 -0
- package/build/audio/backend/types.d.ts +97 -0
- package/build/audio/backend/types.d.ts.map +1 -0
- package/build/audio/backend/voice.d.ts +33 -0
- package/build/audio/backend/voice.d.ts.map +1 -0
- package/build/audio/playback.d.ts +49 -4
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/{backend.d.ts → state.d.ts} +13 -11
- package/build/audio/state.d.ts.map +1 -0
- package/build/audio/types.d.ts +93 -1
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +5 -5
- package/build/const.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +10048 -10341
- package/build/index.js.map +4 -4
- package/build/input/input.d.ts +19 -0
- package/build/input/input.d.ts.map +1 -1
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/lang/deprecated.d.ts +4 -2
- package/build/lang/deprecated.d.ts.map +1 -1
- package/build/level/gltf/GLTFScene.d.ts +12 -0
- package/build/level/gltf/GLTFScene.d.ts.map +1 -1
- package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
- package/build/loader/loader.d.ts +4 -40
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/parsers/audio.d.ts +31 -0
- package/build/loader/parsers/audio.d.ts.map +1 -0
- package/build/loader/parsers/shader.d.ts.map +1 -1
- package/build/particles/emitter.d.ts +81 -3
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +62 -1
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +50 -7
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +3 -3
- package/build/physics/builtin/body.d.ts +23 -24
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts +1 -1
- package/build/plugin/plugin.d.ts +9 -1
- package/build/plugin/plugin.d.ts.map +1 -1
- package/build/polyfill/index.d.ts +1 -4
- package/build/polyfill/index.d.ts.map +1 -1
- package/build/renderable/container.d.ts +10 -0
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/entity/entity.d.ts +10 -0
- package/build/renderable/entity/entity.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +16 -0
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +122 -32
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +7 -7
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/state/stage.d.ts +4 -2
- package/build/state/stage.d.ts.map +1 -1
- package/build/system/event.d.ts +1 -1
- package/build/system/platform.d.ts +21 -1
- package/build/system/platform.d.ts.map +1 -1
- package/build/utils/fetchdata.d.ts.map +1 -0
- package/build/video/blendmodes.d.ts +107 -0
- package/build/video/blendmodes.d.ts.map +1 -0
- package/build/video/canvas/canvas_renderer.d.ts +31 -24
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/blendEffect.d.ts +57 -0
- package/build/video/effects/blendEffect.d.ts.map +1 -0
- package/build/video/effects/blur.d.ts +4 -4
- package/build/video/effects/blur.d.ts.map +1 -1
- package/build/video/effects/chromaticAberration.d.ts +5 -5
- package/build/video/effects/chromaticAberration.d.ts.map +1 -1
- package/build/video/effects/colorMatrix.d.ts +5 -5
- package/build/video/effects/colorMatrix.d.ts.map +1 -1
- package/build/video/effects/desaturate.d.ts +5 -5
- package/build/video/effects/desaturate.d.ts.map +1 -1
- package/build/video/effects/dissolve.d.ts +4 -4
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +5 -5
- package/build/video/effects/dropShadow.d.ts.map +1 -1
- package/build/video/effects/flash.d.ts +4 -4
- package/build/video/effects/flash.d.ts.map +1 -1
- package/build/video/effects/glow.d.ts +5 -5
- package/build/video/effects/glow.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts +4 -4
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/invert.d.ts +5 -5
- package/build/video/effects/invert.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +6 -6
- package/build/video/effects/outline.d.ts.map +1 -1
- package/build/video/effects/pixelate.d.ts +4 -4
- package/build/video/effects/pixelate.d.ts.map +1 -1
- package/build/video/effects/radialGradient.d.ts +2 -2
- package/build/video/effects/scanline.d.ts +6 -6
- package/build/video/effects/scanline.d.ts.map +1 -1
- package/build/video/effects/sepia.d.ts +5 -5
- package/build/video/effects/sepia.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +12 -10
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +3 -3
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/tintPulse.d.ts +4 -4
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/vignette.d.ts +6 -6
- package/build/video/effects/vignette.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +4 -4
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/renderer.d.ts +3 -4
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/rendertarget/canvasrendertarget.d.ts +1 -0
- package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +1 -1
- package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
- package/build/video/webgl/glshader.d.ts +2 -2
- package/build/video/webgl/glshader.d.ts.map +1 -1
- package/build/video/webgl/utils/program.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts +117 -29
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts +7 -0
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/pipeline/cache.d.ts +0 -9
- package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +101 -6
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +6 -10
- package/skills/AGENTS.md +67 -0
- package/skills/melonjs/SKILL.md +156 -0
- package/skills/melonjs-20-migration/SKILL.md +160 -0
- package/skills/melonjs-3d/SKILL.md +226 -0
- package/skills/melonjs-3d-assets/SKILL.md +239 -0
- package/skills/melonjs-audio/SKILL.md +215 -0
- package/skills/melonjs-camera-and-drawing/SKILL.md +171 -0
- package/skills/melonjs-deployment/SKILL.md +113 -0
- package/skills/melonjs-effects-and-shaders/SKILL.md +177 -0
- package/skills/melonjs-events/SKILL.md +159 -0
- package/skills/melonjs-getting-started/SKILL.md +183 -0
- package/skills/melonjs-input/SKILL.md +166 -0
- package/skills/melonjs-lighting/SKILL.md +139 -0
- package/skills/melonjs-loading-assets/SKILL.md +157 -0
- package/skills/melonjs-particles-and-trails/SKILL.md +136 -0
- package/skills/melonjs-performance/SKILL.md +178 -0
- package/skills/melonjs-physics/SKILL.md +321 -0
- package/skills/melonjs-plugins/SKILL.md +191 -0
- package/skills/melonjs-renderables/SKILL.md +214 -0
- package/skills/melonjs-renderer-backends/SKILL.md +173 -0
- package/skills/melonjs-scenes-and-state/SKILL.md +180 -0
- package/skills/melonjs-sprites-and-animation/SKILL.md +173 -0
- package/skills/melonjs-tilemaps/SKILL.md +193 -0
- package/skills/melonjs-ui-and-text/SKILL.md +175 -0
- package/build/audio/backend.d.ts.map +0 -1
- package/build/loader/parsers/fetchdata.d.ts.map +0 -1
- /package/build/{loader/parsers → utils}/fetchdata.d.ts +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-scenes-and-state
|
|
3
|
+
description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Stage, state, state.set, state.change, state.transition, state.pause, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Scenes, game state and timing
|
|
8
|
+
|
|
9
|
+
## Stages and the state manager
|
|
10
|
+
|
|
11
|
+
A `Stage` is a screen. Register it against a state id, then change to it:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { Stage, state } from "melonjs";
|
|
15
|
+
|
|
16
|
+
class PlayScreen extends Stage {
|
|
17
|
+
onResetEvent(app) { /* build the scene */ }
|
|
18
|
+
onDestroyEvent(app) { /* tear it down */ }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
state.set(state.PLAY, new PlayScreen()); // register first
|
|
22
|
+
state.change(state.PLAY); // then switch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Built-in ids: `state.LOADING`, `state.MENU`, `state.READY`, `state.PLAY`,
|
|
26
|
+
`state.GAMEOVER` (no underscore), `state.GAME_END`, `state.SCORE`,
|
|
27
|
+
`state.CREDITS`, `state.SETTINGS`, `state.DEFAULT`. `state.USER` (100) is the
|
|
28
|
+
base for your own ids — `const CUTSCENE = state.USER + 1`.
|
|
29
|
+
|
|
30
|
+
**Build scene content in `onResetEvent`, not the constructor.** `onResetEvent`
|
|
31
|
+
runs on *every* entry to the state; the constructor runs once. Scene content
|
|
32
|
+
created in the constructor exists once and is not rebuilt when you return.
|
|
33
|
+
|
|
34
|
+
`onResetEvent(app)` receives the `Application` — use that rather than importing
|
|
35
|
+
the global `game`.
|
|
36
|
+
|
|
37
|
+
Extra arguments to `state.change` are forwarded:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
state.change(state.PLAY, false, { level: 2 }); // → onResetEvent(app, { level: 2 })
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The second argument is `forceChange`: pass `true` to switch immediately instead
|
|
44
|
+
of on a deferred tick, which is what you want when changing state right after a
|
|
45
|
+
preload completes. It is ignored when a transition is configured — a transition
|
|
46
|
+
always drives the switch from its own completion callback.
|
|
47
|
+
|
|
48
|
+
`state.change` **throws** `Undefined Stage for state '<id>'` if nothing was
|
|
49
|
+
registered for that id, and `state.set` throws if the second argument is not a
|
|
50
|
+
`Stage` instance. Neither fails quietly.
|
|
51
|
+
|
|
52
|
+
## Persistent objects across levels
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
if (typeof this.HUD === "undefined") {
|
|
56
|
+
this.HUD = new Container(); // construct once (or UIBaseElement)
|
|
57
|
+
}
|
|
58
|
+
this.HUD.isPersistent = true; // survives level changes
|
|
59
|
+
this.HUD.floating = true; // screen coordinates, not world
|
|
60
|
+
app.world.addChild(this.HUD, 100); // explicit z — see melonjs-renderables
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Transitions and pausing
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
state.transition("fade", "#FFFFFF", 250);
|
|
67
|
+
state.transition("mask", "#000", 500, new Ellipse(0, 0, 1, 1)); // shape required
|
|
68
|
+
state.pause(); // freeze update, keep drawing
|
|
69
|
+
state.resume();
|
|
70
|
+
app.freeze(150); // brief hit-stop (proxies state.freeze)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`state.transition` is global and sticky — once set it applies to every later
|
|
74
|
+
`state.change`. The `"mask"` form needs its fourth argument, an `Ellipse` or
|
|
75
|
+
`Polygon`; without one it warns and falls back to a direct switch.
|
|
76
|
+
|
|
77
|
+
## The update loop
|
|
78
|
+
|
|
79
|
+
There is no loop you own. Logic goes in `update(dt)` overrides, or a
|
|
80
|
+
`GAME_UPDATE` subscription for scene-level animation:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
event.on(event.GAME_UPDATE, () => { /* per frame */ });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`update(dt)` must **return `true`** when the object changed and needs redrawing.
|
|
87
|
+
Returning nothing is falsy and the object can appear frozen while its state moves.
|
|
88
|
+
|
|
89
|
+
`dt` is milliseconds since the last frame and is the right basis for motion —
|
|
90
|
+
the engine already paces it. Do not reach for `performance.now()`.
|
|
91
|
+
|
|
92
|
+
A common idiom for a per-frame game controller with nothing to draw is a
|
|
93
|
+
`Renderable(0, 0, 1, 1)` with `alwaysUpdate = true` and an empty `draw()`.
|
|
94
|
+
|
|
95
|
+
## Timers: use the engine's, not the window's
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { timer } from "melonjs";
|
|
99
|
+
|
|
100
|
+
timer.setTimeout(fn, 1000);
|
|
101
|
+
const id = timer.setInterval(fn, 500);
|
|
102
|
+
timer.clearInterval(id); // in the Stage's onDestroyEvent
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`Stage` has only `onResetEvent` and `onDestroyEvent` — there is no
|
|
106
|
+
`onActivateEvent` / `onDeactivateEvent` on a stage. Those two are `Renderable`
|
|
107
|
+
hooks; put per-renderable teardown there instead.
|
|
108
|
+
|
|
109
|
+
Engine timers are **pause-aware**. `window.setTimeout` keeps firing while the
|
|
110
|
+
game is paused, which produces enemies spawning behind a pause menu. Pass
|
|
111
|
+
`false` as the third argument — `timer.setInterval(fn, 500, false)` — for one
|
|
112
|
+
that keeps running through a pause, which is what a pause-menu animation or a
|
|
113
|
+
countdown that should not freeze needs. (That argument was ignored by
|
|
114
|
+
`setInterval` before 20.3.)
|
|
115
|
+
|
|
116
|
+
## Tweens
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import { Tween, pool } from "melonjs";
|
|
120
|
+
|
|
121
|
+
const t = pool.pull("Tween", sprite.pos) // registered name is "Tween"
|
|
122
|
+
.to({ x: 300 }, { duration: 500 }) // options object, not a number
|
|
123
|
+
.easing(Tween.Easing.Quadratic.Out)
|
|
124
|
+
.onComplete(() => { /* … */ })
|
|
125
|
+
.start(); // ← without this, nothing happens
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Four traps:
|
|
129
|
+
|
|
130
|
+
- **A tween without `.start()`** (or `autoStart: true` in the `to()` options)
|
|
131
|
+
silently does nothing.
|
|
132
|
+
- **`to()`'s second argument is an options object**
|
|
133
|
+
(`{ duration, easing, yoyo, repeat, delay, repeatDelay, interpolation,
|
|
134
|
+
autoStart }`). Passing a bare number is not a duration — it is read for a
|
|
135
|
+
`.duration` property, finds none, and the tween silently runs the default
|
|
136
|
+
1000 ms.
|
|
137
|
+
- **Tweens freeze during `state.pause()` / `freeze()`** unless
|
|
138
|
+
`tween.updateWhenPaused = true` — which is how you get an effect that decays
|
|
139
|
+
*through* a hit-stop.
|
|
140
|
+
- **Stop a tween before destroying its target**, or `onUpdate` fires against a
|
|
141
|
+
dead renderable.
|
|
142
|
+
|
|
143
|
+
## Guard teardown-adjacent callbacks
|
|
144
|
+
|
|
145
|
+
Callbacks can fire while a stage is being torn down, when the current state is
|
|
146
|
+
already something else:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
if (!state.isCurrent(state.PLAY)) return;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Without the guard, a "children emptied" handler can reset the wrong stage.
|
|
153
|
+
|
|
154
|
+
## Persistence
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
import { save } from "melonjs";
|
|
158
|
+
|
|
159
|
+
save.add({ hiscore: 0 }); // once; idempotent, loads any stored value
|
|
160
|
+
save.hiscore = 1200; // plain assignment writes to localStorage
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Symptom → cause
|
|
164
|
+
|
|
165
|
+
| symptom | cause |
|
|
166
|
+
|---|---|
|
|
167
|
+
| scene builds once and is empty on return | content created in the constructor, not `onResetEvent` |
|
|
168
|
+
| `Undefined Stage for state 'N'` thrown | no `state.set` for that id first |
|
|
169
|
+
| a tween finishes in 1000 ms whatever you pass | `to()` takes `{ duration }`, not a number |
|
|
170
|
+
| object frozen while its state updates | `update()` not returning `true` |
|
|
171
|
+
| timers fire behind a pause menu | `window.setTimeout` instead of `timer.setTimeout` |
|
|
172
|
+
| a tween does nothing | `.start()` never called |
|
|
173
|
+
| effect stops during a hit-stop | tween needs `updateWhenPaused = true` |
|
|
174
|
+
| crash after a stage switch | callback ran during teardown — guard with `state.isCurrent` |
|
|
175
|
+
| HUD scrolls away with the camera | missing `floating = true` |
|
|
176
|
+
|
|
177
|
+
## Related skills
|
|
178
|
+
|
|
179
|
+
- `melonjs-getting-started` — the Application and the first scene
|
|
180
|
+
- `melonjs-renderables` — `floating`, draw order, update/draw contracts
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-sprites-and-animation
|
|
3
|
+
description: "Use this skill when creating sprites, playing frame animations, or working with texture atlases in melonJS. Covers Sprite construction from a plain image or an atlas region, addAnimation/setCurrentAnimation, TextureAtlas for TexturePacker/ShoeBox/Aseprite, tint, flip, NineSliceSprite, and the centred anchorPoint that offsets naive positioning. Triggers on: Sprite, new Sprite, addAnimation, setCurrentAnimation, isCurrentAnimation, TextureAtlas, TexturePacker, Aseprite, ShoeBox, atlas, region, framewidth, frameheight, tint, flipX, flipY, NineSliceSprite, animation."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Sprites, animation and atlases
|
|
8
|
+
|
|
9
|
+
> Creating sprites from images or atlases, driving frame animation, and the
|
|
10
|
+
> positioning trap that catches almost everyone.
|
|
11
|
+
|
|
12
|
+
## `pos` is the centre, not the top-left
|
|
13
|
+
|
|
14
|
+
`Renderable.anchorPoint` defaults to **`(0.5, 0.5)`**, so a sprite's `pos` is its
|
|
15
|
+
**centre**. `Container` forces `(0, 0)` instead, which is why containers and
|
|
16
|
+
sprites appear to disagree.
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// a 64×64 sprite at (100, 100) covers 68..132, not 100..164
|
|
20
|
+
const s = new Sprite(100, 100, { image: "hero", framewidth: 64, frameheight: 64 });
|
|
21
|
+
|
|
22
|
+
s.anchorPoint.set(0, 0); // switch to top-left positioning if you prefer
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Writing top-left maths against the centred default puts everything half a sprite
|
|
26
|
+
off, with no error. It is the most common source of "close but subtly wrong"
|
|
27
|
+
placement.
|
|
28
|
+
|
|
29
|
+
## Constructing a sprite
|
|
30
|
+
|
|
31
|
+
From a plain image:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
const hero = new Sprite(x, y, {
|
|
35
|
+
image: "hero", // preloaded asset name
|
|
36
|
+
framewidth: 32,
|
|
37
|
+
frameheight: 32,
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
From a texture atlas region:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const atlas = new TextureAtlas(loader.getJSON("sprites"), loader.getImage("sprites"));
|
|
45
|
+
const coin = new Sprite(x, y, { image: atlas, region: "coin_01.png" });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`framewidth` / `frameheight` define the animation grid on a sheet. Omit them and
|
|
49
|
+
the whole image is one frame.
|
|
50
|
+
|
|
51
|
+
## Animation
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
hero.addAnimation("idle", [0, 1, 2, 3], 100); // frames, ms per frame
|
|
55
|
+
hero.addAnimation("walk", [4, 5, 6, 7]);
|
|
56
|
+
hero.addAnimation("die", [8, 9], 150);
|
|
57
|
+
|
|
58
|
+
hero.setCurrentAnimation("walk");
|
|
59
|
+
hero.setCurrentAnimation("die", "idle"); // chain to idle when it ends
|
|
60
|
+
hero.setCurrentAnimation("die", { loop: false }); // play once, hold the last frame
|
|
61
|
+
hero.setCurrentAnimation("die", { loop: false, onComplete: fn }); // …and call fn when it does
|
|
62
|
+
|
|
63
|
+
if (hero.isCurrentAnimation("walk")) { /* … */ }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Frame indices are into the `framewidth`/`frameheight` grid. With an atlas, use
|
|
67
|
+
`atlas.getAnimationSettings([regionNames])` and spread it into the Sprite
|
|
68
|
+
settings, or `atlas.createAnimationFromName([regionNames])`, which returns a
|
|
69
|
+
ready-made Sprite. On a fixed-cell spritesheet only numeric indices are
|
|
70
|
+
accepted — passing region names throws.
|
|
71
|
+
|
|
72
|
+
The second argument to `setCurrentAnimation` is polymorphic. A **string** chains
|
|
73
|
+
to that animation when this one ends. An **options object** takes
|
|
74
|
+
`{ loop, next, speed, onComplete }`, and only an explicit `loop: false` stops the
|
|
75
|
+
looping. A **bare function** is the legacy form: it fires at *every* cycle end,
|
|
76
|
+
and only holds the last frame if it returns `false`. Omit the argument entirely
|
|
77
|
+
and the animation loops forever.
|
|
78
|
+
|
|
79
|
+
Re-selecting the animation that is already current is a no-op, so calling
|
|
80
|
+
`setCurrentAnimation` every frame is safe. An unknown id throws.
|
|
81
|
+
|
|
82
|
+
## Texture atlases
|
|
83
|
+
|
|
84
|
+
`TextureAtlas` reads several formats:
|
|
85
|
+
|
|
86
|
+
| source | how |
|
|
87
|
+
|---|---|
|
|
88
|
+
| TexturePacker / Free Texture Packer JSON | `new TextureAtlas(loader.getJSON(name), loader.getImage(name))` |
|
|
89
|
+
| Aseprite **JSON export** | same as TexturePacker — the format is detected from `meta.app` |
|
|
90
|
+
| Aseprite **`.aseprite` / `.ase` binary** | loader type `"aseprite"`, which stores the composited image *and* a JSON sidecar under the one asset name |
|
|
91
|
+
| ShoeBox | JSON export using the melonJS exporter settings file; same call as TexturePacker |
|
|
92
|
+
| fixed-cell spritesheet | `new TextureAtlas({ framewidth, frameheight, anchorPoint }, loader.getImage(name))` |
|
|
93
|
+
| multi-page | pass an array of atlas JSON objects as the first argument |
|
|
94
|
+
|
|
95
|
+
Helpers: `createSpriteFromName(name)`, `createAnimationFromName([names])`,
|
|
96
|
+
`getAnimationSettings([names])`.
|
|
97
|
+
|
|
98
|
+
Atlases matter for performance: the GPU batchers bind several textures at once
|
|
99
|
+
(up to `renderer.maxTextures`), so a handful of separate images still batch —
|
|
100
|
+
but once a frame crosses that many distinct textures the batch has to flush.
|
|
101
|
+
Packing into one atlas keeps everything on a single binding.
|
|
102
|
+
|
|
103
|
+
## Visual properties
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
sprite.tint.setColor(255, 128, 0); // multiply tint
|
|
107
|
+
sprite.alpha = 0.5;
|
|
108
|
+
sprite.flipX(true); // mirror horizontally
|
|
109
|
+
sprite.flipY(true);
|
|
110
|
+
sprite.blendMode = "additive"; // see the effects skill for the full list
|
|
111
|
+
sprite.scale(2, 2); // ← multiplicative, see below
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**`scale()` is multiplicative, not absolute.** Calling it every frame compounds.
|
|
115
|
+
For absolute scaling reset first:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
sprite.currentTransform.identity();
|
|
119
|
+
sprite.currentTransform.scale(s, s, 1);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This bites hardest with pooled sprites, which arrive carrying their previous
|
|
123
|
+
life's transform.
|
|
124
|
+
|
|
125
|
+
## `NineSliceSprite`
|
|
126
|
+
|
|
127
|
+
For panels and dialogue boxes that stretch without distorting their corners:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
new NineSliceSprite(x, y, {
|
|
131
|
+
image: "panel", width: 300, height: 120,
|
|
132
|
+
insetx: 12, insety: 12, // lowercase x/y — `insetX` is silently ignored
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`width` and `height` are mandatory and the constructor throws without them.
|
|
137
|
+
The insets default to a quarter of the frame.
|
|
138
|
+
|
|
139
|
+
## Pooling
|
|
140
|
+
|
|
141
|
+
Frequently spawned sprites should be pooled:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
pool.register("bullet", Bullet, true); // true = recyclable
|
|
145
|
+
const b = pool.pull("bullet", x, y); // new Bullet(x, y), or onResetEvent(x, y) on a reused one
|
|
146
|
+
world.removeChild(b); // returns it to the pool automatically
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Two consequences:
|
|
150
|
+
|
|
151
|
+
- A recyclable class's `onResetEvent` must reset **everything** its lifetime
|
|
152
|
+
mutates — alpha, tint, scale, animation state. Anything you forget carries
|
|
153
|
+
over to the next use.
|
|
154
|
+
- Pool-recycled objects do **not** fire `onDestroyEvent` on removal. Pair event
|
|
155
|
+
subscriptions with `onActivateEvent` / `onDeactivateEvent` instead.
|
|
156
|
+
|
|
157
|
+
## Symptom → cause
|
|
158
|
+
|
|
159
|
+
| symptom | cause |
|
|
160
|
+
|---|---|
|
|
161
|
+
| everything offset by half a sprite | top-left maths against the centred `anchorPoint` |
|
|
162
|
+
| sprite grows every frame | `scale()` is multiplicative — reset the transform |
|
|
163
|
+
| a recycled sprite keeps its old look | `onResetEvent` does not restore every mutated property |
|
|
164
|
+
| event handlers leak on pooled objects | bound in the constructor rather than `onActivateEvent` |
|
|
165
|
+
| animation never advances | no `framewidth`/`frameheight`, so the sheet is one frame |
|
|
166
|
+
| animation plays once and stops | `{ loop: false }`, or a callback that returned `false` |
|
|
167
|
+
| sprites do not batch | more distinct textures in one frame than `renderer.maxTextures` |
|
|
168
|
+
| nine-slice corners stretch anyway | `insetX`/`insetY` instead of `insetx`/`insety` |
|
|
169
|
+
|
|
170
|
+
## Related skills
|
|
171
|
+
|
|
172
|
+
- `melonjs-renderables` — anchors, draw order, custom draw
|
|
173
|
+
- `melonjs-getting-started` — preloading images and atlases
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-tilemaps
|
|
3
|
+
description: "Use this skill for Tiled maps in melonJS — loading TMX/TSX levels, spawning entities from Tiled objects, collision shapes authored in Tiled, isometric and hexagonal maps, and image layers. Covers the pool.register name contract, camera bounds, compressed maps needing the inflate plugin, and the level director API. Triggers on: Tiled, TMX, TSX, tilemap, level.load, tileset, ImageLayer, isometric, hexagonal, staggered, pool.register, Collectable, Trigger, object layer, collision layer, parallax."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tiled maps
|
|
8
|
+
|
|
9
|
+
> melonJS's flagship content pipeline. Maps are authored in
|
|
10
|
+
> [Tiled](https://www.mapeditor.org/) and loaded through the `level` director —
|
|
11
|
+
> you rarely touch the `TMX*` classes directly.
|
|
12
|
+
|
|
13
|
+
## Loading a level
|
|
14
|
+
|
|
15
|
+
Preload the map **and** everything it references — the `.tmx`, any external
|
|
16
|
+
`.tsx` tilesets, and the tileset images:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
await loader.preload([
|
|
20
|
+
{ name: "map1", type: "tmx", src: "data/map/map1.tmx" },
|
|
21
|
+
{ name: "tileset", type: "tsx", src: "data/map/tileset.tsx" },
|
|
22
|
+
{ name: "tiles", type: "image", src: "data/img/tiles.png" },
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
level.load("map1"); // inside Stage.onResetEvent
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Asset `type` is `"tmx"` for maps and `"tsx"` for external tilesets — those are
|
|
29
|
+
the only two type strings the loader knows. The *file* can be `.tmx` / `.tsx`
|
|
30
|
+
(XML), or `.json` / `.tmj` / `.tsj`; the parser branches on the extension, not
|
|
31
|
+
on the type. `type: "tmj"` throws `unknown or invalid resource type`. You can
|
|
32
|
+
also skip `src` and pass the map inline via `data` (with `format: "json"` or
|
|
33
|
+
`"xml"`).
|
|
34
|
+
|
|
35
|
+
`level.load(levelId, options)` accepts `container` (default `game.world`),
|
|
36
|
+
`onLoaded` (default `game.onLevelLoaded`), `flatten` (default `game.mergeGroup`)
|
|
37
|
+
and `setViewportBounds` (default **`true`**). It throws `level <id> not found`
|
|
38
|
+
for an unknown id.
|
|
39
|
+
|
|
40
|
+
**`level.load` is deferred while the game loop is running.** It calls
|
|
41
|
+
`state.stop()` and finishes the load in a `setTimeout`, so it returns `true`
|
|
42
|
+
before anything is in the world. Do follow-up work from the `onLoaded` callback
|
|
43
|
+
or an `event.LEVEL_LOADED` listener, not on the next line.
|
|
44
|
+
|
|
45
|
+
`level.reload()`, `level.next()`, `level.previous()`, `level.getCurrentLevelId()`
|
|
46
|
+
and `level.levelCount()` round out the namespace. `flatten: false` wraps each
|
|
47
|
+
Tiled object group in its own `Container` named after the group.
|
|
48
|
+
|
|
49
|
+
## Spawning entities from Tiled objects
|
|
50
|
+
|
|
51
|
+
Objects are matched against a factory registry, in this order: the object's
|
|
52
|
+
Tiled **class**, then its **name**, then the structural fallbacks `"text"`,
|
|
53
|
+
`"tile"` and `"shape"`.
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
pool.register("mainPlayer", PlayerEntity); // ← matches Tiled class OR name
|
|
57
|
+
level.load("map1");
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`pool.register(className, classObj, recycling)` also registers the class as a
|
|
61
|
+
Tiled object factory (and again under an `me.`-prefixed alias), unless you set
|
|
62
|
+
`pool.autoRegisterTiled = false`. The dedicated entry points are:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { registerTiledObjectClass, registerTiledObjectFactory } from "melonjs";
|
|
66
|
+
|
|
67
|
+
registerTiledObjectClass("Enemy", Enemy); // new Enemy(x, y, settings)
|
|
68
|
+
registerTiledObjectFactory("Spine", (settings, map) => { /* → Renderable */ });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Register **before** loading. With no match the object falls through to the
|
|
72
|
+
structural factory and becomes a plain `Renderable` with a static body — it does
|
|
73
|
+
not error, it just has none of your behaviour.
|
|
74
|
+
|
|
75
|
+
`registerTiledObjectClass` **throws** if you register a *different* constructor
|
|
76
|
+
under a name already taken (re-registering the same one is a no-op).
|
|
77
|
+
`registerTiledObjectFactory` overwrites and only `console.warn`s, so it is the
|
|
78
|
+
one to use for overriding a built-in such as `"shape"`.
|
|
79
|
+
|
|
80
|
+
melonJS pre-registers `Renderable`, `Sprite`, `NineSliceSprite`, `Text`,
|
|
81
|
+
`BitmapText`, `ImageLayer`, `ColorLayer`, `Light2d`, `Collectable` and `Trigger`
|
|
82
|
+
as Tiled classes, so `Collectable` and `Trigger` work out of the box.
|
|
83
|
+
|
|
84
|
+
## Camera bounds
|
|
85
|
+
|
|
86
|
+
`level.load` sets the viewport bounds to the map size for you —
|
|
87
|
+
`setViewportBounds` defaults to `true`, and the handler is re-run on every
|
|
88
|
+
viewport resize, centring the map when it is smaller than the screen. You only
|
|
89
|
+
need to do it by hand when you opted out:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
level.load("map1", { setViewportBounds: false });
|
|
93
|
+
app.viewport.setBounds(0, 0, mapWidth, mapHeight); // now it's on you
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The same is true if you drive `TMXTileMap.addTo(container, flatten, setViewportBounds)`
|
|
97
|
+
directly — note that method's `setViewportBounds` defaults to **`false`**, the
|
|
98
|
+
opposite of `level.load`'s.
|
|
99
|
+
|
|
100
|
+
## Collision shapes
|
|
101
|
+
|
|
102
|
+
Draw collision geometry in Tiled on an object layer and melonJS turns it into
|
|
103
|
+
world shapes automatically — rectangles, polygons, polylines, ellipses and
|
|
104
|
+
points all work.
|
|
105
|
+
|
|
106
|
+
The contract is the **layer name**: melonJS lowercases it and checks whether it
|
|
107
|
+
*contains* `"collision"` (so `collision`, `Collision`, `world collision` all
|
|
108
|
+
match). Inside such a layer, objects **with no name** get
|
|
109
|
+
`collisionType = collision.types.WORLD_SHAPE` and `type = "static"`. A *named*
|
|
110
|
+
object in a collision layer keeps whatever its factory gave it — that is how you
|
|
111
|
+
mix triggers into the collision layer.
|
|
112
|
+
|
|
113
|
+
An object that matches no factory and carries no text or tile data falls to the
|
|
114
|
+
`"shape"` factory: a plain `Renderable` with a static `bodyDef` built from its
|
|
115
|
+
geometry, which the world auto-registers with the active adapter on `addChild`.
|
|
116
|
+
|
|
117
|
+
For per-object physics, set the object's custom properties in Tiled; they arrive
|
|
118
|
+
on the settings object passed to your entity's constructor.
|
|
119
|
+
|
|
120
|
+
## Map orientations
|
|
121
|
+
|
|
122
|
+
Orthogonal, **isometric**, hexagonal, staggered and oblique are all supported;
|
|
123
|
+
anything else throws `<orientation> type TMX Tile Map not supported!` at load.
|
|
124
|
+
Isometric maps need y-sorting so objects overlap correctly:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
app.world.sortOn = "y"; // after level.load
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`sortOn` only accepts `"x"`, `"y"`, `"z"` (the default) or `"depth"` and throws
|
|
131
|
+
on anything else. `Vector2d`, `Vector3d`, their observable variants and `Polygon`
|
|
132
|
+
all carry `.toIso()` / `.to2d()` for converting between screen and map space.
|
|
133
|
+
|
|
134
|
+
## Image layers
|
|
135
|
+
|
|
136
|
+
`ImageLayer` gives parallax backgrounds with a ratio per axis, set in Tiled or in
|
|
137
|
+
code. Repeat behaviour matches Tiled's.
|
|
138
|
+
|
|
139
|
+
## Compressed maps need a plugin
|
|
140
|
+
|
|
141
|
+
A map saved with **gzip, zlib or zstd** compressed layer data throws
|
|
142
|
+
`No inflate function set — GZIP/ZLIB decompression not supported!` at parse time
|
|
143
|
+
without:
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
import { plugin } from "melonjs";
|
|
147
|
+
import { TiledInflatePlugin } from "@melonjs/tiled-inflate-plugin";
|
|
148
|
+
|
|
149
|
+
plugin.register(TiledInflatePlugin); // before preloading any compressed map
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Uncompressed base64 and CSV layer data need no plugin. If a map loads in Tiled
|
|
153
|
+
but not in melonJS, check the layer compression setting first.
|
|
154
|
+
|
|
155
|
+
## Rendering
|
|
156
|
+
|
|
157
|
+
Each tile layer resolves to one of three `renderMode`s — `"shader"`,
|
|
158
|
+
`"prerender"` or `"perTile"` — auto-selected in that order of preference, or
|
|
159
|
+
forced per layer. The `"shader"` path is a single quad per tileset with a
|
|
160
|
+
per-pixel GID lookup in the fragment shader. It requires **all** of:
|
|
161
|
+
|
|
162
|
+
- a renderer that reports GPU tile-layer support (WebGL 2 / WebGPU — not Canvas),
|
|
163
|
+
- `world.gpuTilemap !== false` (the `gpuTilemap` application setting, default `true`),
|
|
164
|
+
- an **orthogonal** layer — isometric, hexagonal, staggered and oblique always
|
|
165
|
+
fall back,
|
|
166
|
+
- no collection-of-image tileset, no non-zero `tileoffset`, and tile overflow
|
|
167
|
+
within the shader's 4-cell limit.
|
|
168
|
+
|
|
169
|
+
When a layer falls back, melonJS `console.warn`s once with the reason. Practical
|
|
170
|
+
consequences: fewer tilesets means fewer batches (one packed tileset beats
|
|
171
|
+
several small ones), and on the Canvas fallback large maps get noticeably
|
|
172
|
+
slower. `world.preRender` (or a `preRender` layer property in Tiled) opts an
|
|
173
|
+
unanimated layer into the offscreen-bake path instead.
|
|
174
|
+
|
|
175
|
+
## Symptom → cause
|
|
176
|
+
|
|
177
|
+
| symptom | cause |
|
|
178
|
+
|---|---|
|
|
179
|
+
| a Tiled object becomes a plain shape with no behaviour | its class/name does not match any registered factory, or it was registered after `level.load` |
|
|
180
|
+
| the world is still empty right after `level.load` | the load is deferred via `setTimeout` while the loop runs — use `onLoaded` / `LEVEL_LOADED` |
|
|
181
|
+
| `level <id> not found` | the map was never preloaded, or the asset `name` differs from the id passed to `load` |
|
|
182
|
+
| `unknown or invalid resource type` | asset `type` set to `"tmj"` / `"tsj"` — use `"tmx"` / `"tsx"` with the `.tmj` / `.tsj` file |
|
|
183
|
+
| camera will not scroll | `setViewportBounds: false`, or the map was added with `addTo()` (which defaults to `false`) |
|
|
184
|
+
| `No inflate function set` at parse time | compressed layers without the tiled-inflate plugin |
|
|
185
|
+
| collision shapes are not solid | the object layer's name does not contain `"collision"`, or the objects are named |
|
|
186
|
+
| isometric objects overlap wrongly | `world.sortOn` not set to `"y"` |
|
|
187
|
+
| missing tiles or a blank map | the `.tsx` or tileset image was not preloaded |
|
|
188
|
+
| tilemap slow on some machines | layer fell back off the shader path — check the console warning for the reason |
|
|
189
|
+
|
|
190
|
+
## Related skills
|
|
191
|
+
|
|
192
|
+
- `melonjs-physics` — what the collision shapes become once loaded
|
|
193
|
+
- `melonjs-getting-started` — preloading and the asset type list
|