From 8a398cec051a9b1a3d2ec939800fa0a5061af62e Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Mon, 7 Sep 2026 07:25:26 +0800 Subject: [PATCH] Examples: the 3D examples await their level load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three 3D examples that go through the level director — forest, glTF scene and glTF character — now use the `async` option from #1647 instead of the `onLoaded` callback, so the setup that follows the load reads as ordinary sequential code. `loader.preload` deliberately keeps its callback form. Awaiting it would delay returning the teardown function, and the forest example needs that cleanup to exist while its 3 MB glb is still loading. Also corrects a JSDoc default that shipped in the published types: `LevelLoadOptions.castGroundShadow` was documented as defaulting to `false`, but the option is tri-state — omitting it means "inherit the application setting", which is on by default. The three sibling declarations (Mesh, GLTFScene, GLTFModel) already said so. The emitted type is unchanged; only the prose was wrong, in the direction that makes someone set the flag to `true` believing it is off. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N --- .../examples/src/examples/forest/ExampleForest.tsx | 10 +++++++--- packages/examples/src/examples/gltf/ExampleGltf.tsx | 10 +++++++--- .../src/examples/gltf/ExampleGltfCharacter.tsx | 8 ++++++-- packages/melonjs/src/level/level.js | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/examples/src/examples/forest/ExampleForest.tsx b/packages/examples/src/examples/forest/ExampleForest.tsx index 7145cd143..b3d005790 100644 --- a/packages/examples/src/examples/forest/ExampleForest.tsx +++ b/packages/examples/src/examples/forest/ExampleForest.tsx @@ -234,7 +234,7 @@ const createGame = async () => { loader.preload( [{ name: "forest", type: "glb", src: `${base}forest.glb` }], - () => { + async () => { state.change(state.DEFAULT, true); // one call — the instanced node becomes an InstancedMesh, the // ground stays an ordinary Mesh, and the authored sun lights both @@ -243,12 +243,16 @@ const createGame = async () => { // read from the same instance buffer the trees draw from. The // scene's ground plane is skipped automatically — it has no height // to cast, and shadowing it with itself would smear the floor. - level.load("forest", { + // `async: true` hands back a promise that settles once the scene is + // actually in the world, so the setup below reads as ordinary + // sequential code rather than a callback + await level.load("forest", { scale: SCALE, castGroundShadow: true, shadowGroundY: GROUND_Y, - onLoaded: setupScene, + async: true, }); + setupScene(); }, ); diff --git a/packages/examples/src/examples/gltf/ExampleGltf.tsx b/packages/examples/src/examples/gltf/ExampleGltf.tsx index cf1375069..a648fd73e 100644 --- a/packages/examples/src/examples/gltf/ExampleGltf.tsx +++ b/packages/examples/src/examples/gltf/ExampleGltf.tsx @@ -318,7 +318,7 @@ const createGame = async () => { loader.preload( [{ name: "diorama", type: "glb", src: `${base}platformer-diorama.glb` }], - () => { + async () => { state.change(state.DEFAULT, true); // load the whole glTF scene into the world in one call — the glb // auto-registered with the level director on preload, exactly like @@ -329,11 +329,15 @@ const createGame = async () => { // heights rather than on one floor. The scene's ground/platform // meshes are skipped automatically: they have no height to cast // from, and shadowing them with themselves would smear the terrain. - level.load("diorama", { + // `async: true` hands back a promise that settles once the scene is + // actually in the world, so the setup below reads as ordinary + // sequential code rather than a callback + await level.load("diorama", { scale: SCALE, castGroundShadow: true, - onLoaded: setupScene, + async: true, }); + setupScene(); }, ); diff --git a/packages/examples/src/examples/gltf/ExampleGltfCharacter.tsx b/packages/examples/src/examples/gltf/ExampleGltfCharacter.tsx index 401c9cb8b..daf8b4ef6 100644 --- a/packages/examples/src/examples/gltf/ExampleGltfCharacter.tsx +++ b/packages/examples/src/examples/gltf/ExampleGltfCharacter.tsx @@ -265,9 +265,13 @@ const createGame = async () => { // the GLB references an external texture (Textures/texture-a.png), // resolved relative to the asset URL by the loader — no repackaging. [{ name: "character", type: "glb", src: `${base}character.glb` }], - () => { + async () => { state.change(state.DEFAULT, true); - level.load("character", { scale: SCALE, onLoaded: setupScene }); + // `async: true` hands back a promise that settles once the scene is + // actually in the world, so the setup below reads as ordinary + // sequential code rather than a callback + await level.load("character", { scale: SCALE, async: true }); + setupScene(); }, ); diff --git a/packages/melonjs/src/level/level.js b/packages/melonjs/src/level/level.js index 2bb7a84c0..a33cfa2d5 100644 --- a/packages/melonjs/src/level/level.js +++ b/packages/melonjs/src/level/level.js @@ -122,7 +122,7 @@ function levelIdAt(offset) { * @property {boolean} [rightHanded=true] - (glTF/GLB only) convert the right-handed (Y-up) source to the engine's Y-down via a rotation rather than a mirror * @property {boolean} [lights=true] - (glTF/GLB only) add the scene's authored `KHR_lights_punctual` lights (plus a soft ambient fill) as {@link Light3d} world children * @property {number} [lightIntensityScale] - (glTF/GLB only) multiply each light's authored physical intensity by this factor instead of normalizing it to 1 - * @property {boolean} [castGroundShadow=false] - (glTF/GLB only) give every mesh in the scene a ground shadow + * @property {boolean} [castGroundShadow] - (glTF/GLB only) give every mesh in the scene a ground shadow; omit to inherit the application's `castGroundShadow` setting (on by default) * @property {number} [shadowGroundY] - (glTF/GLB only) world Y the ground shadows land on */