[SOLVED] Scenes run simultaneously when using a menu system

I’m making a game with multiple levels, which I have implemented using different scenes. The problem arises when I change the scenes frequently using the menu system I built. If I follow the expected route a player would take, the problem is not very noticeable. If I go back and forth between the menu screens, the scene order is thrown out of whack.

I change scenes using:

//stopping all scene sound
this.sound.stopAll();
//starting a new scene
this.scene.start('sceneName')

This is an example of the problem:
image
Level 1 has finished due to the player losing lives, the game over screen has loaded and afterwards, the hi-score screen (displayed) has loaded. But now there are values being logged in the background, which belong to Level 1 (acceleration values for falling items).
This happens, given that I’ve changed between the menu items on my title screen and level select screen.

Is it a problem with scenes buffering and then starting out of order when they load? Am I misunderstanding how scenes work? (I know they can run simultaneously, which is likely the thing that is causing the issue here) Am I supposed to stop the previous scene after the one I want displayed loads?

Any help would be appreciated.

I can provide more code if necessary

pls help

So I’ve tried stopping scenes entirely after a new one is selected using this.scene.stop() after I .start() a new scene. I thought this would stop the scene leaks, since the menu system in the title scene would no longer render, but it didn’t stop the leaks.

That leaves the issue being associated with solely the menu system. I believe that if I change between title and level select quickly, the values get muddled up and multiple scenes get selected simultaneously (I got 2 gameOvers from 2 different scenes running simultaneously with only one of them rendering on screen.)

Here is the code for the menu system in the Title Scene

