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(): NOTIFICATION_EXIT_TREE

  • _process(delta): NOTIFICATION_PROCESS

  • _physics_process(delta): NOTIFICATION_PHYSICS_PROCESS

  • _draw(): NOTIFICATION_DRAW

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

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

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

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

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

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

Універсальний метод _notification() забезпечує доступ до всіх цих користувацьких сповіщень.

Примітка

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

Класичним прикладом є метод _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() присвоєння: значення властивості замінюється будь-якими призначеннями, зробленими в _init(), що запускає установщик.

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