Node.find_child
最后同步日期:2026-04-16 | Godot 官方原文 — Node.find_child
Node.find_child
定义
find_child 就像在一个大柜子里按名字找东西——你给它一个"名字模式",它会帮你从当前节点的所有后代里找出第一个名字匹配的子节点。
打个更具体的比方:想象你的场景树是一栋多层大楼,当前节点是大楼的入口。find_child 就像一个"寻人广播",你喊一个名字(还支持模糊搜索,比如"张*"表示所有姓张的人),它就逐层逐层地去找,找到第一个匹配的就告诉你位置。
与 get_node 不同的是:get_node 需要你提供精确的路径(比如 "UI/Panel/Button"),而 find_child 只需要一个名字模式就能搜索,不需要你知道完整路径。这在节点层级经常变化、或者你只想按名字模糊查找时非常方便。
函数签名
public Node FindChild(string pattern, bool recursive = true, bool owned = true)find_child(pattern: String, recursive: bool = true, owned: bool = true) -> Node参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| pattern | String(C# 中为 string) | 是 | 要匹配的节点名字模式。支持通配符:* 匹配任意多个字符,? 匹配单个字符。例如 "Health*" 能匹配 "HealthBar" 和 "HealthLabel" |
| recursive | bool | 否 | 是否递归搜索所有后代。true(默认)= 搜索所有子孙节点;false = 只搜索直接子节点(不搜索孙子节点) |
| owned | bool | 否 | 是否只搜索"拥有"的节点。true(默认)= 只搜索由本节点实例化的子节点(忽略通过 add_child 从外部添加的、且 Owner 不是本节点的节点);false = 搜索所有后代 |
返回值
返回第一个名字匹配 pattern 的后代节点(Node 类型)。如果没有找到任何匹配的节点,返回 null。
提示
"第一个"指的是场景树中深度优先遍历找到的第一个匹配节点,不一定是场景编辑器中排列在最上面的那个。如果你需要找所有匹配的节点,可以结合 GetChildren() 遍历使用。
代码示例
基础用法
假设你的场景树结构如下:
Player(当前节点)
├── Sprite2D
├── CollisionShape2D
└── UI
├── HealthBar
└── ScoreLabel用 find_child 按名字查找节点:
public override void _Ready()
{
// 精确匹配:查找名叫 "HealthBar" 的节点
var healthBar = FindChild("HealthBar");
GD.Print(healthBar != null ? "找到了!" : "没找到");
// 运行结果: 找到了!
// 通配符匹配:查找所有以 "Health" 开头的节点
var health = FindChild("Health*");
GD.Print(health?.Name);
// 运行结果: HealthBar
// 问号匹配:查找 "Score?????" (Score + 恰好 5 个字符)
var score = FindChild("ScoreLabel");
GD.Print(score?.Name);
// 运行结果: ScoreLabel
// 查找一个不存在的节点
var notFound = FindChild("Invisible");
GD.Print(notFound == null);
// 运行结果: True
}func _ready():
# 精确匹配:查找名叫 "HealthBar" 的节点
var health_bar = find_child("HealthBar")
print("找到了!" if health_bar != null else "没找到")
# 运行结果: 找到了!
# 通配符匹配:查找所有以 "Health" 开头的节点
var health = find_child("Health*")
print(health.name if health else "null")
# 运行结果: HealthBar
# 精确匹配:查找 "ScoreLabel"
var score = find_child("ScoreLabel")
print(score.name if score else "null")
# 运行结果: ScoreLabel
# 查找一个不存在的节点
var not_found = find_child("Invisible")
print(not_found == null)
# 运行结果: True实际场景
在一个角色控制器中,动态查找血条、动画组件和碰撞体等子节点——不用写死路径,即使场景结构调整了代码也不用改:
public partial class Player : CharacterBody2D
{
private ProgressBar _healthBar;
private AnimatedSprite2D _animatedSprite;
private CollisionShape2D _collisionShape;
[Export] public int ExMaxHealth = 100;
private int _currentHealth;
public override void _Ready()
{
_currentHealth = ExMaxHealth;
// 用 find_child 查找子节点,不依赖精确路径
_healthBar = FindChild("HealthBar") as ProgressBar;
_animatedSprite = FindChild("Animated*") as AnimatedSprite2D;
_collisionShape = FindChild("Collision*") as CollisionShape2D;
// 验证查找结果
GD.Print($"血条: {(_healthBar != null ? _healthBar.Name : "未找到")}");
// 运行结果: 血条: HealthBar
GD.Print($"动画: {(_animatedSprite != null ? _animatedSprite.Name : "未找到")}");
// 运行结果: 动画: AnimatedSprite2D
GD.Print($"碰撞: {(_collisionShape != null ? _collisionShape.Name : "未找到")}");
// 运行结果: 碰撞: CollisionShape2D
// 初始化血条
if (_healthBar != null)
{
_healthBar.MaxValue = ExMaxHealth;
_healthBar.Value = _currentHealth;
GD.Print($"血条已初始化: {_healthBar.Value}/{_healthBar.MaxValue}");
// 运行结果: 血条已初始化: 100/100
}
}
}extends CharacterBody2D
@export var max_health: int = 100
var _health_bar: ProgressBar
var _animated_sprite: AnimatedSprite2D
var _collision_shape: CollisionShape2D
var _current_health: int
func _ready():
_current_health = max_health
# 用 find_child 查找子节点,不依赖精确路径
_health_bar = find_child("HealthBar")
_animated_sprite = find_child("Animated*")
_collision_shape = find_child("Collision*")
# 验证查找结果
print("血条: %s" % (_health_bar.name if _health_bar else "未找到"))
# 运行结果: 血条: HealthBar
print("动画: %s" % (_animated_sprite.name if _animated_sprite else "未找到"))
# 运行结果: 动画: AnimatedSprite2D
print("碰撞: %s" % (_collision_shape.name if _collision_shape else "未找到"))
# 运行结果: 碰撞: CollisionShape2D
# 初始化血条
if _health_bar:
_health_bar.max_value = max_health
_health_bar.value = _current_health
print("血条已初始化: %s/%s" % [_health_bar.value, _health_bar.max_value])
# 运行结果: 血条已初始化: 100/100进阶用法
控制 recursive 和 owned 参数来精确限定搜索范围。同时演示如何安全地处理返回 null 的情况:
public partial class LevelManager : Node
{
public override void _Ready()
{
// 构建测试场景树:
// LevelManager(当前节点)
// ├── Enemy1
// │ └── HitBox
// ├── Enemy2
// │ └── HitBox
// └── Spawner
var enemy1 = new Node();
enemy1.Name = "Enemy1";
var hitBox1 = new Node();
hitBox1.Name = "HitBox";
enemy1.AddChild(hitBox1);
AddChild(enemy1);
var enemy2 = new Node();
enemy2.Name = "Enemy2";
var hitBox2 = new Node();
hitBox2.Name = "HitBox";
enemy2.AddChild(hitBox2);
AddChild(enemy2);
var spawner = new Node();
spawner.Name = "Spawner";
AddChild(spawner);
// ① recursive = true(默认):递归搜索所有后代
var result1 = FindChild("HitBox", recursive: true);
GD.Print($"递归搜索 HitBox: {result1?.GetPath()}");
// 运行结果: 递归搜索 HitBox: /root/LevelManager/Enemy1/HitBox
// ② recursive = false:只搜索直接子节点
var result2 = FindChild("HitBox", recursive: false);
GD.Print($"仅直接子节点搜索 HitBox: {(result2 != null ? result2.Name : "null")}");
// 运行结果: 仅直接子节点搜索 HitBox: null
// 因为 HitBox 是孙子节点,不是 LevelManager 的直接子节点
// ③ 通配符在 recursive=false 中的用法
var result3 = FindChild("Enemy*", recursive: false);
GD.Print($"通配符搜索 Enemy*: {result3?.Name}");
// 运行结果: 通配符搜索 Enemy*: Enemy1
// 找到第一个匹配的 Enemy1
// ④ 安全处理 null:永远检查返回值
var unknown = FindChild("NotExistent");
if (unknown == null)
{
GD.Print("节点未找到,执行降级逻辑");
// 运行结果: 节点未找到,执行降级逻辑
}
else
{
GD.Print(unknown.Name);
}
}
}extends Node
func _ready():
# 构建测试场景树:
# LevelManager(当前节点)
# ├── Enemy1
# │ └── HitBox
# ├── Enemy2
# │ └── HitBox
# └── Spawner
var enemy1 = Node.new()
enemy1.name = "Enemy1"
var hit_box1 = Node.new()
hit_box1.name = "HitBox"
enemy1.add_child(hit_box1)
add_child(enemy1)
var enemy2 = Node.new()
enemy2.name = "Enemy2"
var hit_box2 = Node.new()
hit_box2.name = "HitBox"
enemy2.add_child(hit_box2)
add_child(enemy2)
var spawner = Node.new()
spawner.name = "Spawner"
add_child(spawner)
# ① recursive = true(默认):递归搜索所有后代
var result1 = find_child("HitBox", true)
print("递归搜索 HitBox: %s" % result1.get_path() if result1 else "null")
# 运行结果: 递归搜索 HitBox: /root/LevelManager/Enemy1/HitBox
# ② recursive = false:只搜索直接子节点
var result2 = find_child("HitBox", false)
print("仅直接子节点搜索 HitBox: %s" % (result2.name if result2 else "null"))
# 运行结果: 仅直接子节点搜索 HitBox: null
# 因为 HitBox 是孙子节点,不是 LevelManager 的直接子节点
# ③ 通配符在 recursive=false 中的用法
var result3 = find_child("Enemy*", false)
print("通配符搜索 Enemy*: %s" % result3.name if result3 else "null")
# 运行结果: 通配符搜索 Enemy*: Enemy1
# 找到第一个匹配的 Enemy1
# ④ 安全处理 null:永远检查返回值
var unknown = find_child("NotExistent")
if unknown == null:
print("节点未找到,执行降级逻辑")
# 运行结果: 节点未找到,执行降级逻辑
else:
print(unknown.name)注意事项
只返回第一个匹配结果:如果有多个子节点的名字都能匹配
pattern,find_child只返回场景树中深度优先遍历找到的第一个。如果你需要所有匹配的节点,需要自己用GetChildren()递归遍历。必须检查 null:找不到匹配节点时返回
null,直接对返回值调用属性或方法会导致报错(C# 抛出NullReferenceException,GDScript 报脚本错误)。务必在使用前检查是否为 null。通配符语法:
pattern参数支持类似文件系统的通配符——*匹配零个或多个任意字符,?匹配恰好一个任意字符。例如"HP*"匹配"HPBar"和"HPLabel","HP?"只匹配"HP1"这种恰好三个字符的名字。性能提示:
find_child每次调用都会遍历场景树来搜索节点,时间复杂度与后代节点数量成正比。在_Process或_PhysicsProcess中频繁调用会造成性能问题。建议在_Ready()中调用一次,把结果缓存到变量中反复使用。与
get_node的区别:get_node需要精确路径(如"UI/Panel/Button"),速度更快但路径写死了;find_child只需名字模式,更灵活但速度稍慢。如果路径确定不变,优先用get_node。owned 参数的含义:在 Godot 中,每个节点都有一个
Owner属性,指向"负责管理这个节点"的祖先节点。在场景编辑器中保存的场景,根节点的Owner通常是场景根节点自身。owned = true时只搜索Owner是当前节点的后代,owned = false搜索所有后代。大多数情况下保持默认值true即可。C# 差异:C# 中方法名用 PascalCase(
FindChild),GDScript 中用 snake_case(find_child)。C# 中 string 类型首字母小写(string),这是 C# 语言的关键字,与 Godot 无关。
