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 方法都是由 Object 實作的。其目的是讓 Object 能夠回應各種可能與其相關的引擎層級回呼。例如,如果引擎告知一個 CanvasItem <class_CanvasItem>`「繪製」,它將會呼叫 ``_notification(NOTIFICATION_DRAW)`

這些通知中,如 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 之外其他型別的節點也有通知:

而且,Node 節點中許多 真實存在 的回呼都沒有專屬的方法,但這些回呼還是很實用。

The universal _notification() method provides access to all these custom notifications.

備註

在說明文件中標記為「虛擬」的方法都是為了讓腳本覆寫而存在的。

一個經典的範例是 Object 中的 _init 方法。雖然它沒有對應的 NOTIFICATION_* ,引擎仍然會呼叫這個方法。大多數程式語言(除了 C#)都依賴它作為建構子。

So, when should you use each of these notifications or virtual functions?

_process vs. _physics_process vs. *_input

Use _process() when you need 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 how often the evaluations need 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 you need 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, you 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.

You can check for input actions within the input callbacks just the same. If you want to use delta time, you 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. 匯出

如果腳本初始化自己的節點子樹(沒有場景),該程式碼應該在 _init() 中執行。其他屬性或獨立於 SceneTree 的初始化也應該在這裡執行。

備註

C# 中對應 GDScript 的 _init() 方法是建構子。

_init() 會在 _enter_tree()_ready() 之前觸發,但在腳本建立並初始化其屬性之後。當實例化場景時,屬性值會依照下列順序設定:

  1. 初始值賦予: 屬性會被賦予其初始化時的值,如果沒有指定初始化值,則會使用預設值。如果存在 setter 函式,則不會被調用。

  2. _init() 賦值: 屬性的值會被 _init() 中進行的任何賦值取代,並觸發 setter。

  3. 匯出值賦予: 匯出屬性的值會再次被屬性檢視器中設定的任何值取代,並觸發 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 you set test to "three" from the Inspector, it would trigger
# the setter, changing test's value from "two!" to "three!".

因此,實例化腳本與實例化場景可能會影響初始化 以及 引擎呼叫 setter 的次數。

_ready vs. _enter_tree vs. NOTIFICATION_PARENTED

當實體化一個連接到首次執行場景的場景時,Godot 會在場景樹中向下實體化 (呼叫 _init ) 並一直從根節點往深層建置。因為這樣,所以 _enter_tree 就是在場景樹中由頂層往深層級呼叫的。建置完整棵樹後,葉上的節點就會呼叫 _ready 。節點會在所有自節點都呼叫完 _ready 後才呼叫自己的 _ready ,因此在呼叫的時候就是反過來從樹最深層往回呼叫到根節點。

當實體化腳本或獨立的場景時,節點並不會在建立時被加到 SceneTree 上,所以不會觸發 _enter_tree 回呼,而只會有 _init 以及之後的 _ready 呼叫。

If you need to trigger behavior that occurs as nodes parent to another, regardless of whether it occurs as part of the main/active scene or not, you can use the PARENTED notification. For example, here is a snippet that connects a node's method to a custom signal on the parent node without failing. Useful on data-centric nodes potentially created at runtime.

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