Home > Blockchain >  Phaser 3, multiple scenes of same class being created on second use of .launch() function
Phaser 3, multiple scenes of same class being created on second use of .launch() function

Time:02-03

Im creating a multiplayer game using Phaser 3 in combination with Socket.io, it is a Mario party style game with multiple minigames that are separated by quiz rounds where questions are asked.

I intend for the host of the game to be able to pick the minigame and then the players will play it, then the same minigame could be replayed if the host picks it again, however after a minigame has been played once when it is loaded again multiple versions of the scene are created.

It is difficult to explain the issue so I will try to show it visually by showing how the scenes are loaded:

Host Client Scene order:

  • Main page (This is the page where a client chooses to be host or player)
  • Host Page (This is where the host waits for players to join)
  • Minigame Selector (Where the host picks a minigame)
  • Minigame 1 Host side (For this example a minigame called minigame 1 is picked)
  • Minigame Selector (The minigame selector is loaded again)
  • Minigame 1 Host side (Minigame 1 is picked again)
  • Minigame Selector

Player Client Scene order

  • Main Page
  • Player Page (This is where a player waits in the lobby until the host starts the game)
  • Intermediate Player Scene (This is where a player waits until a minigame is chosen)
  • Minigame 1 Player (once host picks minigame 1 all players connected to the host lobby will play the minigame)
  • Intermediate Player Scene
  • Minigame 1 Player x2 (After the minigame is launched again 2 versions of it are loaded simultaneously causing scores to be messed up)
  • Intermediate Player Scene x2 (At this point the error is exponential, if the minigame is loaded again then scores become even more skewed and more versions of the same scene are loaded)

Im pretty sure it is nothing to do with multiple socket events being emitted because I tried just launching the scenes on the player side with no socket interactions and the same error occurred.

Things I have tried:

  • Just using .launch() and .stop() to start and stop scenes
  • Using if statements and variables to prevent multiple launches
  • Clearing Timer interval at beginning of scene
  • Using .remove() to completely destroy it, then .add() to add it back to manager
  • Using this.events.once() to ensure it can only happen once

Code:

Host Minigame Scene:

class minigame1Host extends Phaser.Scene
{
    constructor() {
        super('mg1Host');
        
    }

    create() 
    {
        clearInterval(this.countdown)
        this.socket = this.registry.get("socket")
        this.val = this.registry.get("pin")
        let { width, height } = this.sys.game.canvas;
      
        this.timeLimit = 15
        this.doneCheck = null
      
        this.timeText = this.add.text(width/2,height/2-200,this.timeLimit).setScale(2)
      
        this.countdown = setInterval(() => {
          this.timeLimit = this.timeLimit - 1
      
          this.timeText.text = String(this.timeLimit)
      
          if (this.timeLimit == 0) {
            if(this.doneCheck != true)
            {
                
                this.doneCheck = true
                clearInterval(this.countdown)
                this.scene.launch("selector").stop()

            }
          }
        }, 1000);

        
    }

    update()
    {
      //some code to generate a random value for enemy spawning
    }

      

}

Player Minigame Scene:

class minigame1Player extends Phaser.Scene
{
    constructor() {
        super('mg1Player');
        
    }

    create()
    {
        clearInterval(this.countdown)
        this.socket = this.registry.get("socket")
        this.val = this.registry.get("pin")
        let { width, height } = this.sys.game.canvas;
      
        this.timeLimit = 15
        this.score = 0
        
        
        //create groups for scorers (a scorer is something a player can click to get points)
        this.goodScorers = this.add.group()
        this.badScorers = this.add.group()
       
        this.timeText = this.add.text(width/2,height/2-460,this.timeLimit).setScale(2)

        this.scoreText = this.add.text(width/2-200,height/2-100,this.score).setScale(2)
        this.doneCheck = false


      
        this.countdown = setInterval(() => {
            this.timeLimit = this.timeLimit - 1
        
            this.timeText.text = String(this.timeLimit)
        
            if (this.timeLimit == 0) {
              if(this.doneCheck != true)
              {
                  this.goodScorers.destroy()
                  this.badScorers.destroy()
                  this.doneCheck = true
                  clearInterval(this.countdown)
                  clearTimeout(this.deleteBadAfter)
                  clearTimeout(this.deleteGoodAfter)
                  score = score   this.score
                  this.scene.launch("tempPlayer").stop()
  
              }
            }
          }, 1000);

        this.socket.on("createScorer" ,(values) =>
        {
          //code that creates scorers
        })

    }

}

Minigame Selector:

class pickMinigameHost extends Phaser.Scene
{
    constructor() {
        super('selector');
        
    }

    create()
    {
        this.socket = this.registry.get("socket")
        this.val = this.registry.get("pin")
        let { width, height } = this.sys.game.canvas;

        this.add.text(width/2, height/2, "Pick a minigame:")

        this.mg1But = this.add.image(width/2,height/2-300,"dissButton").setInteractive().setScale(0.5)


        this.mouseCheck = false

        
        this.mg1But.on('pointerdown', () => 
        {
            if(this.mouseCheck == false)
            {
                this.mouseCheck = true
                this.socket.emit("startMG1", [this.val]);
                this.scene.launch("mg1Host").stop()
            }
        })

    }
}

Temporary Player Scene:

class temporaryPlayer extends Phaser.Scene
{
    constructor() {
        super('tempPlayer');
        
    }

    create()
    {
        clearInterval(this.countdown)
        this.socket = this.registry.get("socket")
        let { width, height } = this.sys.game.canvas;

        this.add.text(width/2, height/2, "A Minigame is being selected")

        this.socket.on("startMg1Comp" ,() =>
        {
            this.scene.launch("mg1Player").stop()

        })

    }
}

Note: All of the provided code is client side as I dont think server is the issue, btw sorry for my awful code and if it is a really easy fix.

CodePudding user response:

Your code is pretty complicated , with all the setTimeout and setInterval function calls, and Alot of "Scene-hopping", what makes following the flow very difficult.

That said, your assumtion is incorrect, as far as I know there can't be two scene running with the same name. You can check it calling console.info(this.scene.manager.keys) this will show all scenes.

I can just asume that the problem has to do with the socket call, in the create function, for example:

    this.socket.on("createScorer", (values) => {
      //code that creates scorers
    })

Since you are binding on each launch function a new listener to the same socket. Possible solutions, all with there own drawbacks:

  • remove listener on stopping the scene, ( something like: this.events.on('shutdown', () => socket.off('createScorer'));)
  • attach the listener in the constructor (a scene constructor is only called once)
  • create a master/background Scene that handels all socket actions (I have no good example at hand, but you could create a hierachy something like in this answer)

P.s: I would recommend createng CustomScenes with you extend to keep code duplication to a minimum.

For example: (extracting some logic/data into the custom class)

class MySocketScene extends Phaser.Scene {
    constructor(name) {
        super(name);
    }

    create() {
        this.socket = this.registry.get("socket");
        this.val = this.registry.get("pin");
        
        this.width = this.sys.game.canvas.width;
        this.height = this.sys.game.canvas.height;
        
        // ... Other repeating code
    }
}

// btw.: I would Name My Classes in PascalCase
class minigame1Host extends MySocketScene {
    constructor() {
        super('mg1Host');
    }

    create() {
        super.create();
        let { width, height } = this;
        //...
    }
    
    //...
}
  • Related