How to move entities to new scene during scene transition

When I load a new scene, I want to copy the Player entity to the new scene.

I’ve started with the standard scene switching tutorial, and I’m attempting to copy the Player entity
to the new scene on each scene switch.

It seems to work for a couple switches and then the Player starts only showing up in one of the scenes, but not the other.

Why doesn’t the Player continue to show up?

(Am I going about this in the wrong way?)

Thanks!

The editor is here:

https://playcanvas.com/editor/scene/947401

This is the code:

/*jshint esversion: 6 */

var ChangingScenes = pc.createScript('changingScenes');

ChangingScenes.attributes.add("sceneName", {type: "string", default: "", title: "Scene Name to Load"});

ChangingScenes.prototype.initialize = function(dt) {
    // Change scenes in 1 second
    var self = this;
    setTimeout(function (){ 
        self.loadScene(self.sceneName);
    }, 1000);
};


ChangingScenes.prototype.loadScene = function (sceneName) {
    // persist the Player entity so I can move it from scene to scene
    if(!this.app.persist){
        this.app.persist = {};
        this.app.persist.player = this.app.root.findByName ('Root').findByName('Player');
        p = this.app.persist.player.getPosition();
        this.app.persist.player.setPosition(p.x, p.y + 2, p.z);
    }
    // remove the player before loading the new scene so it's not destroyed when I destroy the old scene
    if(this.app.persist.player.parent) this.app.persist.player.parent.removeChild(this.app.persist.player);
    
    // Get a reference to the scene's root object
    var oldHierarchy = this.app.root.findByName ('Root');
    
    // Get the path to the scene
    var scene = this.app.scenes.find(sceneName);
    
    // Load the scenes entity hierarchy
    this.app.scenes.loadScene(scene.url, (err, scene)  => {
        if (!err) {
            oldHierarchy.destroy();
            pc.ComponentSystem.initialize(scene.root);
            pc.ComponentSystem.postInitialize(scene.root);
            
            // add the persisted player to the new scene
            this.app.root.findByName ('Root').addChild(this.app.persist.player);
            
        } else {
            console.error('loadScene callback error: ', err);
            
        }
    });
};

https://playcanvas.com/editor/scene/947401

Both scenes have an Entity named ‘Player’. Going from Scene 1 to Scene 2 would mean that when you are in Scene 2, you now have two Entities in the Scene Graph named ‘Player’.

Was this intended?

I wanted to keep it super simple for the purposes of requesting help on this forum. In another version of this experiment I delete a player if there is already a player saved in this.app.persist, but it still has the same problem. Since, I only search by name a single time (if this.app.persist is not populated), having a Player in both scenes shouldn’t cause any name collisions. (Having a player in both scenes means that you can start in either scene 1 or scene 2 and it should still work.)

So far, I can’t even get the basics to work. Am I going about this wrong? If not, can you help me identify the bug that starts preventing this.app.persist.player from appearing in one of the scenes after a few scene switches?

Hmm, well, one thing you should consider if you really need another scene. Keep in mind, you will trigger the garbage collection on the scene switch. In most cases, it is simply enough to arrange your entities in “level 1”, “level 2” etc hierarchy of the same scene. Then simply enabling/disabling the required parent to switch the scenes, even though, technically, it is the same scene.

Nevertheless, one way to keep the “Player” entity is to reparent it to the app.root:

/*jshint esversion: 6 */

var ChangingScenes = pc.createScript('changingScenes');

ChangingScenes.attributes.add("sceneName", {type: "string", default: "", title: "Scene Name to Load"});

ChangingScenes.prototype.initialize = function(dt) {
    // Change scenes in 1 second
    this.timeout = 1;
    
    if (!this.app.persist) {
        var player = this.app.root.findByName('Player');
        player.reparent(this.app.root);
        player.setPosition(0, 2, 0);
        player.enabled = true;
        
        this.app.persist = {};
        this.app.persist.player = player;
    } else {
        this.app.persist.player.enabled = true;
    }
};


ChangingScenes.prototype.loadScene = function (sceneName) {
    // Get a reference to the scene's root object
    var oldHierarchy = this.app.root.findByName('Root');
    
    // Get the path to the scene
    var scene = this.app.scenes.find(sceneName);
    
    // Load the scenes entity hierarchy
    this.app.scenes.loadScene(scene.url, (err, scene)  => {
        if (!err) {
            oldHierarchy.destroy();
            pc.ComponentSystem.initialize(scene.root);
            pc.ComponentSystem.postInitialize(scene.root);
            
        } else {
            console.error('loadScene callback error: ', err);
            
        }
    });
};

ChangingScenes.prototype.update = function(dt) {
    this.timeout -= dt;
    if (this.timeout < 0) {
        this.timeout = 1;
        this.loadScene(this.sceneName);
    }
};

This way, when a new scene loads, it will be able to use references created by another scene. You should disable the original “Player” entity at start, so it won’t spawn when the scene is loaded again.

2 Likes

Had a look at this and this is the same issue that I ran into here with a quirk in our entity component system: https://github.com/playcanvas/engine/issues/2167

Because of the GUID system, when an entity with the same GUID is created when one already exists in the scene graph, thinks start to go wrong as the system can’t identify the correct set of data.

There are two ways around this that I can think of:

3 Likes

Thanks LeXXik.

Your suggestion not to do a scene switch is interesting. Perhaps I misunderstand the purpose of scenes. When WOULD it make sense, or be necessary, to use a new scene, if not for new levels?

Thanks yaustar! That seems to solve the problem.

Well, to be honest with you, I am yet to find a good use for scenes. Perhaps, if you have many levels, and you don’t want to hold them all in memory, could be one case. Someone with more experience than I do may give you a better example.

There is a cost associated with having lots of entities in a scene during load (even when disabled) as it still has to create them, add the data, etc

When templates roll into production, this will help a lot and arguably, scenes may become redundant for most use cases.

I can imagine having a scene for development and another configured for production.

2 Likes