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.
Checking the stable version of the documentation...
Сповіщення 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")
)
using Godot;
public partial class MyNode : Node
{
// Allows for recurring operations that don't trigger script logic
// every frame (or even every fixed frame).
public override void _Ready()
{
var timer = new Timer();
timer.Autostart = true;
timer.WaitTime = 0.5;
AddChild(timer);
timer.Timeout += () => GD.Print("This block runs every 0.5 seconds");
}
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
public:
// Allows for recurring operations that don't trigger script logic
// every frame (or even every fixed frame).
virtual void _ready() override {
Timer *timer = memnew(Timer);
timer->set_autostart(true);
timer->set_wait_time(0.5);
add_child(timer);
timer->connect("timeout", callable_mp(this, &MyNode::run));
}
void run() {
UtilityFunctions::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())
using Godot;
public partial class MyNode : Node
{
// Called every frame, even when the engine detects no input.
public void _Process(double delta)
{
if (Input.IsActionJustPressed("ui_select"))
{
GD.Print(delta);
}
}
// Called during every input event. Equally true for _input().
public void _UnhandledInput(InputEvent @event)
{
switch (@event)
{
case InputEventKey:
if (Input.IsActionJustPressed("ui_accept"))
{
GD.Print(GetProcessDeltaTime());
}
break;
}
}
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
public:
// Called every frame, even when the engine detects no input.
virtual void _process(double p_delta) override {
if (Input::get_singleton->is_action_just_pressed("ui_select")) {
UtilityFunctions::print(p_delta);
}
}
// Called during every input event. Equally true for _input().
virtual void _unhandled_input(const Ref<InputEvent> &p_event) override {
Ref<InputEventKey> key_event = event;
if (key_event.is_valid() && Input::get_singleton->is_action_just_pressed("ui_accept")) {
UtilityFunctions::print(get_process_delta_time());
}
}
};
_init, initialization та export
Якщо скрипт ініціалізує власне піддерево вузлів без сцени, цей код має виконуватися в _init(). Інші властивості або незалежні від SceneTree ініціалізації також повинні виконуватися тут.
Примітка
Конструктором є еквівалент C# методу _init() GDScript.
_init() запускається перед _enter_tree() або _ready(), але після того, як скрипт створює та ініціалізує свої властивості. Під час створення екземпляра сцени значення властивостей буде встановлено відповідно до такої послідовності:
Початкове призначення значення: властивості присвоюється значення ініціалізації або значення за замовчуванням, якщо воно не вказано. Якщо сеттер існує, він не використовується.
_init()присвоєння: значення властивості замінюється будь-якими призначеннями, зробленими в_init(), що запускає установщик.Експортоване призначення значення: значення експортованої властивості знову замінюється будь-яким значенням, установленим в інспекторі, що запускає установщик.
# 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!".
using Godot;
public partial class MyNode : Node
{
private string _test = "one";
[Export]
public string Test
{
get { return _test; }
set { _test = $"{value}!"; }
}
public MyNode()
{
// Triggers the setter, changing _test's value from "one" to "two!".
Test = "two";
}
// If you set Test to "three" in the Inspector, it would trigger
// the setter, changing _test's value from "two!" to "three!".
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
String test = "one";
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("get_test"), &MyNode::get_test);
ClassDB::bind_method(D_METHOD("set_test", "test"), &MyNode::set_test);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "test"), "set_test", "get_test");
}
public:
String get_test() { return test; }
void set_test(String p_test) { test = p_test + "!"; }
MyNode() {
// Triggers the setter, changing _test's value from "one" to "two!".
set_test("two");
}
// If you set test to "three" in 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!")
using Godot;
public partial class MyNode : Node
{
private Node _parentCache;
public bool ConnectionCheck()
{
return _parentCache.HasUserSignal("InteractedWith");
}
public override void _Notification(int what)
{
switch ((long)what)
{
case NotificationParented:
_parentCache = GetParent();
if (ConnectionCheck())
{
_parentCache.Connect("InteractedWith", Callable.From(OnParentInteractedWith));
}
break;
case NotificationUnparented:
if (ConnectionCheck())
{
_parentCache.Disconnect("InteractedWith", Callable.From(OnParentInteractedWith));
}
break;
}
}
private void OnParentInteractedWith()
{
GD.Print("I'm reacting to my parent's interaction!");
}
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
Node *parent_cache = nullptr;
void on_parent_interacted_with() {
UtilityFunctions::print("I'm reacting to my parent's interaction!");
}
public:
void connection_check() {
return parent_cache->has_user_signal("interacted_with");
}
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PARENTED:
parent_cache = get_parent();
if (connection_check()) {
parent_cache->connect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
}
break;
case NOTIFICATION_UNPARENTED:
if (connection_check()) {
parent_cache->disconnect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
}
break;
}
}
};