Best way to handle scenes with own tilemaps and launching seperate UI Scenes

Hi there.

So I’ve been working on a project, and have become stumped due to a problem.

import { Player } from '../../classes/entities/Player.js'
import { Zone } from '../../classes/entities/Zone.js'
import { NPCMinotaur } from '../../classes/entities/NPCMinotaur.js'
import { Item } from '../../classes/entities/Item.js'

export class GameScene extends Phaser.Scene {
  constructor() {
    super({ key: 'GameScene', active: false })
  }
  init(data) {
    this.sceneName = 'GameScene'
    this.posX = data.posX
    this.posY = data.posY

  }
  create() {
    let selectData = { mainScene: this}
    this.scene.launch("ToolInventoryScene", selectData)
    this.scene.launch("ItemInventoryScene", selectData)
    this.menuKeys = this.input.keyboard.addKeys("TAB");

    
    this.players = this.physics.add.group({
      classType: Player,
      runChildUpdate: true,
    })
    
    this.zones = this.physics.add.group({
      classType: Zone,
      runChildUpdate: true
    })
    
    this.npcMinotaurs = this.physics.add.group({
      classType: NPCMinotaur,
      runChildUpdate: true,
    })

    this.droppedItems = this.physics.add.group({
      classType: Item,
      runChildUpdate: true
    })


    this.map2 = this.make.tilemap({ key: 'map-1-1' })
    let tiles2 = this.map2.addTilesetImage('tileset-extruded', 'tiles', 16, 16, 2, 2);
    this.layer1 = this.map2.createLayer('groundLayer', tiles2, 0, 0);
    this.layer2 = this.map2.createLayer('collideLayer', tiles2, 0, 0);
    this.npcMinotaursLayer = this.map2.getObjectLayer('npcs')
    this.zonesLayer = this.map2.getObjectLayer('zones')

    this.players.get(this.posX, this.posY, "adventurer")

    this.npcMinotaursLayer.objects.forEach((obj) => {
      this.npcMinotaurs.get(obj.x, obj.y, 'minotaur').setDepth(100)
    })

    this.zonesLayer.objects.forEach((obj) => {
      this.zones.get(obj.x + obj.width / 2, obj.y + obj.height / 2, obj.width, obj.height)
    })

    this.dungeonOneScene = this.scene.get("DungeonOne")
    
    this.physics.add.overlap(this.zones.getChildren()[0], this.players.getChildren()[0], () => {
      let data = {
        players: this.players,
        posX: 57,
        posY: 77,
        playerHealth: this.players.getChildren()[0].health,
        playerInventory: this.players.getChildren()[0].playerInventory,
        toolInventory: this.players.getChildren()[0].toolInventory,
        itemInventory: this.players.getChildren()[0].itemInventory,
        prevScene: this,
      }
      this.scene.wake('DungeonOne', data)
      this.scene.sleep()
    }) 

    let data = { players: this.players, scene: this}
    this.scene.launch('GameUIScene', data)
  }

  setPlayerPos(x, y) {
    this.players.getChildren()[0].x = x
    this.players.getChildren()[0].y = y
  }
  setPlayerHealth(value) {
    this.players.getChildren()[0].health = value
  }
  dropItem(x, y, randomNumber) {
    if (randomNumber == 0) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: 'Healing Potion', quantity: 1 })
    }
    if (randomNumber == 1) {
      this.droppedItems.create(x, y, 'item')
        .setInteractive()
        .inventory.addItem({ name: 'Healing Potion', quantity: 2 })
    }
    if (randomNumber == 2) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: "Apple", quantity: 3 })
    }
    if (randomNumber == 3) {
      this.droppedItems.create(x, y, 'item').setInteractive()
    }
    if (randomNumber == 4) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: "Flame Staff", quantity: 1 })
    }
    if (randomNumber == 5) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: "Healing Potion", quantity: 2 })
    }
    if (randomNumber == 6) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.items[{ name: 'test', quantity: 2 }, { name: 'qweqw', quantity: 2 }]
    }
  }
  update() {
    let player = this.players.getChildren()[0]
    console.log(player.x, player.y)
    this.cameras.main.startFollow(player, false, 0.08, 0.08)
    this.cameras.main.setZoom(2.8);
    if (Phaser.Input.Keyboard.JustDown(this.menuKeys.TAB)) {
      let data = { playerInventory: this.players.getChildren()[0].playerInventory, player: this.players.getChildren()[0], scene: this };
      this.scene.pause()
      this.scene.launch("InGameMenuScene", data);
    }
  }
}

import { Player } from '../../classes/entities/Player.js'
import { Zone } from '../../classes/entities/Zone.js'

