1
\$\begingroup\$

I'm having an issue trying to understand the ButtonGroup logic, totally new to GDScript, don't have a clue of what I'm doing. Obviously is wrong.

I created a scene that I saved in a folder called "Interface", here is how it looks. Sorry, I know it's probably messy:


enter image description here


The scene is called "MenuButtons" and it looks as follow:


enter image description here


The GDScript attached to the scene looks as follow:


extends Control

export(ButtonGroup) var group

func _ready():
    print(group.get_buttons())
    for i in group.get_buttons():
        i.connect("pressed", self, "button_pressed")

func button_pressed():
    var pressed_button = str(group.get_pressed_button())
    var split_text = pressed_button.split(':')
    var button_method = str(split_text[0])
    print(button_method)
    
    if button_method == "NewGameButton": print('Restarting the game')
    if button_method == "OptionsButton": print('Opening the Options screen')
    if button_method == "ExitButton": print('Quiting the game')

When I execute the scene with F6 everything works as expected:


enter image description here


Still not what I'm expecting, but it prints something. But here is the situation, I instantiated the MenuButtons scene as a node in another scene called GameOverlay, which is the test game UI.


enter image description here


This scene as well is instantiated inside the Level Scene.


enter image description here


When the Level is run and the Game Over screen shows, neither group.get_buttons() method returns a list and of course the buttons do not initiate the events. It seems to me that the script from MenuButtons does not recognize the button group at all.


enter image description here


I was following two tutorials trying to match their insight about using buttons in Godot. Yeah, I have no idea what I'm doing as you can tell. Any help would be really appreciated.

These are the tutorials I'm following:

  1. GODOT Menu System tutorial using button themes 20 lines of code
  2. How to use a Button Group - Godot Engine
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I'm to sidestep the question a little, because I'm going to suggest to not use ButtonGroup at all.


First, why not ButtonGroup?

For what I understand the BaseButtons register themselves with the ButtonGroup, but the ButtonGroup is local to the scene by default, and that is problematic when nesting the scene inside of another one.

We can implement the functionality of your code without ButtonGroup at all, and I don't think these kind of menu needs a ButtonGroup at all.

ButtonGroup prevents multiple buttons to stay pressed at the same time (think radio buttons or mutually exclusive toggle buttons), but that is not necessary if the buttons cannot stay pressed.


First, we want to connect the pressed signal of the BaseButtons from code. We already have a container MenuVContainer, let us use it:

extends Control

func _ready() -> void:
    for i in get_node("MenuVContainer").get_children():
        var button := i as BaseButton
        if is_instance_valid(button):
            button.connect("pressed", self, "button_pressed")

Here we are iterating over all the children of MenuVContainer. And we will try to cast them as BaseButton with i as BaseButton (which results in null if it wasn't a BaseButton, thus checking if the instance is valid allows us to skip anything that isn't a BaseButton). And, of course, we connect the signal.

Ah, next you want to know which button was pressed. Ok, no matter, we are going to bind that on the signal connection, like this:

extends Control


func _ready() -> void:
    for i in get_node("MenuVContainer").get_children():
        var button := i as BaseButton
        if is_instance_valid(button):
            button.connect("pressed", self, "button_pressed", [button])

And now button_pressed will get it as a parameter:

extends Control


func _ready() -> void:
    for i in get_node("MenuVContainer").get_children():
        var button := i as BaseButton
        if is_instance_valid(button):
            button.connect("pressed", self, "button_pressed", [button])


func button_pressed(pressed_button:BaseButton) -> void:
    pass

And, of course, you can check which one you got a do something accordingly:

extends Control


func _ready() -> void:
    for i in get_node("MenuVContainer").get_children():
        var button := i as BaseButton
        if is_instance_valid(button):
            button.connect("pressed", self, "button_pressed", [button])


func button_pressed(pressed_button:BaseButton) -> void:
    var button_method := pressed_button.name
    if button_method == "NewGameButton": print('Restarting the game')
    if button_method == "OptionsButton": print('Opening the Options screen')
    if button_method == "ExitButton": print('Quiting the game')

Here match would work too:

extends Control


func _ready() -> void:
    for i in get_node("MenuVContainer").get_children():
        var button := i as BaseButton
        if is_instance_valid(button):
            button.connect("pressed", self, "button_pressed", [button])


func button_pressed(pressed_button:BaseButton) -> void:
    match pressed_button.name:
        "NewGameButton":
            print('Restarting the game')
        "OptionsButton":
            print('Opening the Options screen')
        "ExitButton":
            print('Quiting the game')

You could even call methods by those names:

extends Control


func _ready() -> void:
    for i in get_node("MenuVContainer").get_children():
        var button := i as BaseButton
        if is_instance_valid(button):
            button.connect("pressed", self, "button_pressed", [button])


func button_pressed(pressed_button:BaseButton) -> void:
    if has_method(pressed_button.name):
        call(pressed_button.name)


func NewGameButton() -> void:
    print('Restarting the game')


func OptionsButton() -> void:
    print('Opening the Options screen')


func ExitButton() -> void:
    print('Quiting the game')

Wait, if we are calling methods, why not connect them directly? You still have to write what happens for each button anyway.

extends Control


func _ready() -> void:
    get_node("MenuVContainer/NewGameButton").connect("pressed", self, "NewGameButton")
    get_node("MenuVContainer/OptionsButton").connect("pressed", self, "OptionsButton")
    get_node("MenuVContainer/ExitButton").connect("pressed", self, "ExitButton")


func NewGameButton() -> void:
    print('Restarting the game')


func OptionsButton() -> void:
    print('Opening the Options screen')


func ExitButton() -> void:
    print('Quiting the game')

If we are connecting buttons to methods. Why not do that from the editor?

For each button go to the Node panel and then on the Signals tab, double click "pressed", and then select the root node of you scene (the dialog also allows you to change the name of the method it will connect the signal to and set other settings). And you will end up with code that looks like this:

extends Control


func _on_NewGameButton_pressed() -> void:
    print('Restarting the game')


func _on_OptionsButton_pressed() -> void:
    print('Opening the Options screen')


func _on_ExitButton_pressed() -> void:
    print('Quiting the game')

Yes, do the default thing.

\$\endgroup\$
1
  • \$\begingroup\$ I really like your solutions, and definitely I won't use the ButtonGroup, unless inside a menu scene by itself. But I'm looking for ways to avoid having to connect buttons in the traditional way. Feels like a waste of time when you can run everything is a simple way, like you suggested. The simpler the code the easier to maintain. Appreciate the answer I though I was going to be stuck. \$\endgroup\$
    – raphie
    Commented Sep 22, 2022 at 14:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .