How to create a menu system with sub-menus

Hello there. I’m trying to create a menu system with sub-menus, with both interaction with the mouse and/or keyboard.

I’m having trouble managing how this would work without making spaghetti code with lots of booleans and such. I’ve seperated each draw that I make into its own class, such as a Window class and a Panel class that has interaction.

It all seems fine but my process doesnt seem as modular and reuseable as I would like it to be. I’ve come across a lot of problems, one being unable to seperate what my Z key does when the menu is loaded, as opposed to when you’re moving your player.

import { Window } from '../classes/Window.js'

import { Panel } from '../classes/Panel.js'

import { QuestMenu } from '../classes/QuestMenu.js'

export class InGameMenu {
    constructor(scene) {
        this.menuWindow = new Window(scene, 10, 10, 780, 90)
        this.menuWindow.getGraphics().visible = false

        this.questMenu = new QuestMenu(scene)
        this.questMenu.clear()
        
        this.isMenuOn = false

        this.menuKeys = scene.input.keyboard.addKeys("P, Z")

        // menu select
        this.activeRect = null

        this._this = scene
        this.activeRect = 0
        this.rectGroup = []
        this.rects = [0, 1, 2, 3]
        this.textContainer = ["Inventory", "Quests", "Info", "Exit"]


        this.rects.forEach(index => {
            this.rectGroup.push(new Panel(this._this, 120 + 140 * index, 20, 120, 70, this.textContainer[index], index))
        })

        for (let i = 0; i < this.rectGroup.length; i++) {
            this.rectGroup[i].visible = false
            this.rectGroup[i].getText().visible = false
        }

        // input set to increment selected rectangle
        scene.input.keyboard.on('keydown', event => {
            switch (event.key) {
                case 'ArrowLeft':
                    this.activeRect -= 1
                    this._this.events.emit('CHANGE_BUTTON')
                    break;
                case 'ArrowRight':
                    this.activeRect += 1
                    this._this.events.emit('CHANGE_BUTTON')
                    break;
            }
        })

        scene.events.addListener('CHANGE_BUTTON', (payload) => {
            if (this.activeRect > 3) {
                this.activeRect = 0
            }
            if (this.activeRect < 0) {
                this.activeRect += this.rects.length;
            }
            if (payload && typeof payload.setIndex !== 'undefined')
                this.activeRect = payload.setIndex;
            this.rectGroup.forEach((rect) => {
                rect.setStyleActive(rect.index == this.activeRect % this.rects.length)
            })
            if (this.activeRect == 0) {
                console.log("selected inventory")
            }
        })


    }

    update(player) {
        if (this.isMenuOn == false) {
            if (Phaser.Input.Keyboard.JustDown(this.menuKeys.P)) {
                this.gamestate = "INGAMEMENU"
            }
        } else {
            if (Phaser.Input.Keyboard.JustDown(this.menuKeys.P)) {
                this.gamestate = "GAME"
            }
        }

        this.menuKeys.Z.enabled = false

        switch (this.gamestate) {
            case "GAME":
                this.isMenuOn = false
                this.menuWindow.getGraphics().visible = false
                for (let i = 0; i < this.rectGroup.length; i++) {
                    this.rectGroup[i].visible = false
                    this.rectGroup[i].getText().visible = false
                }
                player.expression = "MOVING"
                this.menuKeys.Z.enabled = false
                this.questMenu.clear()
                break;
            case "INGAMEMENU":
                this.isMenuOn = true
                this.menuWindow.getGraphics().visible = true
                for (let i = 0; i < this.rectGroup.length; i++) {
                    this.rectGroup[i].visible = true
                    this.rectGroup[i].getText().visible = true
                }
                player.expression = "PAUSED"
                this.menuKeys.Z.enabled = true

                break;
            case "ENTERDIALOGUEUI":
                this.isMenuOn = false

                break;
            default:
        }
        // use handling for key Z
        if (this.activeRect == 0) {
            if (Phaser.Input.Keyboard.JustDown(this.menuKeys.Z)) {
                console.log("pressed on 0");
                this.questMenu.clear()
            }
        }

        if (this.activeRect == 1) {
            if (Phaser.Input.Keyboard.JustDown(this.menuKeys.Z)) {
                console.log("pressed on 1");
                this.questMenu.show()
            }
        }

        if (this.activeRect == 2) {
            if (Phaser.Input.Keyboard.JustDown(this.menuKeys.Z)) {
                console.log("pressed on 2");
                this.questMenu.clear()

            }
        }
    }
}

I want to eventually create an inventory, with items, similar to a lot of rpg games. My initial thoughts was to create a menu system similar to early Final Fantasy games (FF6, 7, 8, 9) or atleast the bare bones of it.

If anyone can offer advice on to do something like this, I’d really appreciate it.

example-menu1

example-menu2

One possible solution to the issue of handling what the Z key does depending on if the player is in the menu or not would be to move the Menu to a separate Phaser Scene. This would allow you to encapsulate that logic, and you could switch between the scenes by using the sleep and wake methods of the scene manager.

Here is a basic example:

If you press the P key, the scene should switch and if you press Z you should see a message logged to the developer console that shows which scene it is tied to. Also, if you wanted the main game scene to still be rendered, you could use the pause method instead.

1 Like

I added submenus to the original example. It’s a rather dirty hack, but hey, maybe you can use it.

Menu with Keyboard and Mouse control.
Submenu’s are declared as nested array.

    var texts = [
        'Text1', ['Text1_1', 'Text1_2', ['Text1_2_1', 'Text1_2_2', ['Text1_2_2_1']], 'Text1_3'],
        'Text2', ['Text2_1', 'Text2_2'],
        'Text3', ['Text3_1', 'Text3_2'],
        'Text4', ['Text4_1', 'Text4_2'],
        'Text5', ['Text5_1', 'Text5_2']
    ];
1 Like

Please excuse my absence, but Im back.

I can see how using scenes would be appropriate for handling ui. But what if you’re using multiple scenes and are wanting to run a function from each scene, from outside it’s own scene. Like one scene handling a general menu, and other ui scenes that handle submenus like an inventory or items section.

I’m aware that parent scenes can be inherited into a child scene using the init() command, but what if I want to call a function from the child scene. this.scene.launch() will launch the scenes, but all the functions only run within the scenes itself, and not where you launched your scene from. Apologies if any of this ain’t making much sense.

I’m struggling to run functions from outside the scene, while at the same time not sure if that is possible. I figure you could have a destroy function, or use the visible boolean in any of the additional scenes, but it seems I can only run the function from within the scene only.

If someone could enlighten me I would really appreciate. Once again, thanks Milton and Scottwestover

EDIT:

after looking through examples, I found this. Phaser - Examples - Call Function In Another Scene

seems to do what I want it to do. Thanks!