import { NPCMinotaur } from '../../classes/entities/NPCMinotaur.js'
import { Item } from '../../classes/entities/Item.js'

export class DungeonOne extends Phaser.Scene {
  constructor() {
    super({ key: 'DungeonOne', active: false })
  }
  init(data) {
    this.sceneName = 'DungeonOne'
    this.playerInventory = data.playerInventory
    this.toolInventory = data.toolInventory
    this.itemInventory = data.itemInventory
  }
  create(data) {


    let selectData = { mainScene: this}
    this.scene.launch("ToolInventoryScene", selectData)
    this.scene.launch("ItemInventoryScene", selectData)
    this.menuKeys = this.input.keyboard.addKeys("TAB");
    

    this.players = this.physics.add.group({
      classType: Player,
      runChildUpdate: true,
    })


    this.zones = this.physics.add.group({
      classType: Zone,
      runChildUpdate: true
    })
    this.npcMinotaurs = this.physics.add.group({
      classType: NPCMinotaur,
      runChildUpdate: true,
    })


    this.droppedItems = this.physics.add.group({
      classType: Item,
      runChildUpdate: true
    })
    console.log(this.npcMinotaurs, this.npcs)

    this.map2 = this.make.tilemap({ key: 'map-1-2' })
    let tiles2 = this.map2.addTilesetImage('tileset-extruded', 'tiles', 16, 16, 2, 2);
    this.layer1 = this.map2.createLayer('groundLayer', tiles2, 0, 0);
    this.layer2 = this.map2.createLayer('collideLayer', tiles2, 0, 0);
    this.npcMinotaursLayer = this.map2.getObjectLayer('npcs')
    this.zonesLayer = this.map2.getObjectLayer('zones')
    this.zonesLayer.objects.forEach((obj) => {
      this.zones.get(obj.x + obj.width / 2, obj.y + obj.height / 2, obj.width, obj.height)
    })

    this.npcMinotaursLayer.objects.forEach((obj) => {
      this.npcMinotaurs.get(obj.x, obj.y, 'minotaur').setDepth(100)
    })


    this.players.get(this.posX, this.posY, "adventurer")

    this.physics.add.overlap(this.zones.getChildren()[0], this.players.getChildren()[0], () => {
      let data = {
        players: this.players,
        posX: 318,
        posY: 142,
        npcs: this.npcs,
        playerHealth: this.players.getChildren()[0].health,
        playerInventory: this.players.getChildren()[0].playerInventory,
        toolInventory: this.players.getChildren()[0].toolInventory,
        itemInventory: this.players.getChildren()[0].itemInventory,
        prevScene: this,
      }
      this.scene.wake('GameScene', data)
      this.scene.sleep()
    })
  }

  setPlayerPos(x, y) {
    this.players.getChildren()[0].x = x
    this.players.getChildren()[0].y = y
  }
  setPlayerHealth(value) {
    this.players.getChildren()[0].health = value
  }
  dropItem(x, y, randomNumber) {
    // console.log(npc)
    // npc.npcStateMachine.transition("dead")
    if (randomNumber == 0) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: 'Healing Potion', quantity: 1 })
    }
    if (randomNumber == 1) {
      this.droppedItems.create(x, y, 'item')
        .setInteractive()
        .inventory.addItem({ name: 'Healing Potion', quantity: 2 })
    }
    if (randomNumber == 2) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: "Apple", quantity: 3 })
    }
    if (randomNumber == 3) {
      this.droppedItems.create(x, y, 'item').setInteractive()
    }
    if (randomNumber == 4) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: "Flame Staff", quantity: 1 })
    }
    if (randomNumber == 5) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.addItem({ name: "Healing Potion", quantity: 2 })
    }
    if (randomNumber == 6) {
      this.droppedItems.create(x, y, 'item').setInteractive()
        .inventory.items[{ name: 'test', quantity: 2 }, { name: 'qweqw', quantity: 2 }]
    }
  }
  update() {
    this.gameScene = this.scene.get("GameScene")
    let player = this.players.getChildren()[0]
    this.cameras.main.startFollow(player, false, 0.08, 0.08)
    this.cameras.main.setZoom(2.8);
    if (Phaser.Input.Keyboard.JustDown(this.menuKeys.TAB)) {
      let data = { playerInventory: this.players.getChildren()[0].playerInventory, player: this.players.getChildren()[0], scene: this };
      this.scene.pause()
      this.scene.launch("InGameMenuScene", data);
    }
  }
}

I have seperate scenes for the UI, being launched via this.scene.launch inside the first scene (called MainGameScene).
The first scene displays the first tilemap, and once a zone is overlapped in the tilemap, the player is led into the next scene (called DungeonOneScene), which displays the second map.

I’m having problems making the UI work, some of this I’ll have to explain. It works fine in MainGameScene as I launch the UI Scene. Because of the way the code is being handled in the UI scenes, usage of the UI doesnt work. I’m sending current scene data into the UI scene as it is launched, and I want that scene data to change depending on what scene you’re currently in.

As it switches to the DungeonOneScene, the UI scene only receives the previous scenes data, and so therefore the UI doesnt work properly in the DungeonOneScene. Ideally I’d want the scene that is passed into the UI scene to chance depending on which scene you are in.

I tried to launch the UI scenes again inside the DungeonOneScene, hopefully to update to the scene the player is in, inside the DungeonOneScene but it doesnt work. Seems like you can only use the this.scene.launch() function once, if I’m not mistaken.

export class ItemInventoryScene extends Phaser.Scene {
    constructor() {
        super("ItemInventoryScene")

        this.rows = 1
        this.uiScale = 1.5
        this.gridSpacing = 4
        this.margin = 8
        this._tileSize = 32
        this.inventorySlots = []

    }
    preload() {

    }
    init(data) {
// the data being passed from the first scene (data.mainScene is the scene from its current scene the player is in )
        this.mainScene = data.mainScene
        this.inventory = this.mainScene.players.getChildren()[0].itemInventory
        this.maxColumns = this.mainScene.players.getChildren()[0].itemInventory.maxColumns
        this.maxRows = this.mainScene.players.getChildren()[0].itemInventory.maxRows
        this.inventory.subscribe(() => this.refresh())
        this.playerStateMachine = this.mainScene.players.getChildren()[0].playerStateMachine
        console.log(this.dungeonOneScene)

        this.items = {
            "Long Sword": { frame: "sword.png" },
            "Short Sword": { frame: "sword.png" },
            "Firebolt": { frame: "fire.png" },
            "Blank": { frame: "blank.png" },
            "Healing Potion": { frame: "potion.png" },
            "Mana Potion": { frame: "potion2.png" },
            "Apple": { frame: "apple.png" }
        
        }
    }

    get tileSize() {
        return this._tileSize * this.uiScale
    }

    destroyInventorySlot(inventorySlot) {
        if (inventorySlot.item) inventorySlot.item.destroy()
        inventorySlot.destroy()
    }

    refresh() {
        console.log("refresh")
        this.inventorySlots.forEach(s => this.destroyInventorySlot(s))
        this.inventorySlots = []
        for (let index = 0; index < this.maxColumns * this.rows; index++) {
            let x = this.margin + this.tileSize / 2 + (index % this.maxColumns) * (this.tileSize + this.gridSpacing) + 130
            let y = this.margin + this.tileSize / 2 + 37
            let inventorySlot = this.add.sprite(x, y, "items1", "panel.png")
            inventorySlot.setScale(this.uiScale)
            let item = this.inventory.getItem(index)

            if (item) {
                inventorySlot.item = this.add.sprite(inventorySlot.x, inventorySlot.y, "items1", this.items[item.name].frame)
                inventorySlot.setInteractive()
            }
            this.inventorySlots.push(inventorySlot)
            this.inventorySlots[index].tint = 0xfffff

        }
    }

    handleSlotPressed(index) {
        this.inventorySlots[index].tint = 0xffff00
        let timer = this.time.delayedCall(100, () => {
            this.inventorySlots[index].tint = 0xfffff
        });
    }

    create() {
        this.input.keyboard.on("keydown-E", () => {
            this.inventory.selected = 0
        })
        this.input.keyboard.on("keydown-R", () => {
            this.inventory.selected = 1
        })

        this.refresh()
    }
}

So long story short, my goal is for the player to be able to transition from scene to another with each scene having its own tilemap, and ideally, be able to retain all the items in the UI, and retain the current states of the npcs and the players.

Initally, I was using the this.scene.start, meaning that each time you entered the next scene, the scene would start each time. This was fine until I realized the npcs would restart every time back to its beginning.

I changed the code, started the game scenes in preload, and put the scenes being unused to sleep, and used this.scene.wake to wake up the scenes once the zone is overlapped.

So, I’ve realized that this is getting increasingly complex, and am asking if anyone knows of any manageable ways to handle scenes with its own tilemaps, and launching UI scenes on top.
Apologies if this is unbearable to read or understand.

I plan to get a working example up tomorrow to help solve this problem.

If anyone has any suggestions, I’d very much appreciate it!

Thanks

I managed to get it working… I put this.scene.launch function in the update of the player, where the scene currently will update according to which scene the player is in. I’m unsure if this is the best solution but for now, it will do.