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 «малювати», він викличе _notification(NOTIFICATION_DRAW).

Деякі з цих сповіщень, наприклад, малювання (draw), корисно замінити у скриптах. Настільки, що Godot поставляє багатьох із них зі спеціальними функціями:

  • _ready(): ПОВІДОМЛЕННЯ_ГОТОВЕ

  • _enter_tree(): NOTIFICATION_ENTER_TREE

  • _exit_tree() : ДЕРЕВО_ВИХОДУ_ПОВІДОМЛЕННЯ

  • _process(delta) : ПРОЦЕС_ПОВІДОМЛЕННЯ

  • _physics_process(delta): ПОВІДОМЛЕННЯ_ФІЗИЧНОГО_ПРОЦЕСУ

  • _draw() : ПОВІДОМЛЕННЯ

Користувачі можуть не розуміти, що сповіщення існують і для інших типів, окрім, наприклад, Node:

  • Object::NOTIFICATION_POSTINITIALIZE: зворотний виклик, який спрацьовує під час ініціалізації об’єкта. Не доступний для скриптів.

  • Object::NOTIFICATION_PREDELETE: зворотний виклик, який спрацьовує перед тим, як рушій видалить об'єкт, тобто "деструктор".

І багато зворотних викликів, які існують у вузлах, не мають спеціальних методів, але все ще є досить корисними.

  • Node::NOTIFICATION_PARENTED: зворотний виклик, який запускається щоразу, коли один додає дочірній вузол до іншого вузла.

  • Node::NOTIFICATION_UNPARENTED: зворотний виклик, який запускається в будь-який час, коли один видаляє дочірній вузол з іншого вузла.

Доступ до всіх цих кастомних сповіщень можна отримати за допомогою універсального методу _notification().

Примітка

Методи в документації, позначені як "virtual" (віртуальні), також призначені для перевизначення у скриптах.

Класичним прикладом є метод _init в Object. Хоча він не має еквівалента NOTIFICATION_*, рушій все одно викликає цей метод. Більшість мов (крім C#) використовують його як конструктор.

Отже, в якій ситуації слід використовувати кожне з цих сповіщень або віртуальні функції?

_process та _physics_process vs. *_input

Використовуйте _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() запускатимуться лише на кадрах, у яких механізм фактично виявив введення.

Так само можна перевірити дії введення в зворотних викликах введення. Якщо потрібно використовувати дельта-час, його можна отримати за потреби з відповідних методів дельта-часу.

# 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, initialization та export

Якщо сценарій ініціалізує власне піддерево вузлів без сцени, цей код має виконуватися в _init(). Інші властивості або незалежні від SceneTree ініціалізації також повинні виконуватися тут.

Примітка

Конструктором є еквівалент C# методу _init() GDScript.

_init() запускається перед _enter_tree() або _ready(), але після того, як сценарій створює та ініціалізує свої властивості. Під час створення екземпляра сцени значення властивостей буде встановлено відповідно до такої послідовності:

  1. Початкове призначення значення: властивості присвоюється значення ініціалізації або значення за замовчуванням, якщо воно не вказано. Якщо сеттер існує, він не використовується.

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

  3. Експортоване призначення значення: значення експортованої властивості знову замінюється будь-яким значенням, установленим в інспекторі, що запускає установщик.

# 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!".

Як наслідок, створення екземпляра сценарію проти сцени може вплинути як на ініціалізацію та на кількість викликів механізму налаштування.

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