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:
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!
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. Which makes sense on some level, but should probably be expressed in the documentation.
I figured it out, it’s a real gotcha and, to be frank, wildly unintuitive.
So technically 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”.
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.