What is the best way to check a property from all children, assuming it is a shared property

Godot Version

4.5

Question

As the title explains, I am curious what the easiest way to check if a property, or more than one property(s), are all true or all false from all of a parent node’s children in GDScript;

this i would say would of course use a for loop, however I have never truly used them, at all, and I am simply curious how one would go about doing this.

Any suggestions, etc. are appreciated.

Take a step back and tell us what you are trying to accomplish. Because I think there’s an easier way.

But to do what you want, you want a Recursive Function. It looks like this:

First you need the same value to exist on EVERY NODE. Otherwise you’ll need code to filter out node types you don’t care about.

var is_flag_set: bool = true

Then, if you wanted to check to see if any node had the value set to false and fail if it did, you’d do something like this.

func _is_flag_set(node: Node) -> bool:
	if not is_flag_set:
		return false

	var return_value: bool = true
	for subnode in node.get_children():
		if not _is_flag_set(subnode):
			return false

	return true

Thank you very much also, this is extremely helpful:

Ahh yes,
now, backstory, i have been messing with my UI, at this point, it is slow, unconventional, and overall just awful and annoying;

my main goal here was that, i could use get_viewport().gui_get_focus_owner() alongside a flag in each button, which would in this case check if itself is being focused,

and the parent object which would be a grid container, would simply check if all child nodes for the flag;
if none of them are true, then it should focus child 0. This could be controlled by a menu level
global variable and exported index variables that determine when it should focus or not to avoid conflicting behavior.

Another thing was, I did not see anyone else asking about this, and I assumed that me asking would do others in the future a help.

Yess, if any of the child nodes lacks the flag, it will throw an invalid reference error,


Anyways,
at this point I do not think there is much of an easier way, but at this point i seem to like “re-inventing the wheel with cubes” very often, and indeed I lack 2 or more years of experience.

Like I said, there’s an easier way. Just track it. Every Control has a focus_entered signal. Just connect to the ones you care about and store that information somewhere. I do it at the screen level in my User Interface Plugin.

## A default screen that is tracked by the UI autoload. All buttons in the screen are automatically
## hooked up to play the click sound set up in the Sound autoload. It also allows you to set a
## default control for when the screen loads, and tracks the last button pressed for when a player
## returns to this screen.
@icon("res://addons/dragonforge_user_interface/assets/textures/icons/screen.svg")
class_name Screen extends Control

## The control that receives focus by default when starting.
@export var default_focused_control: Control

# For tracking the last focused button when traversing menus.
var _button_last_focused: BaseButton
# The button to use if no default button is set.
var _default_button_focus_fall_back: BaseButton


func _ready() -> void:
	hide()
	visibility_changed.connect(_on_visibility_changed)
	child_exiting_tree.connect(_on_control_removed)
	child_entered_tree.connect(_on_control_added)
	_connect_buttons(self)
	UI.register_screen(self)


func _on_visibility_changed() -> void:
	if visible:
		_set_focus()


func _on_control_added(node: Node) -> void:
	_connect_buttons(node)

func _on_control_removed(node: Node) -> void:
	_disconnect_buttons(node)


# Sets focus for a control for keyboard and gamepad users. Picks the last
# button that had focus, then the default if set, then defaults to the first
# button it finds on the screen.
func _set_focus() -> void:
	if _button_last_focused:
		_button_last_focused.grab_focus()
	elif default_focused_control:
		default_focused_control.grab_focus()
	elif _default_button_focus_fall_back:
		_default_button_focus_fall_back.grab_focus()


# Stores the currently selected button for focusing upon exiting and re-entering
# the screen. Only buttons are tracked, since to enter or exit a screen, you
# must typically be on a button.
func _on_button_focused(button: BaseButton) -> void:
	_button_last_focused = button


# Play the default button pressed sound stored in [Sound] (if [Sound] exists).
func _on_button_pressed() -> void:
	if get_tree().root.has_node("Sound"):
		var sound: Variant = get_tree().root.get_node("Sound")
		sound.play_ui_sound(sound.get_sound("button_pressed"))


# Connects any button in the passed node for the button click sound and for
# default focus. Does the same for any buttons farther down the tree.
func _connect_buttons(node: Node) -> void:
	for subnode in node.get_children():
		if subnode is BaseButton:
			if not _default_button_focus_fall_back:
				_default_button_focus_fall_back = subnode
			subnode.pressed.connect(_on_button_pressed)
			subnode.focus_entered.connect(_on_button_focused.bind(subnode))
		_connect_buttons(subnode)


# Disconnects any button the passed node for the button click sound and for
# default focus. Does the same for any buttons farther down the tree.
func _disconnect_buttons(node: Node) -> void:
	for subnode in node.get_children():
		if subnode is BaseButton:
			if _default_button_focus_fall_back == subnode:
				_default_button_focus_fall_back = null
			subnode.pressed.disconnect(_on_button_pressed)
			subnode.focus_entered.disconnect(_on_button_focused.bind(subnode))
		_disconnect_buttons(subnode)

When you open or close a screen, it remembers where the user last was and focuses on that Control. It also defaults to focusing on the first Control in the the list unless you set a specific Control to be used as the default.

I see…

How do you make sure there aren’t any conflictions?
before this topic, I did a very, very simple _focuser() function,

func _focuser():
	if Snes.menu_level == index1 or Snes.menu_level == index2:
		for grid_buttons in self.get_children():
			if get_viewport().gui_get_focus_owner() != grid_buttons:
				self.get_child(0).grab_focus()

I would assume it is very archaic, but considering i want a retro feel, that isnt much of an issue,
either way, if you dont mind me asking,
what are the pros/cons of my initial method, versus the easier method, assuming that i dont want option memory,

apologies if it comes across as rude, I am curious about each method

The code handles that. I’m not sure why there would be conflicts.

Your _focuser() seems to be operating at a higher level. I let each Screen handle that itself. So nothing needs to “manage” it.

If you don’t want the option of memory, it’s still easier to have every menu know what its default is.

I mean if there were multiple instances of the same node and script, this may be irrelevant though.

the _focuser() mostly handles itself, but there is 1 other level involved other than the actual global variable, but this is only so that options can change the level independently,
to me it seems like less syntax, i highly doubt that to be the case though.

at this point i should probably stop completely redoing my systems, but either way, thank you for your help.

I am going to assume, from context, you mean there multiple copies of the same node and script in the UI.

That doesn’t matter to Godot. Each node/script combination is a template. The instances created from that template are unique.

i mean multiple copies attempting to focus at the same time, not exactly copies being the issue, but the action of calling focus on more than one node simutaniously.

That, unfortunately, can happen if you have overlapping nodes. The trick is to stop parsing input once it arrives at the first control.

I believe the call is set_input_as_handled

visibility or the lack thereof, and additional handlers can also fix this though also