godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Add `type` to `find_child()` and `find_parent()` #7498

Open TheColorRed opened 1 year ago

TheColorRed commented 1 year ago

Describe the project you are working on

A tug-o-war game

Describe the problem or limitation you are having in your project

I would like to be able to use find_parent() and find_child() by passing in a type. I had to write my own matcher to do so. It seems strange that find_children has this parameter and find_parent and find_child do not.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

This would allow searching and only getting an item that is of a specific type (and possibly has a specific name).

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Similar to how find_children() work:

# The first parent that has the name `my_sprite` and is of type `Sprite2D`
var item = find_parent('my_sprite', 'Sprite2D')
# The first parent that is a `Sprite2D` no matter the name
var item = find_parent('', 'Sprite2D')
# The first child that has the name `my_sprite` and is of type `Sprite2D`
var item = find_child('my_sprite', 'Sprite2D')
# The first child that is a `Sprite2D` no matter the name
var item = find_child('', 'Sprite2D')

If this enhancement will not be used often, can it be worked around with a few lines of script?

This can be worked around by looping over the parents or children and then writing your own check, but isn't convenient. However, find_children() has this parameter whereas these two do not.

Instead of adding new params, new functions could be added that take a callback, then you could do any type of matching:

# Look up the tree
my_node.closest(func(parent): parent.some_value == 10)

# Look down the tree and get one item
my_node.find(func(node): node is Sprite2D)
# Look down the tree and get an array of items
my_node.filter(func(node): node is Sprite2D)

Is there a reason why this should be core and not an add-on in the asset library?

This would modify the gdscript api code.

saminton commented 7 months ago

I would like to suggest an implementation closer to @balintbarna's solution in https://github.com/godotengine/godot-proposals/issues/3661#issuecomment-1003397595

find_child_of_type( type, bool recursive=true, bool owned=true )
find_children_of_type( type, bool recursive=true, bool owned=true )

Where we pass the class rather than a string, I'm guessing this would make it easier to know the return type of the function allowing for @onready var sprite := find_child_of_type(Sprite2D). This solution would also be more in line with the current implementation of find_child and find_children.

Sythelux commented 5 months ago

I'd like to just append here, that I have a similar issue for "pure C#" that would be solved as well if this is solved: https://github.com/godotengine/godot-proposals/issues/5512 (ignore the level order search part for this)

this is good:

find_child_of_type( type, bool recursive=true, bool owned=true ) find_children_of_type( type, bool recursive=true, bool owned=true )

it should've been like this in the first place.

PLyczkowski commented 3 months ago

My proposal ^ was closed so I'll post the summary here:

get_child_by_class to be added to Node

I use Nodes to set up a component based system. I want to add and retrieve components by class to avoid using strings, to keep my project as strictly-typed and refactor friendly as possible.

get_child_by_class would retrieve the first node of specified class from the children, acting like GetComponent in component based systems.

Additionally get_children_by_class would also be useful in other contexts.

This is how it would work:

func increase_entity_damage(entity : Entity):
    var damage_component : Damage = entity.get_child_by_class(Damage)
    damage_component.set_base_amount(damage_component.get_base_amount() + 1)

It is currently possible to create this function manually like this, but obviously this as a part of Node and in c++ would be superior:

func get_child_by_class(parent: Node, class_type: Variant):
    for child in parent.get_children():
        if is_instance_of(child, class_type):
            return child
    return null