Babylon.js Editor: How to completely reset/restart the current scene without reloading the page?

Hi,

I’m working on a game using Babylon.js Editor 5.5.0, and I’m trying to implement a “Try Again” button after the player loses.

What I would like to do is completely reset the current level to its original state, without reloading the browser page.

For example, during the game I may:

  • Instantiate new objects from .scene assets.

  • Destroy objects.

  • Move objects.

  • Change game variables.

  • Change the player’s position/state.

When the player clicks “Try Again”, I would like everything to go back to the exact state it had when the level started.

Ideally, I would like something similar to:

Try Again
    ↓
Dispose/reset current level
    ↓
Restore the original scene
    ↓
Run the scripts/onStart again
    ↓
Start the level from the beginning

I know I can use:

window.location.reload();

but that reloads the entire webpage, which is not what I want.

I’ve also considered simply using setEnabled(false) and setEnabled(true), but that doesn’t reset the state of the objects.

I’m wondering if Babylon.js Editor has a recommended way to reload/reset the current scene or level at runtime, without reloading the browser.

Would the recommended approach be to:

  1. Keep the level as a .scene / AdvancedAssetContainer and instantiate a fresh copy when restarting?

  2. Dispose the current level and instantiate it again?

  3. Use some built-in scene reset/reload functionality?

  4. Or is there another recommended pattern for this in Babylon.js Editor?

I’d appreciate any advice on the best approach.

Thanks!

Hey @Maxi !

The AdvancedAssetContainer is a way to do that, the instantiate method of it returns a AdvancedAssetContainerInstantiatedEntries that provdes a dispose method used to dispose the instantiated entries. I suggest that you could dispose all entries and re-instantiate them.

Or you could also reload the scene using loadScene like it’s used in the template (the main page) but this requires to dispose the previous scene to load a fresh new one (takes more time dans just using advanced asset container)

Can you tell me more about your game? It’s gameplay etc;

Hey @julien-moreau

Thanks for the explanation!

The problem is that my level.scene is not just a collection of meshes. It contains pretty much the whole level, including other scene assets, GUI, and several scripts. Each of those scripts creates or instantiates additional things in their onStart().

This is a runner game, so when the player loses I need to completely restart the level and put everything back into its initial state.

I tried using AdvancedAssetContainer with @sceneAsset("level.scene") and removeDefault(), but I’m still seeing things from the level after calling removeDefault(). I think this is because the scripts inside level.scene are already creating other scene assets, GUI, and dynamic objects in their onStart(), so not everything is handled by the removeDefault() call.

This is why I’m wondering if completely reloading the scene with loadScene() might be a better approach for this kind of setup. That way I could start with a completely clean state instead of having to manually manage everything created by the different scripts.

The problem is that I’m not really sure how to use loadScene() in Babylon.js Editor. You mentioned using it like in the template/main page, but I’m not sure where that implementation is or how I should adapt it to my project.

Would you recommend using loadScene() for this setup? If so, could you point me to an example of how I should implement it in Babylon.js Editor?

Thanks!

Update:

I managed to get loadScene() working from my script, at least partially. I am now creating a new Scene, loading example.babylon into it, and disposing the old scene:

public async _restart(): Promise<void> {
    try {
        const oldScene = this.mesh.getScene();
        const engine = oldScene.getEngine();

        engine.stopRenderLoop();

        const newScene = new Scene(engine); 

        SceneLoaderFlags.ForceFullSceneLoadingForIncremental = true;

        await loadScene(
            "./scene/",
            "example.babylon",
            newScene,
            scriptsMap,
            { quality: "high" }
        );

        console.log("cameras newScene:", newScene.cameras.map(c => c.name));

        if (!newScene.activeCamera && newScene.cameras.length > 0) {
            newScene.activeCamera = newScene.cameras[0];
        }

        if (newScene.activeCamera) {
            newScene.activeCamera.attachControl();
        }

        oldScene.dispose();

        engine.runRenderLoop(() => newScene.render());
        engine.resize();

        GameManager._gameReady = false;

        console.log("_restart() complete");
    } catch (err) {
        console.error("ERROR:", err);
    }
}

This works partially: the new scene loads and the game starts again, but some materials from the player and some objects in the scene are missing or behave differently after the reload.

So I wanted to ask if I am heading in the right direction with this approach.

I’ve been working on this for several hours and I still haven’t managed to get a clean scene reset. What I ultimately want is for the game to return to exactly the same state it had when it initially started, as if the player had never played the level.

Is this approach correct for that, or is there something else I should be doing when reloading the scene with loadScene()?

Thanks again for your help!

