+1 vote

I am trying to create a main menu for my game, which I technically already have, but it currently has no way to transition between multiple levels of menu ("into Game select screen, back up to the main menu, into options, back again, etc") and every single resource on designing a menu ignores this part. I am fairly new to gdscript and it is loose enough that it would be extremely easy to make a system that is extremely janky.

So my question essentially is does anyone know of the "best practices" or common approaches / design patterns used for menu navigation in gdscript / godot? If there are any resources on this matter that you know of that would be nice to know about too. Loading separate scenes one after another doesn't seem like a good option, stacking the entire menu on one scene in layers and showing / hiding them doesn't seem particularly clean. Is there something I am failing to think of here or are those really the only options?

in Engine by (37 points)

1 Answer

+3 votes
Best answer

I took a page out of my old unreal engine games book and made a menu handler in my GameState singleton (autoload) that handles menus in a similar way to the same method used for handling scene switching in a singleton.

For anyone looking to do this it looks as follows:

you preload all of your menus into a dictionary:

#MENU_LEVEL.MAIN is index 1 not zero so keep that in mind if you change to an array
enum MENU_LEVEL {
        NONE,
        MAIN,
        START,
        JOIN,
        OPTIONS
    }

var menus = {
    MENU_LEVEL.MAIN : preload("res://gui/MainMenuScreen.tscn").instance(), 
    MENU_LEVEL.START : preload("res://gui/StartGameScreen.tscn").instance(),
    MENU_LEVEL.JOIN : preload("res://gui/JoinGameScreen.tscn").instance(),
    MENU_LEVEL.OPTIONS : preload("res://gui/OptionsScreen.tscn").instance()
}

the enum is optional but it makes it nicer to call the switch menu function later. You could technically store all of the menus in an array or individually but that is lame. :P

after the menus dictionary you have this var: var current_menu : Node = null
which stores a reference to the currently open menu

in _ready() call this: load_menu(MENU_LEVEL.MAIN)

and here is the magic sauce:

func load_menu(menulevel):
    call_deferred("_deferred_load_menu", menulevel)


func _deferred_load_menu(menulevel):
    #replace the current menus instance with the new ones
    current_menu = menus[menulevel]

    var container = current_scene.find_node("menu", false, false)
    if not container:
        var menunode = Node.new()
        menunode.set_name("menu")
        current_scene.add_child(menunode)
        container = menunode
    #clear the current menu item/s
    for location in container.get_children():
        container.remove_child(location)
    #add our selected menu
    container.add_child(current_menu)

that deferred calls the load menu function which replaces currentmenu with a reference to our already initialized menu of chosen index, and then gets our "menu" node in our scene (or adds it if it doesn't exist yet), empties the menu node of all children, and adds our currentmenu back again to it.

The benefits of this approach are:

  • you only have to initialize your menu once and keep a copy of it
    without deleting and reinitializing menus every time you switch
    scenes
  • switching menus is as simple as calling loadmenu(MENULEVEL.OPTIONS) and it will handle removing the current
    ui, replacing with the new one, keeping it organized in its own
    container node, and all of that other stuff
  • adding new menu items is as simple as providing the path to its scene and adding a new enum for it
  • you can make a re-usable main menu back button that always calls loadmenu(MENULEVEL.MAIN) <- i did ;)
  • it is very reliable and you can modify it to work in tandum with in game menus if you want as well.

Best of luck with this singleton menu controller pattern :) Let me know if anyone reads this, uses this, likes it, or notices some sort of error. Feedback is most welcome!

Enjoy!

by (37 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.