FindChildren type argument

Godot Version

4.2

Question

How does Node.FindChildren’s “type” argument work?

The docs on this function state:

Blockquote
Finds descendants of this node whose name matches pattern as in String.match, and/or type matches type as in Object.is_class. Internal children are also searched over (see internal parameter in add_child).
Blockquote

It yields no results however when I attempt to find children by their type name like so:

FindChildren("*", typeof(ClassToFind).Name).ToList()

I’ve checked, the descendants I’m trying to find are in fact of ClassToFind.

I tried using that function as well but I quickly gave up on it.
As a result, I made my own utility functions that serve similar purposes.
Here they are:

TryGetNode ( )
	/// <summary>
	/// Attempt to find a node of type, T, in this node's children.
	/// </summary>
	/// <typeparam name="T">Node type.</typeparam>
	/// <param name="node">The found node.</param>
	/// <param name="recursive">If enabled, performs this operation recursively to perform a depth-first search.<br />NOTE: Should probably be breadth-first.</param>
	/// <returns>Whether a node was found.</returns>
	public static bool TryGetNode<T>(this Node n, out T node, bool recursive = false) where T : Node
	{
		for (int i = 0; i < n.GetChildCount(); i++)
		{
			GD.Print($"Checking {n.GetChild(i).Name}...");
			if (n.GetChild(i) is T)
			{
				GD.Print($"Found the {typeof(T)} node!");
				node = (T)n.GetChild(i);
				return true;
			}
			else
			{
				if (recursive)
				{
					if (n.GetChild(i).TryGetNode(out T recurseResult, recursive))
					{
						node = recurseResult;
						return true;
					}
				}
			}
		}

		node = null;
		return false;
	}
GetAllNodes ( )
	/// <summary>
	/// Finds all nodes of type T amongst this node's children.
	/// </summary>
	/// <typeparam name="T">The target node type.</typeparam>
	/// <param name="recursive">If enabled, performs this operation recursively to perform a depth-first search.<br />NOTE: Should probably be breadth-first.</param>
	/// <returns>All children of type T.</returns>
	public static T[] GetAllNodes<T>(this Node n, bool recursive = false) where T : Node
	{
		List<T> nodes = new();
		for (int i = 0; i < n.GetChildCount(); i++)
		{
			if (n.GetChild(i) is T)
			{
				nodes.Add((T)n.GetChild(i));
			}
			else
			{
				if (recursive)
				{
					T[] childResult = n.GetChild(i).GetAllNodes<T>(recursive);
					nodes.AddRange(childResult);
				}
			}
		}
		
		return nodes.ToArray();
	}

An example of how to use these utility functions:

// Retrieve a node reference
Sprite2D node;
this.TryGetNode(out node);

// Retrieve and use node
if (this.TryGetNode(out Sprite2D sprite))
    sprite.visible = false;

// Retrieve all nodes of type
Sprite2D[] sprites = this.GetAllNodes<Sprite2D>();

// Retrieve all nodes of type (including childrens' children)
Sprite2D[] sprites = this.GetAllNodes<Sprite2D>(true);

Put these functions in a static class and you’re good to go!

1 Like

Thnx for the elaborate answer, this is helpful for people who run into the same problem and are looking for a workaround.

However I cannot accept this solution as an answer since it doesn’t quite address why the code doesn’t work.

If no light is shed on the matter within a few days over here I’ll open a ticket on git and relay the results.

1 Like

Try FindChildren("*", typeof(ClassToFind).Name, true, false) instead and see if works

1 Like

Note: This method can be very slow. Consider storing references to the found nodes in a variable.

That’s irrelevant to this topic and serves no purpose without describing why the OP must be mindful of this note.

I was curious, because this seemed correct to me (I’d use nameof(ClassToFind), but it’s basically similar).

It happens that this will only work if ClassToFind is tagged with [GlobalClass][1] here. :thinking: Which makes sense on some level, but should probably be expressed in the documentation.


  1. C# global classes — Godot Engine (stable) documentation in English ↩︎

1 Like

Is this actually the case? That feels unbelievably useless.
I definitely agree with you that this should be a part of the documentation.

1 Like

I figured it out, it’s a real gotcha and, to be frank, wildly unintuitive.

So technically :nerd_face: the docs are correct but require further reading.

Node.find_children docs state:

Finds descendants of this node whose name matches pattern as in String.match, and/or type matches type as in Object.is_class.

If we then look at the docs for Object.is_class:

Returns true if the object inherits from the given class . See also get_class.
Note: This method ignores class_name declarations in the object’s script.

The answer is there in the Note and the docs for Oject.get_class expands on it:

Note: This method ignores class_name declarations. If this object’s script has defined a class_name , the base, built-in class name is returned instead.

So for example:

Say you have made a script for a much cooler button that inherits from Godot’s built-in class “Button” like so:

public partial class CoolerButton: Button { ... }

Then proceed to attach your script to one (or more) of your Buttons. These will then not be found by a node that calls FindChildren("*", typeof(CoolerButton).Name);
Because “CoolerButton” counts as a “script defined class_name” as far as Godot is concerned. In this case the notes say Godot will resort to the build-in classes and will instead use “Button” for the equality check. And “CoolerButton” != “Button”. :man_shrugging:

Note that when iterating over the descendants and you decide to use the C# is operator you’ll find that the descendants you’re trying to find are indeed of the class CoolerButton. :man_facepalming:

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.