removeDefault is used to remove all the entities from the scene that were created automatically during the loading process: they remain in memory but are not part of the scene anymore until you instantiate new entries.
In my game I considered a level to be the root of my gameplay where for each level I attached my play.ts script. The play script used @sceneAsset for each zombie type available in the game, remove default for all zombie scene assets and then instantiate them. So it looks we are on same category.

There is a method that you can implement in your scripts which is called once the entity the script is attached to is disposed. You could store all the entities you create dynamically by code outside of .scene assets and dispose them when the attached entity is disposed like so:

public onStop(): void {
    myDynamicEntities.forEach((e) => e.dispose(...));
}

This would allow to never recall loadScene and always remain on .instantiate of scene assets.

There is also helpers that you can use outside of scripts but in the main app like:

import { getScriptByClassForObject } from "babylonjs-editor-tools";

const playComponent = getScriptByClassForObject(scene, MyRootGameClass);

This is what I do to create a link between the game that is made using the Babylon.js Editor and the rest of my app such as the UI etc.: I get the instance of the main play script and then I can call methods on it like any other instance of class but outside the editor project’s context

What do you think?

Thanks again, I think I finally get it now, but let me share the actual code I ended up with so you can tell me if I got the pattern right.

First, my understanding of onStop(): it’s never called manually by me, it’s triggered automatically by the framework whenever the node a script is attached to gets disposed (either manually via .dispose(), or when the editor/game stops). For my “reset on death” flow specifically, onStop() isn’t really involved.

Here’s what I built instead:

  1. A static registry on my GameManager that any script can push instantiated entries into:
export class GameManager {
    private static _spawnedEntries: AdvancedAssetContainerInstantiatedEntries[] = [];

    public static registerSpawn(entry: AdvancedAssetContainerInstantiatedEntries): void {
        GameManager._spawnedEntries.push(entry);
    }

    public static resetGame(): void {
        // dispose everything that was dynamically instantiated during gameplay
        GameManager._spawnedEntries.forEach(entry => entry.dispose());
        GameManager._spawnedEntries = [];

        GameManager._player?.resetPlayer();
        GameManager._boss?.resetBoss();

        GameManager._gameReady = true;
    }
}
  1. Whenever a script instantiates something dynamically (walls, diamonds, a rake thrown by the boss, etc.), it registers it instead of just keeping a local reference:
private _createWalls(): void {
    for (let i = 0; i < 20; i++) {
        const wall = this._wall.instantiate();
        GameManager.registerSpawn(wall);
        wall.rootNodes.forEach(root => {
            root.position = new Vector3(0, 50, posZ);
        });
    }
}

public resetBoss(): void {
    this._fightstart = false;
    this._attack = false;
    this._createWalls(); // re-create fresh dynamic content
}
  1. My UI flow (button click → fade animation → on animation end) just calls GameManager.resetGame() once the fade finishes, instead of destroying/recreating the Scene like I was doing before (which is what was causing me to lose the camera and textures).

The player, boss, camera, lights, and the scene itself are never disposed or recreated — only their internal state resets, and only the dynamically instantiated stuff gets disposed/re-instantiated.

Does this match the pattern you had in mind, or is there something about the centralized registry (vs per-script onStop) that could bite me later?

I finally got the reset working! Thanks again for pointing me in the right direction.

I ended up creating an Empty Mesh called Level with a script attached to it. This script is now responsible for all my @sceneAsset references and their removeDefault() calls.

It also creates all the dynamic scene instances during gameplay and keeps them in an array:

private _dynamicScenes: any[] = [];

Whenever I create something dynamically, I register the instantiated entries in this array. For example, if the Boss needs to spawn projectiles, it doesn’t have its own @sceneAsset reference. Instead, it calls the Level script, and the Level script creates and keeps track of the spawned entities.

When I need to restart the game, I simply dispose all the dynamically created entities:

public _removeLevel(): void {
    this._dynamicScenes.forEach((e) => e.dispose());
    this._dynamicScenes = [];
}

Then I reset the GUI and the other gameplay state.

So now the structure is basically:

  • Level is responsible for all @sceneAsset references.

  • Level calls removeDefault() for those assets.

  • Level creates and keeps track of all dynamically instantiated entities.

  • The Boss, Player, and other gameplay objects don’t have their own @sceneAsset references.

  • They simply call the Level script when they need to spawn something.

  • I never dispose the Level itself; I just dispose all the dynamic instances through the Level script.

This is working perfectly for my runner game and the reset is now completely clean.

I’m not sure if this is the most optimal way to structure it, though. Since every gameplay object now has access to the Level script, I was wondering if you see any potential issues with this approach or if there is another pattern you would recommend.

Thanks again for your help!