Trying to call functions from a Singleton doesn't call the function

Godot Version

4.2

Question

Hi, I’m pretty new to Godot, and back whenever I originally implemented UI into the game I’ve been working on my implementation was truly awful. Now that I’m a little more competent I decided to make a system that was a little more foolproof. I have a singleton called Menu_Handler. The Menu_Handler has an Enum with states for each of the UI - states (Main menu, pause menu, settings menu, none, and inventory). I have functions for each of these states that handle what should happen if the user attempts to switch to one of these states while in their current state. from my testing it appears that calling these functions directly from the singleton works fine; however, whenever I try to call the function from one of the menus (ex. clicking the settings button in the main menu) the function is not executed. It doesn’t error or anything, it just simply does not trigger. I’ve tried adding breakpoints and the input to the button goes through completely fine, but the function is just simply not executed. I’ve autoloaded the singleton, and it autocompletes the function whenever I try to type it out so I don’t understand why the function cannot be called.

The code for the singleton is here:


extends Node
class_name Menu_Handler
enum MenuStates {MAIN, SETTINGS, PAUSE, INVENTORY, NONE}
var Menu := MenuStates.NONE
var prev_Menu := MenuStates.NONE
signal change_state(MenuState)

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	Menu = MenuStates.MAIN
	prev_Menu = MenuStates.NONE
	change_state.emit(Menu)


func _input(event: InputEvent) -> void:
	if event.is_action_pressed("ESC"):
		pause()
	if event.is_action_pressed("Inventory"):
		inventory()


func pause():
	match Menu:
		MenuStates.NONE:
			prev_Menu = Menu
			Menu = MenuStates.PAUSE
			change_state.emit(Menu)
		MenuStates.PAUSE:
			prev_Menu = Menu
			Menu = MenuStates.NONE
			change_state.emit(Menu)
		MenuStates.SETTINGS:
			Menu = prev_Menu
			prev_Menu = MenuStates.SETTINGS
			change_state.emit(Menu)
		MenuStates.INVENTORY:
			prev_Menu = Menu
			Menu = MenuStates.NONE
			change_state.emit(Menu)

func inventory():
	match Menu:
		MenuStates.NONE:
			prev_Menu = Menu
			Menu = MenuStates.NONE
			change_state.emit(Menu)
		MenuStates.INVENTORY:
			prev_Menu = MenuStates.INVENTORY
			Menu = MenuStates.NONE
			change_state.emit(Menu)

func settings():
	match Menu:
		MenuStates.SETTINGS:
			print("exit settings")
			Menu = prev_Menu
			prev_Menu = MenuStates.SETTINGS
			change_state.emit(Menu)
		MenuStates.MAIN or MenuStates.PAUSE:
			print("enter settings")
			prev_Menu = Menu
			Menu = MenuStates.SETTINGS
			change_state.emit(Menu)

func main():
	match Menu:
		MenuStates.SETTINGS or MenuStates.PAUSE:
			prev_Menu = Menu
			Menu = MenuStates.MAIN
			change_state.emit(Menu)

func none():
	match Menu:
		MenuStates.PAUSE or MenuStates.INVENTORY or MenuStates.MAIN:
			prev_Menu = Menu
			Menu = MenuStates.NONE
			change_state.emit(Menu)

and the code for the main menu is here. The rest of the UI states have similar code.

extends Control

func _on_play_pressed():
	MenuHandler.none()

func _on_settings_pressed():
	MenuHandler.settings()

func _on_quit_pressed():
#probably save the game, Isaac 
	get_tree().quit()


func _on_menu_handler_change_state(MenuState: Variant) -> void:
	if MenuState == MenuHandler.MenuStates.MAIN:
		set_visible(true)
		set_mouse_filter(Control.MOUSE_FILTER_STOP)
	else: 
		set_visible(false)
		set_mouse_filter(Control.MOUSE_FILTER_IGNORE)

sorry about that random comment btw, this is a collaborative project, and one of my companions is handling save/load in the game.

I don’t believe match works with keyword or, try using a comma instead

func settings():
	match Menu:
		MenuStates.SETTINGS:
			print("exit settings")
			Menu = prev_Menu
			prev_Menu = MenuStates.SETTINGS
			change_state.emit(Menu)
		MenuStates.MAIN, MenuStates.PAUSE:
			print("enter settings")
			prev_Menu = Menu
			Menu = MenuStates.SETTINGS
			change_state.emit(Menu)

Hmmm that seems to have been an issue, as now it is printing the “enter settings” statement; however, the actual menu does not change.

By the way, you’ve helped me with a previous issue before, and you were amazing. Thank you, gertkeno!

1 Like

It must be emitting the signal, where is it connected?

— your welcome :smiley:

I have them connected via the editor rather than code because the menus are hard-coded, and always in the root scene. I know that they are actually connected though, because the change_state.emit(Menu) statement on line 13 of the Menu_Handler does execute. Whenever the project is run without the line, every menu renders on top of each other.

Is the change_state signal connected to the _on_menu_handler_change_state method?

yes, it is.

Maybe add a print to check the method is actually being called

Could you show that scene tree? Is it all within the autoloaded scene? I imagine you cannot connect signals between an Autoload and other scenes through the editor. You might have two Menu_Handlers in your game because of this.

there already is one attached to the settings function in Menu handler. I should add that it swaps between exit settings and enter settings. This means that the Menu variable is changing states correctly.

There’s probably a more intuitive way to show you the scene tree, but here it is.
I didn’t originally create a few of the menu states, because one of my collaborators, who is even less versed in Godot, did. I intend to convert UI control into a packed scene, but for now, it isn’t.

I’m dumb, I realize that you were likely referring to the signal receiver. Upon testing, it properly executes whenever the ready function calls change_state; however, it does not whenever it is called elsewhere.

Everything is there cool! But it looks like you did make an instance of the script in a scene, if you have this script as an Autoload then you have two of them. This one in the scene is connected, but the autoloaded one is not.

You could have each menu listen to the appropriate signal on _ready(). Make sure to delete the MenuHandler in the scene.

extends Control

func _ready() -> void:
	MenuHandler.change_state.connect(_on_menu_handler_change_state)

func _on_menu_handler_change_state(MenuState: Variant) -> void:
	if MenuState == MenuHandler.MenuStates.MAIN:
		set_visible(true)
		set_mouse_filter(Control.MOUSE_FILTER_STOP)
	else: 
		set_visible(false)
		set_mouse_filter(Control.MOUSE_FILTER_IGNORE)

Another option is to lean into the scene-based one and remove the Autoload. Try each one out.

1 Like

Let me try that really quick. I didn’t realize that was how the autoload project setting worked. I think you may have also solved the other large bug with my project that was completely unrelated.