Godot4 生命周期与通知
分为两大部分: 内置生命周期虚函数(_init/_ready/_process 等) 通用节点通知 _notification() + 全部 NOTIFICATION 常量,附带触发时机、使用场景、坑点、最佳实践。
一、核心前置概念
- 生命周期函数:GDScript 自动调用的预留虚方法,无需手动绑定,引擎自动按节点树加载 / 运行时序执行。
- 通知 Notification:引擎向节点发送整数信号,统一在
_notification(what)接收,覆盖节点树挂载、父子变更、窗口、物理、渲染、销毁等全事件;所有生命周期函数本质是引擎内部封装了对应通知。 - 执行顺序总览(新建节点→加载→运行→销毁): $1
_init→ NOTIFICATION_ENTER_TREE(_enter_tree) → NOTIFICATION_READY(_ready) → 帧循环 (_process/_physics_process) → 退出树 → 销毁
第一部分:内置生命周期虚函数(全部官方标准方法)
1. _init()
触发时机
脚本实例刚被创建、还未挂载到任何节点树时执行,构造函数。
- 场景编辑器预加载节点、动态
new()创建脚本都会触发; - 此时节点还没有父节点、未进入场景树、节点属性未加载完成。
可用场景
- 初始化纯脚本常量、内部变量、预定义枚举;
- 创建无依赖的基础数据结构(数组、字典、缓存池);
- 定义全局默认配置,不访问节点树、不读取外部节点属性。
禁止操作(高频踩坑)
- 不能用
$路径、get_parent()、get_node()、global_position; - 不能修改子节点、父节点、动画、碰撞层;
- 不能加载资源、实例化场景(资源管理器未就绪)。
示例
var hp_max: int
func _init():
hp_max = 100
var skill_list = ["jump", "attack"]2. _enter_tree()
对应通知:NOTIFICATION_ENTER_TREE
触发时机
节点成功挂载到场景树(SceneTree) 的瞬间:
- 场景实例化加载完成、节点被
add_child()添加进父节点; - 此时已有父节点、可
get_parent(),但节点资源 / 子节点还未全部加载完毕,_ready还没执行。
可用场景
- 注册全局单例、全局信号绑定(如监听全局输入、游戏管理器事件);
- 向全局管理器注册自身实例(玩家、敌人注册进怪物管理器);
- 提前获取父节点引用、缓存父节点变量;
- 动态监听场景树全局事件。
限制
子节点可能还未加载完成,$子节点 大概率返回 null,不要访问子节点。
示例
var parent_mgr: Node3D
func _enter_tree():
parent_mgr = get_parent()
GameManager.player_register(self)
GameManager.connect("game_pause", _on_game_pause)3. _ready()
对应通知:NOTIFICATION_READY
触发时机
节点自身 + 所有子节点全部加载完成、资源解析完毕、进入场景树后最后一步初始化回调。
90% 游戏初始化逻辑写在这里。
可用场景
- 缓存子节点引用:
$CollisionShape3D、$Pivot、动画播放器、射线检测; - 读取导出变量、配置参数、初始化动画、碰撞层、遮罩;
- 绑定子节点本地信号(
$MobDetector.body_entered.connect(_on_mob_found)); - 生成初始道具、初始化 AI 状态机、相机跟随配置;
- 加载本地贴图、音效等绑定到节点的资源。
优势
此时全节点树就绪,$路径、get_child()、global_position 全部可用。
示例
@export float move_speed = 5.0
var pivot: Node3D
func _ready():
pivot = $Pivot
$MobDetector.body_entered.connect(_on_mob_detected)
collision_mask = 1 | 44. _process(delta: float)
对应通知:NOTIFICATION_PROCESS
触发时机
渲染帧循环,每渲染一帧执行一次,帧率不固定(屏幕刷新率 60/144 帧)。
参数 delta:距离上一帧的时间(秒),用于帧速率无关平滑计算。
适用场景
- 视觉、动画、相机插值、UI 渲染、画面平滑旋转;
- 输入检测、鼠标 / 键盘操作(移动方向采集);
- 粒子特效、材质渐变、UI 文字更新;
- 相机跟随、模型平滑旋转插值(你的 Pivot 平滑转向)。
不推荐
物理移动、重力、碰撞检测(帧率波动会导致移动速度忽快忽慢)。
5. _physics_process(delta: float)
对应通知:NOTIFICATION_PHYSICS_PROCESS
触发时机
固定物理步进,默认每秒 60 次,间隔恒定 delta=1/60,不受屏幕帧率影响。
适用场景(物理相关唯一标准位置)
CharacterBody2D/3D角色移动、move_and_slide();- 重力、速度、加速度、刚体力施加;
- 碰撞检测、射线投射、怪物 AI 寻路;
- 所有需要稳定物理计算的逻辑(你的角色移动代码)。
强制规范
角色移动、碰撞、物理逻辑全部写这里,不要放 _process。
6. _input(event: InputEvent)
对应通知:NOTIFICATION_INPUT
触发时机
每次硬件输入事件(按键按下 / 松开、鼠标移动、手柄摇杆)触发。
适用场景
- 一次性按键触发(跳跃、攻击、技能释放,而非持续移动);
- 鼠标点击、右键交互、手柄按键;
- 区分按下 / 松开状态,处理单次触发动作。
区分 _process 输入检测
_input:事件触发一次,适合瞬发技能;_process:持续检测Input.is_action_pressed,适合持续移动。
7. _unhandled_input(event: InputEvent)
对应通知:NOTIFICATION_UNHANDLED_INPUT
触发时机
输入事件没有被任何 UI 控件、输入捕获节点消费时才执行。
适用场景
全局快捷键、游戏全局暂停、ESC 打开菜单,不被 UI 遮挡。
8. _exit_tree()
对应通知:NOTIFICATION_EXIT_TREE
触发时机
节点从场景树移除瞬间(queue_free()、remove_child()、场景切换)。
适用场景
- 解绑全局信号,防止内存泄漏(关键!);
- 从全局管理器注销自身;
- 停止计时器、动画、循环协程;
- 释放手动创建的资源、移除单例引用。
必做规范
所有在 _enter_tree 绑定的全局信号,必须在 _exit_tree disconnect,否则场景销毁后残留信号报错。
9. _process_frame_post_draw() / _process_frame_pre_draw()(渲染回调)
触发时机
渲染管线前后执行,仅 2D / 自定义绘制使用。
pre_draw:绘制前修改材质、画布;post_draw:绘制后叠加自定义图形、后处理。
10. _notification(what: int)
统一接收所有底层引擎通知的总入口,所有上面的生命周期函数本质是引擎对特定 what 值的封装。
下面完整列出全部常用通知常量、触发时机、使用场景。
第二部分:全量 Notification 通知大全(NOTIFICATION_*)
在脚本中统一接收:
func _notification(what: int):
match what:
NOTIFICATION_PARENTED:
print("节点获得父节点")
NOTIFICATION_UNPARENTED:
print("节点失去父节点")
NOTIFICATION_ENTER_TREE:
print("进入场景树,对应_enter_tree")
NOTIFICATION_READY:
print("全部加载完成,对应_ready")一、节点树父子关系通知
1. NOTIFICATION_PARENTED
触发:节点被 add_child() 附加到父节点,早于 _enter_tree
场景:动态获取父节点、监听父节点变更、缓存父组件。
2. NOTIFICATION_UNPARENTED
触发:节点被 remove_child()、父节点销毁、场景切换失去父节点
场景:清空父节点缓存、解绑父节点信号。
3. NOTIFICATION_ENTER_TREE
等价 _enter_tree(),节点加入场景树。
4. NOTIFICATION_EXIT_TREE
等价 _exit_tree(),节点离开场景树。
二、加载就绪通知
5. NOTIFICATION_READY
等价 _ready(),节点 + 所有子节点加载完毕。
三、帧循环更新通知
6. NOTIFICATION_PROCESS
每渲染帧执行,对应 _process(delta)。
7. NOTIFICATION_PHYSICS_PROCESS
固定物理帧执行,对应 _physics_process(delta)。
四、输入通知
8. NOTIFICATION_INPUT
对应 _input(event),接收所有输入事件。
9. NOTIFICATION_UNHANDLED_INPUT
对应 _unhandled_input(event),未被 UI 捕获的输入。
10. NOTIFICATION_INPUT_MOUSE
鼠标单独通知,底层鼠标事件。
五、可见 / 渲染通知
11. NOTIFICATION_VISIBILITY_CHANGED
节点 visible 属性切换时触发
场景:物体显隐切换时关闭 / 开启碰撞、暂停粒子。
12. NOTIFICATION_DRAW
2D 节点自定义绘制回调(draw_line/draw_texture)。
六、窗口 / 游戏生命周期通知(全局通用)
13. NOTIFICATION_WINDOW_FOCUS_IN
游戏窗口获得鼠标焦点(切回游戏)
场景:解除游戏暂停、恢复背景音乐。
14. NOTIFICATION_WINDOW_FOCUS_OUT
窗口失去焦点(切桌面、切浏览器)
场景:自动暂停游戏、降低音效音量。
15. NOTIFICATION_WINDOW_RESIZED
窗口分辨率 / 窗口大小修改
场景:UI 自适应布局、相机视野适配。
16. NOTIFICATION_WINDOW_CLOSE_REQUEST
玩家点击窗口关闭按钮
场景:弹出退出确认弹窗,拦截直接关闭。
七、物理 / 碰撞相关通知
17. NOTIFICATION_PHYSICS_COLLISION
刚体发生碰撞时底层通知(替代部分 Area 信号)
18. NOTIFICATION_BODY_ENTERED / NOTIFICATION_AREA_ENTERED
Area 检测到物体进入底层通知(等价 body_entered 信号)
八、销毁 / 内存释放通知
19. NOTIFICATION_PREDELETE
节点执行 queue_free() 销毁前最后一次通知,销毁前最后执行逻辑
场景:终极资源释放、存档、保存临时数据、彻底清理缓存。
20. NOTIFICATION_DELETED
节点内存被释放,脚本已失效,禁止读写任何节点属性,仅做日志打印。
九、父子层级变更补充通知
21. NOTIFICATION_MOVED_IN_PARENT
节点在父节点内部排序变更(调整层级顺序)
场景:UI 层级切换、渲染顺序变更检测。
十、时间 / 计时器通知
22. NOTIFICATION_TIMER_TIMEOUT
Timer 节点计时结束底层通知。
第三部分:生命周期完整执行时序演示
场景 1:编辑器实例化节点(游戏启动加载场景)
_init()脚本构造NOTIFICATION_PARENTED绑定父节点NOTIFICATION_ENTER_TREE→_enter_tree()- 所有子节点递归加载完成
NOTIFICATION_READY→_ready()- 进入循环:
NOTIFICATION_PROCESS(_process)/NOTIFICATION_PHYSICS_PROCESS(_physics_process) - 切换场景 / 调用 queue_free ()
NOTIFICATION_EXIT_TREE→_exit_tree()NOTIFICATION_PREDELETE销毁前回调NOTIFICATION_DELETED内存释放
场景 2:代码动态创建节点 var player = Player.new(); add_child(player)
_init()add_child()触发NOTIFICATION_PARENTED- 加入场景树
NOTIFICATION_ENTER_TREE→_enter_tree() - 子节点加载完成执行
_ready()
场景 3:节点从父节点移除 remove_child(player)
NOTIFICATION_UNPARENTEDNOTIFICATION_EXIT_TREE→_exit_tree()
第四部分:分场景最佳实践规范
1. 角色 / 物理实体(CharacterBody3D、你的 Player)
_init:仅定义基础数值;_enter_tree:注册进全局游戏管理器、绑定全局暂停信号;_ready:缓存 Pivot、碰撞形状、探测 Area、绑定本地碰撞信号;_process:Pivot 平滑旋转插值、相机跟随、UI 血量更新;_physics_process:移动输入、move_and_slide、重力、射线检测;_input:跳跃、攻击、技能单次按键;_exit_tree:注销全局管理器、断开所有全局信号;NOTIFICATION_WINDOW_FOCUS_OUT:玩家切桌面时停止移动。
2. Area 触发器(MobDetector 探测区域)
_ready:绑定body_entered信号;NOTIFICATION_VISIBILITY_CHANGED:隐藏时关闭碰撞层;_exit_tree:断开探测信号防止内存泄漏。
3. UI 面板、菜单
_input_unhandled:ESC 关闭菜单;NOTIFICATION_WINDOW_RESIZED:自适应布局;NOTIFICATION_WINDOW_CLOSE_REQUEST:拦截关闭,弹出确认框。
4. 全局管理器单例
_enter_tree:监听窗口焦点、全局输入;NOTIFICATION_WINDOW_FOCUS_IN/OUT:全局暂停 / 恢复游戏;NOTIFICATION_PREDELETE:自动保存游戏进度。
第五部分:高频踩坑总结
_init访问子节点报错:节点还未挂载,改用_ready;- 全局信号忘记在
_exit_tree断开:场景销毁后残留信号,下一次加载疯狂报错; - 物理移动写在
_process:帧率波动导致角色速度忽快忽慢,必须放_physics_process; - 销毁时读写节点属性:
NOTIFICATION_DELETED节点已失效,逻辑写NOTIFICATION_PREDELETE; - 混淆 PARENTED / ENTER_TREE:PARENTED 仅代表有父,ENTER_TREE 代表完整进入场景树;
- 在
_ready中动态add_child新节点:新节点会完整走一遍_init→enter_tree→ready,不会阻塞当前脚本。
第六部分:通用标准模板(可直接复制使用)
extends CharacterBody3D
# 构造函数
func _init():
pass
# 进入场景树
func _enter_tree():
pass
# 加载完成初始化
func _ready():
pass
# 渲染帧更新(视觉、相机、平滑插值)
func _process(delta: float) -> void:
pass
# 固定物理帧(移动、碰撞、重力)
func _physics_process(delta: float) -> void:
pass
# 单次按键、鼠标输入
func _input(event: InputEvent) -> void:
pass
# 失去焦点、退出树清理资源
func _exit_tree():
pass
# 统一捕获底层通知
func _notification(what: int):
match what:
NOTIFICATION_PARENTED:
print("获得父节点")
NOTIFICATION_UNPARENTED:
print("失去父节点")
NOTIFICATION_WINDOW_FOCUS_OUT:
print("窗口切出,暂停游戏")
NOTIFICATION_PREDELETE:
print("即将销毁,保存数据")