_ready
2026/4/14大约 6 分钟
最后同步日期:2026-04-16 | Godot 官方原文 — _ready
Node._ready
定义
想象你搬进新家——在所有家具都搬进来、水电都接好之后,你才会正式开始"住"在这里。_ready 就是这个"正式入住"的时刻:当一个节点被添加到场景树中,并且它的所有子节点也都已经进入场景树之后,引擎会自动调用这个方法。
也就是说,_ready 是你"准备就绪"的信号。在这个方法里,你可以放心地:
- 初始化变量(给变量赋初始值)
- 获取其他节点的引用(比如拿到按钮、精灵图、摄像机等)
- 连接信号(让节点之间能互相通信)
- 播放初始动画或音效
一句话总结
_ready 就是节点的"开机自检"——在所有零件(子节点)都装好之后,自动执行一次初始化操作。
函数签名
C#
public override void _Ready()GDScript
func _ready() -> void参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| — | — | — | 此方法没有参数 |
返回值
无返回值(void)。
代码示例
基础用法
最简单的用法——在 _ready 中打印一条消息,验证节点已成功进入场景树:
C#
using Godot;
public partial class MyNode : Node
{
public override void _Ready()
{
// 节点"准备就绪"时自动执行
GD.Print("节点已就绪!");
// 运行结果: 节点已就绪!
// (在 Output 面板中可以看到这行输出)
}
}GDScript
extends Node
func _ready() -> void:
# 节点"准备就绪"时自动执行
print("节点已就绪!")
# 运行结果: 节点已就绪!
# (在 Output 面板中可以看到这行输出)实际场景
在实际开发中,_ready 最常用来做三件事:初始化变量、获取节点引用、连接信号。下面是一个角色控制器的典型写法:
C#
using Godot;
public partial class Player : CharacterBody2D
{
// 导出属性:可在编辑器 Inspector 面板中调整
[Export] public float ExMoveSpeed = 200f;
[Export] public int ExMaxHealth = 100;
// 内部变量:脚本内部使用
private int _currentHealth;
private Sprite2D _sprite;
private AnimationPlayer _animPlayer;
public override void _Ready()
{
// 1. 初始化变量
_currentHealth = ExMaxHealth;
// 2. 获取子节点引用(因为此时子节点都已就绪,所以一定能拿到)
_sprite = GetNode<Sprite2D>("Sprite2D");
_animPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
// 3. 播放初始动画
_animPlayer.Play("idle");
GD.Print($"玩家就绪!速度={ExMoveSpeed}, 血量={_currentHealth}");
// 运行结果: 玩家就绪!速度=200, 血量=100
}
}GDScript
extends CharacterBody2D
# 导出属性:可在编辑器 Inspector 面板中调整
@export var ex_move_speed: float = 200.0
@export var ex_max_health: int = 100
# 内部变量:脚本内部使用
var _current_health: int
var _sprite: Sprite2D
var _anim_player: AnimationPlayer
func _ready() -> void:
# 1. 初始化变量
_current_health = ex_max_health
# 2. 获取子节点引用(因为此时子节点都已就绪,所以一定能拿到)
_sprite = get_node("Sprite2D")
_anim_player = get_node("AnimationPlayer")
# 3. 播放初始动画
_anim_player.play("idle")
print("玩家就绪!速度=%s, 血量=%s" % [ex_move_speed, _current_health])
# 运行结果: 玩家就绪!速度=200.0, 血量=100进阶用法
下面展示一个更完整的场景:在 _ready 中连接信号、访问其他节点的引用,以及使用 RequestReady() 强制重新触发 _ready:
C#
using Godot;
public partial class GameManager : Node
{
// 导出属性
[Export] public PackedScene ExEnemyScene;
// 内部变量
private int _score = 0;
private Label _scoreLabel;
private Timer _spawnTimer;
public override void _Ready()
{
// 1. 获取节点引用
_scoreLabel = GetNode<Label>("HUD/ScoreLabel");
_spawnTimer = GetNode<Timer>("SpawnTimer");
// 2. 连接信号(把 SpawnTimer 的 timeout 信号连到自己的方法上)
_spawnTimer.Timeout += OnSpawnTimerTimeout;
// 3. 更新 UI 显示
UpdateScoreDisplay();
// 4. 启动计时器
_spawnTimer.Start();
GD.Print("GameManager 就绪,敌人生成计时器已启动");
// 运行结果: GameManager 就绪,敌人生成计时器已启动
}
private void OnSpawnTimerTimeout()
{
// 计时器每响一次,生成一个敌人
if (ExEnemyScene != null)
{
var enemy = ExEnemyScene.Instantiate<Node2D>();
GetTree().CurrentScene.AddChild(enemy);
GD.Print("生成敌人: " + enemy.Name);
// 运行结果: 生成敌人: Enemy@SomeName
}
}
private void UpdateScoreDisplay()
{
if (_scoreLabel != null)
{
_scoreLabel.Text = $"分数: {_score}";
}
}
/// <summary>
/// 演示 RequestReady 的用法:
/// 如果一个节点被移出场景树后又重新加入,
/// _ready 默认不会再次调用。
/// 调用 RequestReady() 可以让它在下次进入场景树时重新触发 _ready。
/// </summary>
public void TemporarilyRemoveAndReadd(Node child)
{
// 移除子节点
RemoveChild(child);
// 标记为"下次进入场景树时重新调用 _ready"
child.RequestReady();
// 重新添加,此时 child 的 _Ready() 会再次被调用
AddChild(child);
GD.Print("子节点已移除并重新添加,_Ready 会再次执行");
// 运行结果: 子节点已移除并重新添加,_Ready 会再次执行
}
}GDScript
extends Node
# 导出属性
@export var ex_enemy_scene: PackedScene
# 内部变量
var _score: int = 0
var _score_label: Label
var _spawn_timer: Timer
func _ready() -> void:
# 1. 获取节点引用
_score_label = get_node("HUD/ScoreLabel")
_spawn_timer = get_node("SpawnTimer")
# 2. 连接信号(把 SpawnTimer 的 timeout 信号连到自己的方法上)
_spawn_timer.timeout.connect(_on_spawn_timer_timeout)
# 3. 更新 UI 显示
_update_score_display()
# 4. 启动计时器
_spawn_timer.start()
print("GameManager 就绪,敌人生成计时器已启动")
# 运行结果: GameManager 就绪,敌人生成计时器已启动
func _on_spawn_timer_timeout() -> void:
# 计时器每响一次,生成一个敌人
if ex_enemy_scene:
var enemy = ex_enemy_scene.instantiate()
get_tree().current_scene.add_child(enemy)
print("生成敌人: " + enemy.name)
# 运行结果: 生成敌人: Enemy@SomeName
func _update_score_display() -> void:
if _score_label:
_score_label.text = "分数: %d" % _score
## 演示 request_ready 的用法:
## 如果一个节点被移出场景树后又重新加入,
## _ready 默认不会再次调用。
## 调用 request_ready() 可以让它在下次进入场景树时重新触发 _ready。
func temporarily_remove_and_readd(child: Node) -> void:
# 移除子节点
remove_child(child)
# 标记为"下次进入场景树时重新调用 _ready"
child.request_ready()
# 重新添加,此时 child 的 _ready() 会再次被调用
add_child(child)
print("子节点已移除并重新添加,_ready 会再次执行")
# 运行结果: 子节点已移除并重新添加,_ready 会再次执行注意事项
- 只调用一次:
_ready在节点的整个生命周期中默认只调用一次——就是节点首次进入场景树并且所有子节点也就绪的时候。如果你把节点从场景树中移除后再重新添加回来,_ready不会再次执行,除非你事先调用了RequestReady()。 - 调用顺序:引擎先调用父节点的
_EnterTree(),再调用子节点的_EnterTree(),等所有子节点都进入场景树后,从最深的子节点开始往上依次调用_Ready(),最后才调用父节点的_Ready()。这意味着在父节点的_Ready()中,你可以安全地访问所有子节点——它们一定已经准备好了。 - 获取节点引用的正确位置:因为
_ready执行时所有子节点都已就绪,所以这是获取节点引用(GetNode、get_node)的最佳位置。如果在_EnterTree中获取,子节点可能还没准备好,会返回 null。 - 不要在 _ready 中做耗时操作:
_ready在游戏的第一帧之前执行。如果你在里面写了很重的计算逻辑或长时间的等待,会导致游戏启动卡顿。耗时操作应该放到协程或_Process中异步处理。 - C# 差异:C# 中方法名用 PascalCase(
_Ready),GDScript 中用 snake_case(_ready)。C# 中需要override关键字来重写,GDScript 中只需直接定义同名函数即可。
