Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Godot 通知

Godot 中的每个对象都实现了 _notification 方法。其目的是允许对象响应可能与之相关的各种引擎级回调。例如,如果引擎想让某个 CanvasItem 去执行“绘制”(draw)操作,它就会去调用 _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

What users might not realize is that notifications exist for types other than Node alone, for example:

而且, 在节点中 确实 存在许多回调, 都没有任何专用方法, 但是它们仍然非常有用.

One can access all these custom notifications from the universal _notification() method.

备注

文档中被标记为“virtual”的方法(虚方法)是为使用脚本覆盖准备的。

一个经典的例子是 Object 中的 _init 方法. 虽然它没有等效的 NOTIFICATION_*, 但是引擎仍然调用该方法. 大多数语言(C#除外)都将其用作构造函数.

那么, 在哪种情况下应该使用这些通知或虚函数呢?

_process vs. _physics_process vs. *_input

Use _process() when one needs a framerate-dependent delta time between frames. If code that updates object data needs to update as often as possible, this is the right place. Recurring logic checks and data caching often execute here, but it comes down to the frequency at which one needs the evaluations to update. If they don't need to execute every frame, then implementing a Timer-timeout loop is another option.

# 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")
    )

Use _physics_process() when one needs a framerate-independent delta time between frames. If code needs consistent updates over time, regardless of how fast or slow time advances, this is the right place. Recurring kinematic and object transform operations should execute here.

While it is possible, to achieve the best performance, one should avoid making input checks during these callbacks. _process() and _physics_process() will trigger at every opportunity (they do not "rest" by default). In contrast, *_input() callbacks will trigger only on frames in which the engine has actually detected the input.

One can check for input actions within the input callbacks just the same. If one wants to use delta time, one can fetch it from the related delta time methods as needed.

# 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. export

If the script initializes its own node subtree, without a scene, that code should execute here. Other property or SceneTree-independent initializations should also run here. This triggers before _ready() or _enter_tree(), but after a script creates and initializes its properties.

脚本具有三种可能在实例化过程中发生的属性分配方法:

# "one" is an "initialized value". These DO NOT trigger the setter.
# If someone set the value as "two" from the Inspector, this would be an
# "exported value". These DO trigger the setter.
export(String) var test = "one" setget set_test

func _init():
    # "three" is an "init assignment value".
    # These DO NOT trigger the setter, but...
    test = "three"
    # These DO trigger the setter. Note the `self` prefix.
    self.test = "three"

func set_test(value):
    test = value
    print("Setting: ", test)

当实例化一个场景时, 将根据以下顺序设置属性值:

  1. 初始值分配:实例化将先赋初值 initialization 或在 _init 方法中赋值。_init 赋值优先级高于 initialization 初值。

  2. 导出值赋值:如果从一个场景而不是脚本中实例化,Godot 将用导出的值替换脚本中定义的初始值。

因此,实例化脚本和场景,将影响初始化 引擎调用 setter 的次数.

_ready vs. _enter_tree vs. NOTIFICATION_PARENTED

When instantiating a scene connected to the first executed scene, Godot will instantiate nodes down the tree (making _init() calls) and build the tree going downwards from the root. This causes _enter_tree() calls to cascade down the tree. Once the tree is complete, leaf nodes call _ready. A node will call this method once all child nodes have finished calling theirs. This then causes a reverse cascade going up back to the tree's root.

When instantiating a script or a standalone scene, nodes are not added to the SceneTree upon creation, so no _enter_tree() callbacks trigger. Instead, only the _init() call occurs. When the scene is added to the SceneTree, the _enter_tree() and _ready() calls occur.

如果需要触发作为节点设置父级到另一个节点而发生的行为, 无论它是否作为在主要/活动场景中的部分发生, 都可以使用 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!")