//menu system
	let activeText = 0;
    let textGroup = [];
    const texts = ['Story Mode', 'Level Select', 'Hi Scores', '[credits]'];
    texts.forEach((text, index) => {
        textGroup.push(new MenuText(this, config.scale.width / 2, 400 + 70 * index + 1, "pixelFont", text, 60, index).setOrigin(0.5));
    });
    this.input.keyboard.on('keydown', event => {
        switch (event.key) {
            case 'ArrowUp':
                activeText -= 1;
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'ArrowDown':
                activeText += 1;
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'Enter':
                this.events.emit('SELECT');
                break;
            case 'f':
                this.events.emit('FULLSCREEN');
                break;
        }
    });
    this.events.addListener('CHANGE_BUTTON', payload => {
        this.click.play();
        if (activeText < 0)
            activeText += texts.length;
        if (payload && typeof payload.setIndex !== 'undefined')
            activeText = payload.setIndex;
        textGroup.forEach(text => {
            text.setStyleActive(text.index === activeText % texts.length);
        });
    });
    this.events.addListener('SELECT', payload => {
        this.sound.stopAll();
        if (activeText == 0){
            storyMode = true;
            console.log("Title to lvl1 instructions");
            this.scene.start('instructions1');
            console.log("Stopping current scene");
            this.scene.stop();
        }
        if (activeText == 1){
            console.log("Title to level select");
            this.scene.start('levelSelect');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
        if (activeText == 2){
            console.log("Title to HiScores");
            this.scene.start('displayHiScores');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
        if (activeText == 3){
            console.log("Title to credits");
            this.scene.start('credits');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
            activeText += texts.length;
        if (payload && typeof payload.setIndex !== 'undefined')
            activeText = payload.setIndex;
        textGroup.forEach(text => {
            text.setStyleActive(text.index === activeText % texts.length);
        });
    });
    this.events.addListener('FULLSCREEN', payload => {
        if (this.scale.isFullscreen)
        {                
            this.scale.stopFullscreen();
        }
        else
        {
            this.scale.startFullscreen();
        }
    });

Here is the menu system code for level select:

//menu options
	let activeText = 0;
    let textGroup = [];
    let iconGroup = [];
    const texts = ['Level 1', 'Level 2', 'Level 3'];
    const icons = [0,1,2];
    icons.forEach((icon, index) => {
        iconGroup.push(new Icon(this, 300 + 210 * index + 1, 300,  "level_icon", index).setOrigin(0.5));
    });
    texts.forEach((text, index) => {
        textGroup.push(new MenuText(this, 300 + 210 * index + 1, 420,  "pixelFont", text, 60, index).setOrigin(0.5));
    });
    textGroup.push(new MenuText(this, config.scale.width/2, 550, "pixelFont", "[back]", 60, 3).setOrigin(0.5));
    this.input.keyboard.on('keydown', event => {
        switch (event.key) {
            case 'ArrowLeft':
                activeText -= 1;
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'ArrowRight':
                activeText += 1;
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'ArrowDown':
                    activeText = 3;
                    this.events.emit('CHANGE_BUTTON');
                break;
            case 'ArrowUp':
                activeText = 1;
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'Enter':
                this.events.emit('SELECTITEM');
                break;
        }
    });
    this.events.addListener('CHANGE_BUTTON', payload => {
        this.click.play();
        if (activeText < 0)
            activeText += textGroup.length;
        if (payload && typeof payload.setIndex !== 'undefined')
            activeText = payload.setIndex;
        textGroup.forEach(text => {
            text.setStyleActive(text.index === activeText % textGroup.length);
        });
    });
    
    this.events.addListener('SELECTITEM', payload => {
        this.levelSelectBGM.stop();
        if (activeText == 0){
            this.select.play();
            console.log("Level Select to lvl1 instructions");
            this.scene.start('instructions1');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
        if (activeText == 1){
            this.select.play();
            console.log("Level Select to lvl2 instructions");
            this.scene.start('instructions2');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
        if (activeText == 2){
            this.select.play();
            console.log("Level Select to lvl3 instructions");
            this.scene.start('instructions3');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
        if (activeText == 3){
            console.log("Level Select to title");
            this.scene.start('gameTitle');
            console.log("Stopping current Scene");
            this.scene.stop();
        }
            activeText += texts.length;
        if (payload && typeof payload.setIndex !== 'undefined')
            activeText = payload.setIndex;
        textGroup.forEach(text => {
            text.setStyleActive(text.index === activeText % texts.length);
        });
    });

I would love it if you could take a look and tell me if I’m missing something. I’ll keep trying to debug this…

I used @yannick 's menu system post in this thread:

Turns out I overlooked the fact that every time I change back to the title screen and the level select screen (and any scenes branching from those 2), a listener for the ‘SELECT’ event gets added each time, increasing the number of times a scene gets selected.

I also spotted a few other bugs to do with menu navigation along the way and fixed them.

To fix the initial bug where a listener gets added every time, I added

//remove listeners
this.events.removeAllListeners('SELECT');

To the beginning of the create() function in each of the scenes with a menu system,

My menu system code now looks like this:

//menu system
	this.activeText = 0;
    let textGroup = [];
    const texts = ['Story Mode', 'Level Select', 'Hi Scores', '[credits]'];
    texts.forEach((text, index) => {
        textGroup.push(new MenuText(this, config.scale.width / 2, 400 + 70 * index + 1, "pixelFont", text, 60, index).setOrigin(0.5));
    });
    this.input.keyboard.on('keydown', event => {
        switch (event.key) {
            case 'ArrowUp':
                this.activeText -= 1;
                console.log(this.activeText)
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'ArrowDown':
                this.activeText += 1;
                console.log(this.activeText)
                this.events.emit('CHANGE_BUTTON');
                break;
            case 'Enter':
                this.events.emit('SELECT');
                break;
            case 'f':
                this.events.emit('FULLSCREEN');
                break;
        }
    });
    this.events.addListener('CHANGE_BUTTON', payload => {
        this.click.play();
        if (this.activeText < 0){
            this.activeText = 3;
            console.log("after correction: " + this.activeText)
        }
        else if (this.activeText > 3){
            this.activeText = 0;
            console.log("after correction: " + this.activeText)
        }
        if (payload && typeof payload.setIndex !== 'undefined'){
            this.activeText = payload.setIndex;
        }
        textGroup.forEach(text => {
            text.setStyleActive(text.index === this.activeText % texts.length);
        });
    }, this, true);
    this.events.addListener('SELECT', payload => {
        this.sound.stopAll();
        if (this.activeText == 0){
            storyMode = true;
            console.log("Title to lvl1 instructions");
            this.scene.start('instructions1');
            console.log("Stopping current scene");
            scene.events.
            this.scene.stop('gameTitle');
        }
        else if (this.activeText == 1){
            console.log("Title to level select");
            this.scene.start('levelSelect');
            console.log("Stopping current Scene");
            this.scene.stop('gameTitle');
        }
        else if (this.activeText == 2){
            console.log("Title to HiScores");
            this.scene.start('displayHiScores');
            console.log("Stopping current Scene");
            this.scene.stop('gameTitle');
        }
        else if (this.activeText == 3){
            console.log("Title to credits");
            this.scene.start('credits');
            console.log("Stopping current Scene");
            this.scene.stop('gameTitle');
        }
        if (payload && typeof payload.setIndex !== 'undefined'){
            this.activeText = payload.setIndex;
        }
    },this);
    this.events.addListener('FULLSCREEN', payload => {
        if (this.scale.isFullscreen)
        {                
            this.scale.stopFullscreen();
        }
        else
        {
            this.scale.startFullscreen();
        }
    });

Hope someone finds this useful in the future

1 Like

This was super useful to me, thank you for posting. I’m still working through the right patterns for managing a lot of scenes, so this helps my case

1 Like

I’ll post my menu system code as an example later :slight_smile: What I’ve got here is most of my code for this feature anyway, but I’ll make it more comprehensive.

oh man, I’d love to se your menu system, I’ve been wondering if I need to basically make a router or something