Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Godot 通知

Every Object in Godot implements a _notification method. Its purpose is to allow the Object to respond to a variety of engine-level callbacks that may relate to it. For example, if the engine tells a CanvasItem to "draw", it will call _notification(NOTIFICATION_DRAW).

在所有这些通知之中,有很多类似“绘制”这样经常需要在脚本中去覆盖的通知,多到 Godot 要提供专用函数的地步:

  • _ready(): NOTIFICATION_READY

  • _enter_tree(): NOTIFICATION_ENTER_TREE

  • _exit_tree(): NOTIFICATION_EXIT_TREE

  • _process(delta): NOTIFICATION_PROCESS

  • _physics_process(delta): NOTIFICATION_PHYSICS_PROCESS

  • _draw(): NOTIFICATION_DRAW

用户可能不会意识到 Node 之外的类型也有通知,例如:

并且,在节点中也有很多很有用的回调,但是这些回调不存在专门的方法。

你可以在通用的 _notification() 方法中访问所有这些自定义通知。

备注

文档中被标记为“virtual”的方法(即虚方法)可以被脚本覆盖重写。

A classic example is the _init method in Object. While it has no NOTIFICATION_* equivalent, the engine still calls the method. Most languages (except C#) rely on it as a constructor.

所以说,应该在哪些情况下使用这些通知或虚函数呢?

_process vs. _physics_process vs. *_input

当需要使用“依赖于帧速率的 delta 时间增量”时,请使用 _process 。如果需要尽可能频繁地更新对象数据,也应该在这里处理。频繁执行的逻辑检查和数据缓存操作,大多数都在这里执行。但也需要注意执行频率,如果不需要每帧都执行,则可以选择用定时器循环来替代。

# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
func _ready():
    var timer = Timer.new()
    timer.autostart = true
    timer.wait_time = 0.5
    add_child(timer)
    timer.timeout.connect(func():
        print("This block runs every 0.5 seconds")
    )

当需要与帧速率无关的时间增量时,请使用 _physics_process 。如果代码需要随着时间的推移进行一致的更新,不管时间推进速度是快还是慢,那么就应该在这里执行代码。频繁执行的运动学和对象变换操作,应在此处执行。

为了获得最佳性能,应尽可能避免在这些回调期间进行输入检查。 _process_physics_process 每次都会触发(默认情况下这些更新回调不会 “休眠”)。相反, *_input 回调仅在引擎实际检测到输入的帧上触发。

在 input 回调中同样可以检查输入动作。如果要使用增量时间,则可以使用相关的增量时间获取方法来获取。

# Called every frame, even when the engine detects no input.
func _process(delta):
    if Input.is_action_just_pressed("ui_select"):
        print(delta)

# Called during every input event.
func _unhandled_input(event):
    match event.get_class():
        "InputEventKey":
            if Input.is_action_just_pressed("ui_accept"):
                print(get_process_delta_time())

_init vs. 初始化 vs. 导出

If the script initializes its own node subtree, without a scene, that code should execute in _init(). Other property or SceneTree-independent initializations should also run here.

备注

The C# equivalent to GDScript's _init() method is the constructor.

_init() triggers before _enter_tree() or _ready(), but after a script creates and initializes its properties. When instantiating a scene, property values will set up according to the following sequence:

  1. Initial value assignment: the property is assigned its initialization value, or its default value if one is not specified. If a setter exists, it is not used.

  2. ``_init()`` assignment: the property's value is replaced by any assignments made in _init(), triggering the setter.

  3. Exported value assignment: an exported property's value is again replaced by any value set in the Inspector, triggering the setter.

# test is initialized to "one", without triggering the setter.
@export var test: String = "one":
    set(value):
        test = value + "!"

func _init():
    # Triggers the setter, changing test's value from "one" to "two!".
    test = "two"

# If someone sets test to "three" from the Inspector, it would trigger
# the setter, changing test's value from "two!" to "three!".

As a result, instantiating a script versus a scene may affect both the initialization and the number of times the engine calls the setter.

_ready、_enter_tree、NOTIFICATION_PARENTED对比

首次实例化一个场景并添加到场景树时,Godot将实例化节点(调用 _init)为树,同时自上而下地调用 _enter_tree。当树构建完成后,再自下而上地调用 _ready,最终反向回到树的顶点。

当实例化脚本或独立的场景时,节点不会在创建时被添加到 SceneTree 中,所以未触发 _enter_tree 回调。而只有 _init 调用发生。当场景被添加到 SceneTree 时,才会调用 _enter_tree_ready

如果需要触发作为节点设置父级到另一个节点而发生的行为, 无论它是否作为在主要/活动场景中的部分发生, 都可以使用 PARENTED 通知. 例如, 这有一个将节点方法连接到其父节点上自定义信号, 而不会失败的代码段。对可能在运行时创建并以数据为中心的节点很有用。

extends Node

var parent_cache

func connection_check():
    return parent_cache.has_user_signal("interacted_with")

func _notification(what):
    match what:
        NOTIFICATION_PARENTED:
            parent_cache = get_parent()
            if connection_check():
                parent_cache.interacted_with.connect(_on_parent_interacted_with)
        NOTIFICATION_UNPARENTED:
            if connection_check():
                parent_cache.interacted_with.disconnect(_on_parent_interacted_with)

func _on_parent_interacted_with():
    print("I'm reacting to my parent's interaction!")