melonjs 20.2.0 → 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 +86 -8
- package/build/application/settings.d.ts +3 -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 +8793 -10171
- 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/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/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/mesh.d.ts +16 -0
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +16 -10
- 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 +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +5 -2
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- 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/texture/atlas.d.ts +1 -1
- package/build/video/webgl/glshader.d.ts +2 -2
- package/build/video/webgl/webgl_renderer.d.ts +2 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +2 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +5 -9
- 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,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-20-migration
|
|
3
|
+
description: "Use this skill when writing melonJS code that may be based on pre-20.x knowledge, when migrating a game from melonJS 19.x or earlier, or when an API you expected does not exist. Covers the mandatory async Application.init(), the WebGL 2 baseline and WebGPU backend, renderable post effects replacing the shader property, and the deprecated exports still present. Triggers on: migration, upgrade, melonJS 19, me.game, me.video.init, renderable.shader, Compositor, CanvasTexture, me.Math, deprecated."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Migrating to melonJS 20.x
|
|
8
|
+
|
|
9
|
+
> What changed in melonJS 20.x, and which older APIs are gone, deprecated, or
|
|
10
|
+
> renamed. Read this before writing melonJS code from memory — much of what is
|
|
11
|
+
> published about melonJS predates 20.0.
|
|
12
|
+
|
|
13
|
+
**Why this skill exists:** most melonJS material in circulation describes 19.x or
|
|
14
|
+
earlier. Code written from that knowledge looks correct and fails at runtime.
|
|
15
|
+
The items below are ordered by how likely they are to be reproduced from memory.
|
|
16
|
+
|
|
17
|
+
## 1. `Application.init()` is mandatory and asynchronous
|
|
18
|
+
|
|
19
|
+
Since 20.0, constructing an `Application` no longer initialises it.
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
// ✗ pre-20.x — the constructor called init(width, height, options) for you
|
|
23
|
+
const app = new Application(800, 600, { parent: "screen" });
|
|
24
|
+
app.world.addChild(sprite); // added, but nothing ever renders
|
|
25
|
+
|
|
26
|
+
// ✓ 20.x
|
|
27
|
+
const app = new Application(800, 600, { parent: "screen" });
|
|
28
|
+
await app.init(); // REQUIRED
|
|
29
|
+
app.world.addChild(sprite);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`init()` is async because acquiring a WebGPU device is async. There is no
|
|
33
|
+
synchronous alternative, and no automatic fallback that papers over the missing
|
|
34
|
+
call. Nothing that then breaks names it: the constructor still builds
|
|
35
|
+
`app.world`, so children can be added and simply never draw — no canvas is
|
|
36
|
+
appended, the app never subscribes to the frame tick, and `app.renderer` /
|
|
37
|
+
`app.viewport` stay `undefined` — while constructing a `Sprite` or `Text` first
|
|
38
|
+
throws a `TypeError` on the unset global `game` instead.
|
|
39
|
+
|
|
40
|
+
The signature changed too: 19.x took `init(width, height, options)`, 20.x takes
|
|
41
|
+
none, and anything passed is ignored (settings come from the constructor).
|
|
42
|
+
|
|
43
|
+
**`video.init(...)` no longer exists.** The `video` module now exports only the
|
|
44
|
+
four renderer constants (`AUTO`, `CANVAS`, `WEBGL`, `WEBGPU`) — there is no
|
|
45
|
+
`init` on it to call. Any code shaped like `me.video.init(800, 600, {...})` is
|
|
46
|
+
from a removed API. Build an `Application` and await `init()`.
|
|
47
|
+
|
|
48
|
+
Note that `import * as me from "melonjs"` still works, so `me.Sprite` style is
|
|
49
|
+
fine. It is the *globals bootstrap* that is gone, not the namespace import.
|
|
50
|
+
|
|
51
|
+
## 2. Post effects replaced the `shader` property
|
|
52
|
+
|
|
53
|
+
`renderable.shader = …` is **deprecated since 19.2.0**. Use the post-effect API:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
// ✗ deprecated
|
|
57
|
+
mySprite.shader = new ShaderEffect(app.renderer, glslBody);
|
|
58
|
+
|
|
59
|
+
// ✓
|
|
60
|
+
mySprite.addPostEffect(new ShaderEffect(app.renderer, glslBody));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Related trap: **`removePostEffect()` destroys the effect** — and so does
|
|
64
|
+
assigning `renderable.shader`, which destroys whatever it replaces. Only an
|
|
65
|
+
effect carrying `shared = true` is spared. To turn one off temporarily, toggle
|
|
66
|
+
`effect.enabled` instead: remove-and-re-add hands you back an object whose GPU
|
|
67
|
+
resources have already been freed.
|
|
68
|
+
|
|
69
|
+
## 3. WebGL 2 is the baseline; WebGPU is the default attempt
|
|
70
|
+
|
|
71
|
+
`video.AUTO` now tries **WebGPU → WebGL 2 → Canvas**. WebGL 1 is gone.
|
|
72
|
+
|
|
73
|
+
Consequences:
|
|
74
|
+
|
|
75
|
+
- Which backend runs is a runtime fact. Do not write code that assumes WebGL.
|
|
76
|
+
- Custom shaders can carry both GLSL and WGSL in one asset, so one effect runs
|
|
77
|
+
on either GPU backend.
|
|
78
|
+
- Features needing a programmable pipeline are inert on Canvas and warn once
|
|
79
|
+
rather than throwing.
|
|
80
|
+
|
|
81
|
+
## 4. Deprecated exports still present
|
|
82
|
+
|
|
83
|
+
These resolve but should not be used in new code:
|
|
84
|
+
|
|
85
|
+
| deprecated | since | use instead |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `CanvasTexture` | 17.1.0 | `CanvasRenderTarget` |
|
|
88
|
+
| `Compositor` | 18.1.0 | `WebGLBatcher` |
|
|
89
|
+
| `PrimitiveCompositor` | 18.1.0 | `PrimitiveBatcher` |
|
|
90
|
+
| `QuadCompositor` | 18.1.0 | `QuadBatcher` |
|
|
91
|
+
| `Math` (capitalised) | 18.0.0 | `math` (lowercase) |
|
|
92
|
+
| `device.requestFullscreen` / `exitFullscreen` | 19.7.0 | `app.requestFullscreen()` / `app.exitFullscreen()` |
|
|
93
|
+
| `renderable.shader = …` | 19.2.0 | `addPostEffect()` |
|
|
94
|
+
| `Entity` | 18.1.0 | `Sprite` / `Renderable` + a `Body` |
|
|
95
|
+
| `loader.onload` / `onProgress` / `onError` | 18.2.0, **removed 20.3** | the `LOADER_*` events, or `preload(assets, onloadcb)` |
|
|
96
|
+
| `response.overlap` / `overlapN` / `overlapV` | — | `response.depth` / `response.normal` |
|
|
97
|
+
| `setLineWidth()` | 17.3.0 | the `lineWidth` property |
|
|
98
|
+
|
|
99
|
+
Two of these deserve emphasis:
|
|
100
|
+
|
|
101
|
+
- **`Entity`** is still exported, so code using it compiles and runs while being
|
|
102
|
+
the wrong shape for 20.x. Compose a `Sprite`/`Renderable` with a `Body`
|
|
103
|
+
instead.
|
|
104
|
+
- **The capitalised `Math`** shadows the global `Math` if imported carelessly.
|
|
105
|
+
`me.Math.random()` from older docs resolves to the deprecated re-export.
|
|
106
|
+
|
|
107
|
+
## 5. The global `game` is not the modern shape
|
|
108
|
+
|
|
109
|
+
`game` is still exported, but since 20.0 it names the most recently
|
|
110
|
+
**initialized** `Application` and is `undefined` until the first `init()`
|
|
111
|
+
resolves — so anything reading it at module scope gets `undefined`. Take the
|
|
112
|
+
`Application` instance as a parameter instead: `Stage#onResetEvent` and
|
|
113
|
+
`onDestroyEvent` are handed it, and any renderable can reach it through
|
|
114
|
+
`parentApp`.
|
|
115
|
+
|
|
116
|
+
## 6. Node and SSR
|
|
117
|
+
|
|
118
|
+
melonJS imports cleanly under Node — useful for isomorphic apps and for tooling.
|
|
119
|
+
It has **no runtime dependencies** as of 20.3. Do not add polyfills for
|
|
120
|
+
`globalThis`, `String.trimStart` or `String.trimEnd`: the published bundle
|
|
121
|
+
targets ES2022, so every environment that can parse it already has them.
|
|
122
|
+
|
|
123
|
+
## 7. Browser support
|
|
124
|
+
|
|
125
|
+
The published bundle targets **ES2022** and uses private class members. A browser
|
|
126
|
+
that predates that cannot parse the file at all — it fails at load, not when a
|
|
127
|
+
feature is used. Transpile if you need older browsers, and note that transpiling
|
|
128
|
+
handles *syntax* only: built-in methods need separate polyfills, which a
|
|
129
|
+
bundler's `target` setting will never add.
|
|
130
|
+
|
|
131
|
+
## The shipped examples are not all current
|
|
132
|
+
|
|
133
|
+
The examples in this repository are the best reference for how melonJS is
|
|
134
|
+
assembled, but a few contain stale comments or legacy calls. Do not learn these
|
|
135
|
+
from them:
|
|
136
|
+
|
|
137
|
+
- `webgpu/` has a comment claiming *"`video.AUTO` never selects the WebGPU
|
|
138
|
+
backend"*. That is pre-20 leftover — AUTO tries WebGPU **first**. The code is
|
|
139
|
+
right; the comment is wrong.
|
|
140
|
+
- Several examples assign `viewport.shader = …` / `sprite.shader = …`, which is
|
|
141
|
+
deprecated. `addPostEffect()` is current.
|
|
142
|
+
- Five files set `this.z = Number.POSITIVE_INFINITY` on a HUD. That property
|
|
143
|
+
does not exist; those HUDs draw on top only because they are added last.
|
|
144
|
+
- `platformer/entities/player.ts` reads `response.overlapV`, the deprecated
|
|
145
|
+
field. Use `response.depth` / `response.normal`.
|
|
146
|
+
- Particle speeds tuned before 20.2 look wrong now: the particle transform fix
|
|
147
|
+
changed how far a burst travels, and emitters were retuned upward to match.
|
|
148
|
+
|
|
149
|
+
## Checklist when code "should work" but doesn't
|
|
150
|
+
|
|
151
|
+
1. Is `await app.init()` there?
|
|
152
|
+
2. Is the code assuming WebGL when it might be running on Canvas or WebGPU?
|
|
153
|
+
3. Is it using `renderable.shader =` instead of `addPostEffect()`?
|
|
154
|
+
4. Is it importing a deprecated symbol from the table above?
|
|
155
|
+
5. Is it referencing a global `game`?
|
|
156
|
+
|
|
157
|
+
## Related skills
|
|
158
|
+
|
|
159
|
+
- `melonjs-getting-started` — the correct 20.x bootstrap in full
|
|
160
|
+
- `melonjs-renderables` — post effects, pointer events and custom draw code
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-3d
|
|
3
|
+
description: "Use this skill for anything 3D or 2.5D in melonJS — Camera3d, Mesh, InstancedMesh, Sprite3d billboards, Light3d, ground shadows, glTF/GLB scenes, and depth sorting. Covers the Y-down/+Z-forward convention that is the inverse of OpenGL, the cameraClass opt-in, clip planes, and what does not work on the Canvas fallback. Triggers on: Camera3d, Mesh, InstancedMesh, Sprite3d, Light3d, billboard, glTF, glb, 3D, 2.5D, depth, cameraClass, fov, setClipPlanes, castGroundShadow, lit."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 3D and 2.5D in melonJS
|
|
8
|
+
|
|
9
|
+
> melonJS is a 2D engine that grew a real 3D tier: perspective cameras, meshes,
|
|
10
|
+
> instancing, glTF scenes and 3D lights. Most of what a model "knows" about 3D
|
|
11
|
+
> graphics comes from the OpenGL convention, and **melonJS's is the inverse**.
|
|
12
|
+
> Read the conventions section before writing any 3D code.
|
|
13
|
+
|
|
14
|
+
## Coordinate conventions — the inverse of OpenGL
|
|
15
|
+
|
|
16
|
+
This is the single most important thing on this page.
|
|
17
|
+
|
|
18
|
+
| | melonJS | OpenGL convention |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| vertical | **Y-down** — higher `y` is *lower* on screen | Y-up |
|
|
21
|
+
| depth | **+Z forward** — higher `z` is *farther* away | −Z forward |
|
|
22
|
+
|
|
23
|
+
Rotations are extrinsic XYZ. `Camera3d` exposes two of the three: `camera.pitch`
|
|
24
|
+
(X, look up/down) and `camera.yaw` (Y, look left/right). **There is no `roll`**;
|
|
25
|
+
neither camera applies a Z-axis bank to the view, so assigning one does nothing.
|
|
26
|
+
|
|
27
|
+
The payoff is that 2D code translates directly — anywhere you used `pos.x` /
|
|
28
|
+
`pos.y`, add `pos.z` and the maths still holds. The cost is that every
|
|
29
|
+
OpenGL-shaped instinct is backwards.
|
|
30
|
+
|
|
31
|
+
**Consequence that bites immediately:** "behind everything" is the *largest*
|
|
32
|
+
depth when the camera looks along +Z. Put a backdrop on the wrong side and it
|
|
33
|
+
either paints over the whole scene (it is nearer than everything else) or
|
|
34
|
+
disappears without trace (it falls in front of the near plane and the GPU clips
|
|
35
|
+
it). Which sign means "far" depends on which side the camera sits, so check the
|
|
36
|
+
camera before picking a backdrop depth.
|
|
37
|
+
|
|
38
|
+
glTF assets are authored Y-up right-handed. `level.load` converts them by
|
|
39
|
+
rotation (`rightHanded: true` is its default), so you do not apply a flip
|
|
40
|
+
yourself. The **`Mesh` constructor defaults `rightHanded` to `false`**, which
|
|
41
|
+
bridges by mirroring instead — geometry you hand to a `Mesh` directly comes in
|
|
42
|
+
flipped left/right unless you pass `rightHanded: true`. See
|
|
43
|
+
`melonjs-3d-assets`.
|
|
44
|
+
|
|
45
|
+
## Opting in
|
|
46
|
+
|
|
47
|
+
3D starts with the camera class, and nothing else has to change. Set it once on
|
|
48
|
+
the `Application`:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { Application, Camera3d } from "melonjs";
|
|
52
|
+
|
|
53
|
+
const app = new Application(1024, 768, {
|
|
54
|
+
parent: "screen",
|
|
55
|
+
cameraClass: Camera3d, // ← the opt-in
|
|
56
|
+
});
|
|
57
|
+
await app.init();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That flips the world's sort to `"depth"`, switches the broadphase to a 3D index,
|
|
61
|
+
and renders sprites and meshes with perspective projection. `Camera3d` extends
|
|
62
|
+
`Camera2d`, so follow, fade, shake and post effects all still work.
|
|
63
|
+
|
|
64
|
+
Per-stage opt-in also works, which is useful when the loading screen is 2D:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
class GameStage extends Stage {
|
|
68
|
+
constructor() {
|
|
69
|
+
super({ cameras: [new Camera3d(0, 0, 1024, 768, { fov: Math.PI / 3 })] });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Camera traps
|
|
75
|
+
|
|
76
|
+
- **There is no sky.** The frame clears to `renderer.backgroundColor` (opaque
|
|
77
|
+
black unless the renderer was created transparent), and `world.backgroundColor`
|
|
78
|
+
clears over it when you set one — both are flat colours. Anything more is a
|
|
79
|
+
backdrop renderable, screen-fixed or placed at a far depth.
|
|
80
|
+
- **Set the clip planes for your scene scale.** Defaults are near 0.1 / far 1000.
|
|
81
|
+
Objects beyond the far plane clip or project with bad w-divides; a `near` that
|
|
82
|
+
is too small wastes depth precision and distant props z-fight. Use
|
|
83
|
+
`camera.setClipPlanes(near, far)`.
|
|
84
|
+
- **`camera.pos.set(x, y)` is 2-argument and zeroes z.** Use `camera.depth` —
|
|
85
|
+
the documented z accessor — or assign `pos.x`/`pos.y` individually.
|
|
86
|
+
|
|
87
|
+
## Depth sorting
|
|
88
|
+
|
|
89
|
+
Under `Camera3d` the world sorts on `"depth"` **every frame** (recursively), so
|
|
90
|
+
unlike 2D you *can* assign `depth` after `addChild` and it will reorder. That is
|
|
91
|
+
the one place where the usual z-ordering rule is relaxed — see
|
|
92
|
+
`melonjs-renderables` for the 2D rules, which still apply everywhere else.
|
|
93
|
+
|
|
94
|
+
You will often *have* to. A `Container` has `autoDepth: true` by default, so
|
|
95
|
+
`addChild(child)` with no explicit z **overwrites `pos.z` with the child's
|
|
96
|
+
index** — a real world depth in a 3D scene, and never the one you wanted. Pass
|
|
97
|
+
it (`world.addChild(mesh, z)`), or set `mesh.depth` afterwards. The glTF
|
|
98
|
+
importer turns `autoDepth` off on the container it loads into for this reason.
|
|
99
|
+
|
|
100
|
+
## Meshes
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const cube = new Mesh(x, y, {
|
|
104
|
+
model: "cube", // a preloaded OBJ name — no built-in primitives
|
|
105
|
+
texture: atlas, // TextureAtlas, image, or a preloaded image name
|
|
106
|
+
width: 64, height: 64,
|
|
107
|
+
cullBackFaces: true, // the default
|
|
108
|
+
lit: true,
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**A mesh pivots about its model origin `(0, 0, 0)`, not an anchor point.** Under
|
|
113
|
+
`Camera3d` it opts out of the anchor offset entirely, so `anchorPoint` is inert
|
|
114
|
+
there — place the origin where you want the pivot at authoring time, or nest the
|
|
115
|
+
mesh under a transformed parent. The anchor is only honoured on the legacy
|
|
116
|
+
2D-camera path.
|
|
117
|
+
|
|
118
|
+
**`InstancedMesh`** draws one mesh many times in a single draw call — the
|
|
119
|
+
difference between a hundred trees and a hundred thousand. glTF scenes using
|
|
120
|
+
`EXT_mesh_gpu_instancing` load as an `InstancedMesh` automatically.
|
|
121
|
+
|
|
122
|
+
## Sprite3d and billboards
|
|
123
|
+
|
|
124
|
+
`Sprite3d` is the 2.5D workhorse: a flat sprite living at a real depth, with
|
|
125
|
+
`billboard` controlling how it faces the camera.
|
|
126
|
+
|
|
127
|
+
| `billboard` | behaviour |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `false` (default) / `"none"` | no rotation — a flat plane in the world |
|
|
130
|
+
| `true` / `"cylindrical"` | yaws to face the camera, stays upright (characters, trees) |
|
|
131
|
+
| `"spherical"` | always fully faces the camera (particles, impostors) |
|
|
132
|
+
|
|
133
|
+
`"cylindrical"` is what you want for paper-thin characters in a 2.5D game.
|
|
134
|
+
Billboarding needs a `Camera3d` drawing the frame; under a 2D camera the quad
|
|
135
|
+
renders fixed-orientation. **Any other string falls through to the spherical
|
|
136
|
+
branch**, so a guessed value like `"upright"` silently gives you spherical.
|
|
137
|
+
|
|
138
|
+
## Lighting
|
|
139
|
+
|
|
140
|
+
`Light3d` takes **one argument — the options object** (it has no `x, y, z`
|
|
141
|
+
pair; a light carries its own `position`). Types are `"directional"` (the
|
|
142
|
+
default), `"ambient"`, `"point"` and `"spot"`.
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
world.addChild(new Light3d({ type: "directional", direction: [0.3, 1, 0.2] }));
|
|
146
|
+
world.addChild(new Light3d({ type: "ambient", intensity: 0.3 }));
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use **both halves**: with a key light but no ambient, the shadow side of a mesh
|
|
150
|
+
goes black. With no `Light3d` in the world at all, a `lit: true` mesh falls back
|
|
151
|
+
to a white ambient and renders fullbright — indistinguishable from unlit, which
|
|
152
|
+
is why "I added `lit: true` and nothing changed" is the usual first report.
|
|
153
|
+
|
|
154
|
+
Other defaults worth knowing: `intensity` 1, `color` white, `range` 1000 (point
|
|
155
|
+
and spot, a stylised quadratic falloff, not inverse-square),
|
|
156
|
+
`innerConeAngle` 0 and `outerConeAngle` π/4 (spot).
|
|
157
|
+
|
|
158
|
+
**`Light2d` is 2D-only** and produces visible artifacts under perspective
|
|
159
|
+
projection. Do not combine it with `Camera3d`.
|
|
160
|
+
|
|
161
|
+
Ground shadows are **on by default** (the `castGroundShadow` application
|
|
162
|
+
setting), and need a GPU backend and a `Camera3d`. As a blanket default they
|
|
163
|
+
skip geometry with no vertical extent — a ground plane. Per object,
|
|
164
|
+
`castGroundShadow: true`/`false` overrides the app setting and is obeyed as
|
|
165
|
+
given, safeguard included; `shadowGroundY` names the floor the blob lands on.
|
|
166
|
+
|
|
167
|
+
## glTF / GLB scenes
|
|
168
|
+
|
|
169
|
+
Loaded through the same level director as everything else:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
await loader.preload([{ name: "diorama", type: "glb", src: "data/diorama.glb" }]);
|
|
173
|
+
level.load("diorama", { scale, castGroundShadow, shadowGroundY, onLoaded });
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`.obj` / `.mtl` are also supported loader types.
|
|
177
|
+
|
|
178
|
+
`melonjs-3d-assets` covers the rest: every `level.load` option, which material
|
|
179
|
+
features are imported, imported lights and their intensity units, node-TRS
|
|
180
|
+
animation (and the skinning that is out of scope), and OBJ/MTL.
|
|
181
|
+
|
|
182
|
+
## Requires a GPU backend
|
|
183
|
+
|
|
184
|
+
The whole 3D tier needs WebGPU or WebGL 2. The Canvas renderer has no depth
|
|
185
|
+
buffer, no perspective path and no `drawMesh`, so a `Camera3d` scene does not
|
|
186
|
+
render correctly there — usually a black canvas. `Light3d` and shader effects
|
|
187
|
+
are inert too. A `Mesh` under a *2D* camera still renders, CPU-projected by
|
|
188
|
+
painter's algorithm and unlit.
|
|
189
|
+
|
|
190
|
+
`Application` does warn: constructing with a `cameraClass` whose
|
|
191
|
+
`defaultSortOn` is `"depth"` on a renderer with no depth buffer logs a
|
|
192
|
+
`console.warn`. But `video.AUTO` falls back silently to Canvas, so the strong
|
|
193
|
+
gate is to ask for a GPU backend by name — `renderer: video.WEBGL` (or
|
|
194
|
+
`video.WEBGPU`) makes `await app.init()` reject instead of misrendering:
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
const app = new Application(1024, 768, {
|
|
198
|
+
parent: "screen",
|
|
199
|
+
renderer: video.WEBGL, // init() rejects if WebGL 2 is unavailable
|
|
200
|
+
cameraClass: Camera3d,
|
|
201
|
+
});
|
|
202
|
+
await app.init();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
|
|
206
|
+
`init()`.
|
|
207
|
+
|
|
208
|
+
## Symptom → cause
|
|
209
|
+
|
|
210
|
+
| symptom | cause |
|
|
211
|
+
|---|---|
|
|
212
|
+
| nothing renders, or a backdrop covers everything | wrong depth sign — "far" is *larger* z when looking along +Z |
|
|
213
|
+
| distant objects vanish or warp | scene exceeds the default far plane; `setClipPlanes` |
|
|
214
|
+
| distant surfaces z-fight | `near` too small for the scene scale |
|
|
215
|
+
| black canvas under `Camera3d` | Canvas renderer (no depth buffer) — check the `console.warn` |
|
|
216
|
+
| everything flat and unlit | `lit: true` with no `Light3d` in the world (falls back to fullbright), or a mesh under a 2D camera |
|
|
217
|
+
| a mesh sits at the wrong depth after being added | `autoDepth` overwrote `pos.z` with the child index — pass `addChild(mesh, z)` |
|
|
218
|
+
| a mesh sits half its size off | `anchorPoint` — only on the 2D-camera path; a `Camera3d` mesh pivots on its model origin |
|
|
219
|
+
| a billboard tips over when the camera looks down | `"spherical"`, or a mistyped mode string falling through to it — use `true` / `"cylindrical"` |
|
|
220
|
+
| an object jumps to the camera plane | `Vector3d.set(x, y)` / `camera.pos.set(x, y)` zeroed its z |
|
|
221
|
+
| lighting looks wrong under a 3D camera | `Light2d` used instead of `Light3d` |
|
|
222
|
+
|
|
223
|
+
## Related skills
|
|
224
|
+
|
|
225
|
+
- `melonjs-renderables` — the 2D z-ordering rules, anchors, custom draw
|
|
226
|
+
- `melonjs-getting-started` — Application settings and the renderer fallback
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: melonjs-3d-assets
|
|
3
|
+
description: "Use this skill when loading 3D models into melonJS — glTF and GLB scenes, OBJ/MTL models, materials, imported lights, node animation, ground shadows and GPU instancing. Covers level.load options, the rightHanded conversion, and exactly what the loader does and does not support. Triggers on: glTF, gltf, glb, OBJ, MTL, 3D model, getGLTF, getOBJ, getMTL, GLTFModel, GLTFScene, level.load glb, rightHanded, lightIntensityScale, castGroundShadow, shadowGroundY, EXT_mesh_gpu_instancing, KHR_lights_punctual, skinning, Blender export, 3D asset."
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Loading 3D assets
|
|
8
|
+
|
|
9
|
+
> glTF/GLB is the primary path for 3D content, and it goes through the **level
|
|
10
|
+
> director** rather than the world directly: `level.load("scene")`, the same call
|
|
11
|
+
> a Tiled map uses. OBJ/MTL is the simpler path for a single static model.
|
|
12
|
+
|
|
13
|
+
## Loading a glTF/GLB scene
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
await loader.preload([
|
|
17
|
+
{ name: "diorama", type: "glb", src: "data/scenes/diorama.glb" },
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
level.load("diorama", { scale: 50 });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`type: "gltf"` (JSON plus external buffers) and `type: "glb"` (single binary)
|
|
24
|
+
are both loader types. A self-contained GLB is the better shipping format — one
|
|
25
|
+
request, nothing to resolve relatively.
|
|
26
|
+
|
|
27
|
+
The scene loads under whatever camera the stage has; view it under a `Camera3d`
|
|
28
|
+
or it renders flat. See `melonjs-3d` for the camera.
|
|
29
|
+
|
|
30
|
+
### `level.load` options for glTF/GLB
|
|
31
|
+
|
|
32
|
+
| option | default | what it does |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `scale` | `1` | pixels per glTF unit, applied to the whole scene. Blender's metre-scale export usually needs 20–100. |
|
|
35
|
+
| `container` | `game.world` | where the nodes are added |
|
|
36
|
+
| `onLoaded` | `app.onLevelLoaded` | called with the **level id**, not the scene |
|
|
37
|
+
| `rightHanded` | `true` | see below |
|
|
38
|
+
| `lights` | `true` | instantiate authored `KHR_lights_punctual` lights as `Light3d` world children |
|
|
39
|
+
| `lightIntensityScale` | — | keep authored intensity ratios instead of normalising every light to 1 |
|
|
40
|
+
| `castGroundShadow` | inherits the app setting (**on**) | blob shadows for this scene's meshes |
|
|
41
|
+
| `shadowGroundY` | each object's own base | world Y of the floor the blobs land on |
|
|
42
|
+
|
|
43
|
+
`onLoaded` receives the level id — it is a "done" signal, not a handle on the
|
|
44
|
+
scene. You need it: with the game loop running, `level.load` stops the loop and
|
|
45
|
+
defers the actual load to the next tick, so it returns *before* anything is in
|
|
46
|
+
the world. To get at what was loaded, load into a container you own, or look the
|
|
47
|
+
nodes up by their authored names:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
level.load("diorama", { scale: 50, onLoaded: () => {
|
|
51
|
+
const sun = app.world.getChildByName("Sun")[0]; // note: returns an array
|
|
52
|
+
sun.intensity = 2;
|
|
53
|
+
}});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## `rightHanded` — and the default that differs between the two APIs
|
|
57
|
+
|
|
58
|
+
glTF is authored **Y-up, right-handed**; melonJS is **Y-down, +Z forward**.
|
|
59
|
+
There are two ways to bridge that, and they are not equivalent:
|
|
60
|
+
|
|
61
|
+
- a **rotation** — preserves chirality, matches the authoring tool
|
|
62
|
+
- a **mirror** (negate Y only) — flips the scene left/right
|
|
63
|
+
|
|
64
|
+
`level.load` defaults `rightHanded: true`, so a scene loaded through the level
|
|
65
|
+
director comes in correctly. **The `Mesh` constructor defaults it to `false`**,
|
|
66
|
+
so glTF or OBJ geometry you feed to a `Mesh` yourself comes in mirrored unless
|
|
67
|
+
you pass `rightHanded: true`. A model that is subtly wrong — text backwards, a
|
|
68
|
+
character's gear on the wrong side — is this and nothing else.
|
|
69
|
+
|
|
70
|
+
Do not add a flip of your own on top; two corrections cancel back to wrong.
|
|
71
|
+
|
|
72
|
+
## Materials — what actually arrives
|
|
73
|
+
|
|
74
|
+
The importer covers a deliberate subset:
|
|
75
|
+
|
|
76
|
+
- `pbrMetallicRoughness` **base colour texture and factor**
|
|
77
|
+
- metallic/roughness **factors** converted to the engine's specular model
|
|
78
|
+
- `emissiveFactor`, including `KHR_materials_emissive_strength`
|
|
79
|
+
- `KHR_materials_unlit` — honoured, for baked/stylised workflows
|
|
80
|
+
- `alphaMode: "MASK"` — the cutout threshold is applied
|
|
81
|
+
- `doubleSided`
|
|
82
|
+
|
|
83
|
+
Not imported: **normal maps**, metallic/roughness *textures*, occlusion maps,
|
|
84
|
+
morph targets, `KHR_texture_transform`. Nothing errors — the material simply
|
|
85
|
+
arrives without them, which reads as "flatter than the preview in my authoring
|
|
86
|
+
tool". Bake what you need into the base colour.
|
|
87
|
+
|
|
88
|
+
**Draco compression is the exception that does error.** Its geometry sits in
|
|
89
|
+
accessors with no buffer view, which the parser rejects outright, so the whole
|
|
90
|
+
asset fails to load. Export uncompressed.
|
|
91
|
+
|
|
92
|
+
`textureFilter` on the Application settings (`"nearest"` / `"linear"` /
|
|
93
|
+
`"auto"`) drives sampling; glTF materials that declare their own sampler carry
|
|
94
|
+
it through. Pixel-art models want `"nearest"`.
|
|
95
|
+
|
|
96
|
+
## Lights
|
|
97
|
+
|
|
98
|
+
Authored `KHR_lights_punctual` lights — sun, point, spot — become `Light3d`
|
|
99
|
+
world children, plus a soft ambient fill, and each keeps its authored name.
|
|
100
|
+
A scene carrying any of them renders through the **lit** path automatically.
|
|
101
|
+
|
|
102
|
+
glTF stores physical units — lux for suns, candela for lamps — and a Blender
|
|
103
|
+
daylight sun is 1000+, which blows out the engine's stylised shading. So every
|
|
104
|
+
light is normalised to intensity 1 by default. To keep the *ratios* the artist
|
|
105
|
+
set, scale instead of normalising:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
// a 1000-lux sun lands at 1, a 500-lux fill lands at 0.5
|
|
109
|
+
level.load("diorama", { scale: 50, lightIntensityScale: 0.001 });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Pass `lights: false` to manage lighting yourself.
|
|
113
|
+
|
|
114
|
+
## Animation
|
|
115
|
+
|
|
116
|
+
Node-TRS animation **is** supported: translation, rotation and scale channels,
|
|
117
|
+
the rigid hierarchical animation used by blocky/low-poly character packs. An
|
|
118
|
+
animated asset instantiates as a single `GLTFModel` keeping the node hierarchy
|
|
119
|
+
intact, with an API deliberately mirroring `Sprite`. The model carries the asset
|
|
120
|
+
name, which is how you get hold of it after loading:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
const model = app.world.getChildByName("hero")[0]; // the GLTFModel
|
|
124
|
+
|
|
125
|
+
model.getAnimationNames(); // ["idle", "walk", ...]
|
|
126
|
+
model.setCurrentAnimation("walk", { loop: true, speed: 1, onComplete });
|
|
127
|
+
model.setCurrentAnimation("jump", { next: "idle" }); // chain
|
|
128
|
+
model.isCurrentAnimation("walk");
|
|
129
|
+
model.play();
|
|
130
|
+
model.pause();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`speed` is a playback multiplier (1 = authored speed), not a frame delay. Only
|
|
134
|
+
an explicit `loop: false` stops looping. `setCurrentAnimation` **throws** on a
|
|
135
|
+
clip name the asset does not define — check `getAnimationNames()` first.
|
|
136
|
+
|
|
137
|
+
**Vertex skinning is out of scope** — `JOINTS_0` / `WEIGHTS_0` are not read, and
|
|
138
|
+
neither are morph targets. A smoothly-deforming character will not deform. The
|
|
139
|
+
options are: rig it hierarchically instead (separate meshes parented into a
|
|
140
|
+
node tree), use `Sprite3d` billboards, or use the Spine plugin for 2D skeletal
|
|
141
|
+
work.
|
|
142
|
+
|
|
143
|
+
## Instancing
|
|
144
|
+
|
|
145
|
+
A node using `EXT_mesh_gpu_instancing` loads as an `InstancedMesh` — one draw
|
|
146
|
+
call for every copy, which is the difference between a hundred trees and a
|
|
147
|
+
hundred thousand. Export with instancing enabled where your tool supports it.
|
|
148
|
+
Sparse accessors on the instancing attributes are rejected with a clear error
|
|
149
|
+
rather than silently misplacing every instance.
|
|
150
|
+
|
|
151
|
+
## Ground shadows
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
level.load("diorama", { scale: 50, castGroundShadow: true, shadowGroundY: 0 });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`castGroundShadow` is tri-state: set it and it overrides the application setting
|
|
158
|
+
for this scene in both directions; omit it and the scene inherits — and that
|
|
159
|
+
setting is `true` by default, so shadows are already on. As a scene-wide opt-in
|
|
160
|
+
it skips nodes with no vertical extent — a ground plane is exactly that, and
|
|
161
|
+
shadowing it with itself smears a blob across the whole floor. Blob shadows need
|
|
162
|
+
a GPU backend and a `Camera3d`; nothing is drawn on the 2D-camera or Canvas
|
|
163
|
+
paths.
|
|
164
|
+
|
|
165
|
+
Omit `shadowGroundY` and each blob sits at its own object's base at full
|
|
166
|
+
strength, which is right for props already resting on the ground. Set it when
|
|
167
|
+
things jump or fly, so the shadow stays on the floor and shrinks with height.
|
|
168
|
+
|
|
169
|
+
## OBJ/MTL
|
|
170
|
+
|
|
171
|
+
A different shape entirely: OBJ produces raw geometry you hand to a `Mesh`,
|
|
172
|
+
not a scene. Preload the two files and name them in the `Mesh` settings — the
|
|
173
|
+
`model` and `material` names are independent, the same name for both is just a
|
|
174
|
+
convention:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
await loader.preload([
|
|
178
|
+
{ name: "fox", type: "obj", src: "models/fox.obj" },
|
|
179
|
+
{ name: "fox", type: "mtl", src: "models/fox.mtl" },
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
const mesh = new Mesh(400, 300, {
|
|
183
|
+
model: "fox",
|
|
184
|
+
material: "fox", // texture resolved from the material's map_Kd
|
|
185
|
+
width: 200,
|
|
186
|
+
height: 200,
|
|
187
|
+
rightHanded: true, // the Mesh default is false — see above
|
|
188
|
+
});
|
|
189
|
+
world.addChild(mesh);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**The MTL loader fetches the textures it references itself** — `map_Kd` and
|
|
193
|
+
`map_d` are resolved relative to the `.mtl` and preloaded for you, so there is
|
|
194
|
+
no image entry in the manifest above. Passing `texture:` explicitly overrides
|
|
195
|
+
that and pins *one* binding over the whole model, which on a multi-material
|
|
196
|
+
model suppresses the per-material texture split; leave it out unless you mean
|
|
197
|
+
that.
|
|
198
|
+
|
|
199
|
+
MTL contributes `Kd` (diffuse tint), `d` / `Tr` (opacity), `Ke` (emissive),
|
|
200
|
+
`Ks` + `Ns` (specular highlight), `map_Kd` (diffuse texture) and `map_d`
|
|
201
|
+
(per-texel opacity). Normal and specular *maps* are not supported.
|
|
202
|
+
`loader.getOBJ(name)` and `loader.getMTL(name)` return the parsed data if you
|
|
203
|
+
want it directly. OBJ has no scene graph, no lights, no animation — use glTF for
|
|
204
|
+
anything beyond a single static model.
|
|
205
|
+
|
|
206
|
+
## Packaging
|
|
207
|
+
|
|
208
|
+
A self-contained GLB is one file with nothing to resolve — the safest thing to
|
|
209
|
+
ship. External references still work in both formats: `.bin` buffers and image
|
|
210
|
+
`uri`s are resolved **relative to the asset's own URL** (a GLB with an external
|
|
211
|
+
texture loads as-shipped). But an external file that 404s rejects the parse, so
|
|
212
|
+
the whole asset fails to load rather than arriving partially — no untextured
|
|
213
|
+
fallback. Note that `loader.setBaseURL("*", …)` deliberately skips `gltf`,
|
|
214
|
+
`glb`, `obj` and `mtl` for this reason; set those types individually if you
|
|
215
|
+
need a prefix.
|
|
216
|
+
|
|
217
|
+
## Symptom → cause
|
|
218
|
+
|
|
219
|
+
| symptom | cause |
|
|
220
|
+
|---|---|
|
|
221
|
+
| model mirrored — text backwards, gear on the wrong side | `Mesh` defaults `rightHanded` to `false`; pass `true` |
|
|
222
|
+
| model upside down | a manual axis flip on top of the loader's conversion |
|
|
223
|
+
| scene is enormous or invisibly small | `scale` left at 1 against a metre-scale export |
|
|
224
|
+
| the asset fails to load entirely | an external buffer/image `uri` that 404s, or Draco-compressed geometry |
|
|
225
|
+
| everything is blown out white | authored lux/candela intensities; use `lightIntensityScale` |
|
|
226
|
+
| materials flatter than the authoring-tool preview | no normal or metallic/roughness *textures* are imported |
|
|
227
|
+
| a character does not deform | vertex skinning is out of scope; rig hierarchically or billboard |
|
|
228
|
+
| animation names come back empty | the asset has no node-TRS channels (skin-only rig) |
|
|
229
|
+
| a hundred copies tank the frame rate | exported without `EXT_mesh_gpu_instancing` |
|
|
230
|
+
| a shadow smeared across the whole floor | a ground plane cast its own blob — use the scene-wide opt-in |
|
|
231
|
+
| `onLoaded` gets a string, not the scene | it is called with the level id; load into your own container instead |
|
|
232
|
+
| scene renders flat and unlit | no `Camera3d` — the 2D-camera path is CPU-projected and unlit (with a `Camera3d` on Canvas you get a black canvas instead) |
|
|
233
|
+
|
|
234
|
+
## Related skills
|
|
235
|
+
|
|
236
|
+
- `melonjs-3d` — conventions, `Camera3d`, meshes, `Light3d`, instancing
|
|
237
|
+
- `melonjs-lighting` — how `Light3d` behaves once imported
|
|
238
|
+
- `melonjs-loading-assets` — the loader, asset types and base URLs
|
|
239
|
+
- `melonjs-plugins` — the Spine plugin, for skeletal characters
|