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.
Files changed (165) hide show
  1. package/README.md +88 -10
  2. package/build/application/settings.d.ts +4 -2
  3. package/build/application/settings.d.ts.map +1 -1
  4. package/build/audio/audio.d.ts +2 -2
  5. package/build/audio/audio.d.ts.map +1 -1
  6. package/build/audio/backend/buffer.d.ts +26 -0
  7. package/build/audio/backend/buffer.d.ts.map +1 -0
  8. package/build/audio/backend/cache.d.ts +2 -0
  9. package/build/audio/backend/cache.d.ts.map +1 -0
  10. package/build/audio/backend/core.d.ts +24 -0
  11. package/build/audio/backend/core.d.ts.map +1 -0
  12. package/build/audio/backend/engine.d.ts +54 -0
  13. package/build/audio/backend/engine.d.ts.map +1 -0
  14. package/build/audio/backend/sound.d.ts +79 -0
  15. package/build/audio/backend/sound.d.ts.map +1 -0
  16. package/build/audio/backend/spatial.d.ts +191 -0
  17. package/build/audio/backend/spatial.d.ts.map +1 -0
  18. package/build/audio/backend/types.d.ts +97 -0
  19. package/build/audio/backend/types.d.ts.map +1 -0
  20. package/build/audio/backend/voice.d.ts +33 -0
  21. package/build/audio/backend/voice.d.ts.map +1 -0
  22. package/build/audio/playback.d.ts +49 -4
  23. package/build/audio/playback.d.ts.map +1 -1
  24. package/build/audio/{backend.d.ts → state.d.ts} +13 -11
  25. package/build/audio/state.d.ts.map +1 -0
  26. package/build/audio/types.d.ts +93 -1
  27. package/build/audio/types.d.ts.map +1 -1
  28. package/build/camera/camera3d.d.ts +5 -5
  29. package/build/const.d.ts +2 -2
  30. package/build/index.d.ts.map +1 -1
  31. package/build/index.js +10048 -10341
  32. package/build/index.js.map +4 -4
  33. package/build/input/input.d.ts +19 -0
  34. package/build/input/input.d.ts.map +1 -1
  35. package/build/input/pointerevent.d.ts.map +1 -1
  36. package/build/lang/deprecated.d.ts +4 -2
  37. package/build/lang/deprecated.d.ts.map +1 -1
  38. package/build/level/gltf/GLTFScene.d.ts +12 -0
  39. package/build/level/gltf/GLTFScene.d.ts.map +1 -1
  40. package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
  41. package/build/loader/loader.d.ts +4 -40
  42. package/build/loader/loader.d.ts.map +1 -1
  43. package/build/loader/parsers/audio.d.ts +31 -0
  44. package/build/loader/parsers/audio.d.ts.map +1 -0
  45. package/build/loader/parsers/shader.d.ts.map +1 -1
  46. package/build/particles/emitter.d.ts +81 -3
  47. package/build/particles/emitter.d.ts.map +1 -1
  48. package/build/particles/particle.d.ts +62 -1
  49. package/build/particles/particle.d.ts.map +1 -1
  50. package/build/particles/settings.d.ts +50 -7
  51. package/build/particles/settings.d.ts.map +1 -1
  52. package/build/physics/adapter.d.ts +3 -3
  53. package/build/physics/builtin/body.d.ts +23 -24
  54. package/build/physics/builtin/body.d.ts.map +1 -1
  55. package/build/physics/builtin/detector.d.ts +1 -1
  56. package/build/plugin/plugin.d.ts +9 -1
  57. package/build/plugin/plugin.d.ts.map +1 -1
  58. package/build/polyfill/index.d.ts +1 -4
  59. package/build/polyfill/index.d.ts.map +1 -1
  60. package/build/renderable/container.d.ts +10 -0
  61. package/build/renderable/container.d.ts.map +1 -1
  62. package/build/renderable/entity/entity.d.ts +10 -0
  63. package/build/renderable/entity/entity.d.ts.map +1 -1
  64. package/build/renderable/mesh.d.ts +16 -0
  65. package/build/renderable/mesh.d.ts.map +1 -1
  66. package/build/renderable/renderable.d.ts +122 -32
  67. package/build/renderable/renderable.d.ts.map +1 -1
  68. package/build/renderable/sprite.d.ts +7 -7
  69. package/build/renderable/sprite.d.ts.map +1 -1
  70. package/build/state/stage.d.ts +4 -2
  71. package/build/state/stage.d.ts.map +1 -1
  72. package/build/system/event.d.ts +1 -1
  73. package/build/system/platform.d.ts +21 -1
  74. package/build/system/platform.d.ts.map +1 -1
  75. package/build/utils/fetchdata.d.ts.map +1 -0
  76. package/build/video/blendmodes.d.ts +107 -0
  77. package/build/video/blendmodes.d.ts.map +1 -0
  78. package/build/video/canvas/canvas_renderer.d.ts +31 -24
  79. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  80. package/build/video/effects/blendEffect.d.ts +57 -0
  81. package/build/video/effects/blendEffect.d.ts.map +1 -0
  82. package/build/video/effects/blur.d.ts +4 -4
  83. package/build/video/effects/blur.d.ts.map +1 -1
  84. package/build/video/effects/chromaticAberration.d.ts +5 -5
  85. package/build/video/effects/chromaticAberration.d.ts.map +1 -1
  86. package/build/video/effects/colorMatrix.d.ts +5 -5
  87. package/build/video/effects/colorMatrix.d.ts.map +1 -1
  88. package/build/video/effects/desaturate.d.ts +5 -5
  89. package/build/video/effects/desaturate.d.ts.map +1 -1
  90. package/build/video/effects/dissolve.d.ts +4 -4
  91. package/build/video/effects/dissolve.d.ts.map +1 -1
  92. package/build/video/effects/dropShadow.d.ts +5 -5
  93. package/build/video/effects/dropShadow.d.ts.map +1 -1
  94. package/build/video/effects/flash.d.ts +4 -4
  95. package/build/video/effects/flash.d.ts.map +1 -1
  96. package/build/video/effects/glow.d.ts +5 -5
  97. package/build/video/effects/glow.d.ts.map +1 -1
  98. package/build/video/effects/hologram.d.ts +4 -4
  99. package/build/video/effects/hologram.d.ts.map +1 -1
  100. package/build/video/effects/invert.d.ts +5 -5
  101. package/build/video/effects/invert.d.ts.map +1 -1
  102. package/build/video/effects/outline.d.ts +6 -6
  103. package/build/video/effects/outline.d.ts.map +1 -1
  104. package/build/video/effects/pixelate.d.ts +4 -4
  105. package/build/video/effects/pixelate.d.ts.map +1 -1
  106. package/build/video/effects/radialGradient.d.ts +2 -2
  107. package/build/video/effects/scanline.d.ts +6 -6
  108. package/build/video/effects/scanline.d.ts.map +1 -1
  109. package/build/video/effects/sepia.d.ts +5 -5
  110. package/build/video/effects/sepia.d.ts.map +1 -1
  111. package/build/video/effects/shadereffect.d.ts +12 -10
  112. package/build/video/effects/shadereffect.d.ts.map +1 -1
  113. package/build/video/effects/shine.d.ts +3 -3
  114. package/build/video/effects/shine.d.ts.map +1 -1
  115. package/build/video/effects/tintPulse.d.ts +4 -4
  116. package/build/video/effects/tintPulse.d.ts.map +1 -1
  117. package/build/video/effects/vignette.d.ts +6 -6
  118. package/build/video/effects/vignette.d.ts.map +1 -1
  119. package/build/video/effects/wave.d.ts +4 -4
  120. package/build/video/effects/wave.d.ts.map +1 -1
  121. package/build/video/renderer.d.ts +3 -4
  122. package/build/video/renderer.d.ts.map +1 -1
  123. package/build/video/rendertarget/canvasrendertarget.d.ts +1 -0
  124. package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
  125. package/build/video/texture/atlas.d.ts +1 -1
  126. package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
  127. package/build/video/webgl/glshader.d.ts +2 -2
  128. package/build/video/webgl/glshader.d.ts.map +1 -1
  129. package/build/video/webgl/utils/program.d.ts.map +1 -1
  130. package/build/video/webgl/webgl_renderer.d.ts +117 -29
  131. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  132. package/build/video/webgpu/batchers/quad_batcher.d.ts +7 -0
  133. package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
  134. package/build/video/webgpu/pipeline/cache.d.ts +0 -9
  135. package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
  136. package/build/video/webgpu/webgpu_renderer.d.ts +101 -6
  137. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  138. package/package.json +6 -10
  139. package/skills/AGENTS.md +67 -0
  140. package/skills/melonjs/SKILL.md +156 -0
  141. package/skills/melonjs-20-migration/SKILL.md +160 -0
  142. package/skills/melonjs-3d/SKILL.md +226 -0
  143. package/skills/melonjs-3d-assets/SKILL.md +239 -0
  144. package/skills/melonjs-audio/SKILL.md +215 -0
  145. package/skills/melonjs-camera-and-drawing/SKILL.md +171 -0
  146. package/skills/melonjs-deployment/SKILL.md +113 -0
  147. package/skills/melonjs-effects-and-shaders/SKILL.md +177 -0
  148. package/skills/melonjs-events/SKILL.md +159 -0
  149. package/skills/melonjs-getting-started/SKILL.md +183 -0
  150. package/skills/melonjs-input/SKILL.md +166 -0
  151. package/skills/melonjs-lighting/SKILL.md +139 -0
  152. package/skills/melonjs-loading-assets/SKILL.md +157 -0
  153. package/skills/melonjs-particles-and-trails/SKILL.md +136 -0
  154. package/skills/melonjs-performance/SKILL.md +178 -0
  155. package/skills/melonjs-physics/SKILL.md +321 -0
  156. package/skills/melonjs-plugins/SKILL.md +191 -0
  157. package/skills/melonjs-renderables/SKILL.md +214 -0
  158. package/skills/melonjs-renderer-backends/SKILL.md +173 -0
  159. package/skills/melonjs-scenes-and-state/SKILL.md +180 -0
  160. package/skills/melonjs-sprites-and-animation/SKILL.md +173 -0
  161. package/skills/melonjs-tilemaps/SKILL.md +193 -0
  162. package/skills/melonjs-ui-and-text/SKILL.md +175 -0
  163. package/build/audio/backend.d.ts.map +0 -1
  164. package/build/loader/parsers/fetchdata.d.ts.map +0 -1
  165. /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