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()
: ДЕРЕВО_ВИХОДУ_ПОВІДОМЛЕННЯ_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")
)
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()
assignment: the property's value is replaced by any assignments made in_init()
, triggering the 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 someone sets 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 someone sets 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) { return test = p_test; }
MyNode() {
// Triggers the setter, changing _test's value from "one" to "two!".
set_test("two");
}
// If someone sets 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 void _Notification(int what)
{
switch (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;
}
}
};