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 для цього ви можете використовувати сигнали.
Як і методи (Callable), сигнали є першокласним типом з Godot 4.0. Це означає, що ви можете передавати їх як аргументи методу безпосередньо без необхідності передавати їх як рядки, що забезпечує краще автозавершення та менш схильне до помилок. Перегляньте довідник класу class_signal, щоб отримати список того, що ви можете робити безпосередньо з типом Signal.
Дивись також
Як згадувалося у вступі, сигнали є версією моделі спостерігача Godot. Ви можете дізнатися більше про це в Шаблони програмування ігор.
Тепер ми будемо використовувати сигнал, для керування рухом іконки Godot з попереднього уроку (Обробка вводу гравця).
Примітка
У цьому проєкті ми будемо дотримуватися правил іменування Godot.
GDScript: Класи (вузли) використовують PascalCase, змінні та функції використовують snake_case, а константи використовують ALL_CAPS (Дивіться Посібник зі стилю GDScript ).
C#: Класи, експортовані змінні та методи використовують PascalCase, приватні поля використовують _camelCase, локальні змінні та параметри використовують camelCase (Дивіться Настанови по стилю C# ). Будьте обережні, вводячи назви методів саме під час підключення сигналів.
Налаштування сцени
Щоб додати кнопку до нашої гри, ми створимо нову сцену, яка включатиме як Button, так і сцену sprite_2d.tscn, яку ми створили в уроці Створення першого сценарію.
Створіть нову сцену, перейшовши до меню .
У доці «Сцена» натисніть кнопку . Це додасть Node2D як наш корінь.
На панелі Файлова система клацніть і перетягніть файл sprite_2d.tscn, який ви зберегли раніше, на Node2D, щоб створити його екземпляр.
Ми хочемо додати ще один вузол як брата/сестрину Sprite2D. Для цього клацніть правою кнопкою миші на Node2D та виберіть .
Знайдіть вузол Button та додайте його.
Вузол за замовчуванням невеликий. Натисніть і перетягніть нижній правий маркер Кнопки у вікні перегляду, щоб змінити її розмір.
Якщо ви не бачите маркерів, переконайтеся, що інструмент вибору активний на панелі інструментів.
Натисніть і перетягніть саму кнопку, щоб наблизити її до спрайту.
Ви також можете написати підпис для кнопки, відредагувавши її властивість Text в Inspector. Введіть Toggle motion.
Ваше дерево сцени та область перегляду мають виглядати так.
Збережіть вашу новостворену сцену як node_2d.tscn, якщо ви цього ще не зробили. Ви можете її запустити натиснувши F6 (Cmd + R на macOS). В даний момент кнопку буде видно, проте при натисканні на неї, нічого не буде відбуватися.
Підключення сигналів в редакторі
Тут ми хочемо підключити сигнал кнопки "pressed" до нашого Sprite2D, і ми хочемо викликати нову функцію, яка вмикатиме та вимикає його рух. Нам потрібно мати скрипт, приєднаний до вузла Sprite2D, що ми зробили в попередньому уроці.
Ви можете підключати сигнали в доці Signals. Виберіть вузол Button і, у правій частині редактора, натисніть на вкладку з назвою Signals поруч з Inspector.
Панель відображає список сигналів, доступних на вибраному вузлі.
Двічі клацніть сигнал "pressed", щоб відкрити вікно підключення вузла.
Там ви можете підключити сигнал до вузла Sprite2D. Вузлу потрібен метод приймача, функція, яку Godot буде викликати, коли Кнопка випромінює сигнал. Редактор створить її для вас. За умовою ми називаємо ці методи зворотного виклику "_on_НазваВузла_назва_сигналу". Тут це буде "_on_Button_pressed".
Примітка
When connecting signals via the editor's Signals dock, you can use two modes. The simple one only allows you to connect to nodes that have a script attached to them and creates a new callback function on them.
Розширений вигляд дозволяє підключатися до будь-якого вузла та будь-якої вбудованої функції, додавати аргументи до зворотного виклику та встановлювати параметри. Ви можете перемикати режим у правому нижньому куті вікна, натиснувши кнопку .
Примітка
Якщо ви використовуєте зовнішній редактор (наприклад, VS Code), ця автоматична генерація коду може не працювати. У цьому випадку вам потрібно підключити сигнал за допомогою коду, як описано в наступному розділі.
Натисніть кнопку , щоб завершити підключення сигналу та перейти до робочої області Script. Ви повинні побачити новий метод із значком підключення у лівому полі.
Якщо натиснути іконку, з’явиться вікно з інформацією про з’єднання. Ця функція доступна лише при підключенні вузлів у редакторі.
Давайте замінимо рядок із ключовим словом pass на код, який перемикає рух вузла.
Наш Sprite2D рухається завдяки коду в функції _process(). Godot надає метод вмикання та вимикання обробки: Node.set_process(). Інший метод класу Node , is_processing(), повертає true, якщо обробка активна. Ми можемо використовувати ключове слово not для інвертування значення.
func _on_button_pressed():
set_process(not is_processing())
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
Ця функція перемикає обробку і, в свою чергу, рух значка вмикається і вимикається при натисканні кнопки.
Перш ніж спробувати гру нам потрібно спростити нашу функцію _process() до самостійного переміщення вузла без очікування на введення гравця. Замініть її наступним кодом, який ми бачили два уроки тому:
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
Ваш повний код sprite_2d.gd має виглядати так.
extends Sprite2D
var speed = 400
var angular_speed = PI
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
func _on_button_pressed():
set_process(not is_processing())
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
Запустіть поточну сцену, натиснувши F6 (Cmd + R на macOS), і натисніть кнопку, щоб побачити запуск і зупинку спрайта.
Підключення сигналів за допомогою коду
Ви можете підключати сигнали за допомогою коду замість того, щоб використовувати редактор. Це необхідно, коли ви створюєте вузли або вставляєте сцени всередину скрипту.
Давайте використаємо тут інший вузол. Godot має вузол таймера Timer, який корисний для реалізації часу зарядки навичок, перезавантаження зброї тощо.
Поверніться до 2D-робочого простору. Ви можете натиснути текст "2D" у верхній частині вікна або скористатися клавішами Ctrl + F1 (Ctrl + Cmd + 1 на macOS).
На панелі Сцена клацніть правою кнопкою мишки на вузлі Sprite2D і додайте новий дочірній вузол. Знайдіть Timer і додайте відповідний вузол. Тепер ваша сцена повинна виглядати так.
Вибравши вузол Timer, перейдіть до Inspector та увімкніть властивість Autostart.
Клацніть піктограму скрипта поруч із Sprite2D, щоб повернутися до робочої області скриптів.
Нам потрібно зробити дві операції для з'єднання вузлів за допомогою коду:
Отримайте посилання на Таймер зі Sprite2D.
Викликати метод
connect()на сигнал Таймера "timeout".
Примітка
Щоб підключитися до сигналу за допомогою коду, потрібно викликати метод connect() сигналу, який ви хочете прослухати. У цьому випадку ми хочемо прослухати сигнал Таймера "timeout".
Ми хочемо підключити сигнал, одразу після створення екземпляра сцени, і ми можемо зробити це за допомогою вбудованої функції Node._ready(), яка автоматично викликається рушієм, щойно вузол буде повністю створений.
Щоб отримати посилання на вузол відносно поточного, використовуємо метод Node.get_node(). Ми можемо зберігати посилання в змінній.
func _ready():
var timer = get_node("Timer")
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
}
Функція get_node() переглядає нащадків Sprite2D і отримує вузли за їх назвою. Наприклад, якщо ви перейменували вузол таймера з "Timer" на "BlinkingTimer" у редакторі, вам доведеться змінити виклик на get_node("BlinkingTimer").
Тепер ми можемо підключити Таймер до Sprite2D у функції _ready().
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_on_timer_timeout)
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
Рядок можна прочитати так: підключаємо сигнал Таймера "timeout" до вузла, до якого приєднаний скрипт. Коли таймер випромінює timeout, ми хочемо викликати функцію _on_timer_timeout(), яку нам потрібно визначити. Давайте додамо її в нижній частині нашого скрипту і використаємо для перемикання видимості нашого спрайта.
Примітка
За домовленістю, ми називаємо ці методи зворотного виклику по конструкції "_on_назва_вузла_назва_сигналу" в GDScript і "OnНазваВузлаНазваСигналу" в C#. Тут це буде "_on_timer_timeout" для GDScript і OnTimerTimeout() для C#.
func _on_timer_timeout():
visible = not visible
private void OnTimerTimeout()
{
Visible = !Visible;
}
Властивість visible контролює видимість нашого вузла. Рядок visible = not visible перемикає значення. Якщо visible є true, то стає false, і навпаки.
Якщо ви зараз запустите сцену Node2D, ви побачите, що спрайт блимає та вимикається з інтервалом в одну секунду.
Завершений скрипт
Ось і все для нашої маленької рухомої і миготливої іконки Godot! Ось повний файл sprite_2d.gd для порівняння.
extends Sprite2D
var speed = 400
var angular_speed = PI
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_on_timer_timeout)
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
func _on_button_pressed():
set_process(not is_processing())
func _on_timer_timeout():
visible = not visible
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
private void OnTimerTimeout()
{
Visible = !Visible;
}
}
Власні сигнали
Примітка
Цей розділ пояснює, як визначити і використовувати власні сигнали, і не має відношення до проекту, створеного на попередніх уроках.
Ви можете визначити власні сигнали в скрипті. Скажімо, ви хочете показати екран завершення гри, коли здоров'я гравця досягає нуля. Для цього ви можете визначити сигнал під назвою "died" або "health_depleted", коли їх здоров'я досягає 0.
extends Node2D
signal health_depleted
var health = 10
using Godot;
public partial class MyNode2D : Node2D
{
[Signal]
public delegate void HealthDepletedEventHandler();
private int _health = 10;
}
Примітка
Оскільки сигнали представляють події, які щойно відбулися, ми зазвичай використовуємо дієслово дії в минулому часі в їх назвах.
Ваші сигнали працюють так само, як і вбудовані: вони відображаються на вкладці Signals, і ви можете підключитися до них, як до будь-яких інших.
Щоб випромінювати сигнал у ваших скриптах, викликайте на сигналі emit().
func take_damage(amount):
health -= amount
if health <= 0:
health_depleted.emit()
public void TakeDamage(int amount)
{
_health -= amount;
if (_health <= 0)
{
EmitSignal(SignalName.HealthDepleted);
}
}
Сигнал може при потребі оголошувати один, або кілька, аргументів. Вкажіть назви аргументів між дужками:
extends Node2D
signal health_changed(old_value, new_value)
var health = 10
using Godot;
public partial class MyNode : Node
{
[Signal]
public delegate void HealthChangedEventHandler(int oldValue, int newValue);
private int _health = 10;
}
Примітка
The signal arguments show up in the editor's Signals dock, and Godot can use them to generate callback functions for you. However, you can still emit any number of arguments when you emit signals. So it's up to you to emit the correct values.
Щоб випромінювати значення разом із сигналом додайте їх як додаткові аргументи до функції emit():
func take_damage(amount):
var old_health = health
health -= amount
health_changed.emit(old_health, health)
public void TakeDamage(int amount)
{
int oldHealth = _health;
_health -= amount;
EmitSignal(SignalName.HealthChanged, oldHealth, _health);
}
Підсумок
Будь-який вузол в Godot випромінює сигнали, коли з ним відбувається щось конкретне, наприклад, натискається кнопка. Інші вузли можуть підключатися до окремих сигналів і реагувати на вибрані події.
Сигнали мають багато застосувань. З ними ви можете реагувати на вузол, що входить, чи виходить, з ігрового світу, на зіткнення, на персонажа, що входить, чи залишає область, на елемент інтерфейсу, що змінює розмір, і багато іншого.
Наприклад, Area2D, у вигляді монети, випромінює сигнал body_entered кожного разу, коли стикається з фізичним тілом гравця, що дозволяє вам знати, що гравець її зібрав.
У наступному розділі, Ваша перша 2D гра, ви створите повну 2D гру і використаєте все, чого дізналися до цих пір, на практиці.