Using signals

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

Це механізм делегування, вбудований у Godot, який дозволяє одному ігровому об’єкту реагувати на зміни в іншому, не посилаючись один на одного. Використання сигналів обмежує з'єднання і забезпечує гнучкість вашого коду.

Наприклад, на екрані може бути шкала життя, яка відображає здоров’я гравця. Коли гравець отримує пошкодження або використовує лікувальне зілля, ви хочете, щоб шкала відображала зміни. Для цього в Godot ви можете використовувати сигнали.

Примітка

Як згадувалося у вступі, сигнали є версією Godot шаблону спостерігача. Детальніше про шаблон спостерігач можна прочитати тут: https://gameprogrammingpatterns.com/observer.html

We will now use a signal to make our Godot icon from the previous lesson (Обробка вводу гравця) move and stop by pressing a button.

Налаштування сцени

To add a button to our game, we will create a new "main" scene which will include both a button and the Sprite.tscn scene that we scripted in previous lessons.

Створіть нову сцену перейшовши до меню Сцена -> Нова Сцена.

../../_images/signals_01_new_scene.png

На панелі Сцена клацніть кнопку 2D Сцена. Це додасть вузол Node2D в якості кореня.

../../_images/signals_02_2d_scene.png

На панелі Файлова система клацніть і перетягніть файл Sprite.tscn, який ви зберегли раніше, на Node2D, щоб створити його екземпляр.

../../_images/signals_03_dragging_scene.png

Ми хочемо додати ще один вузол в якості, так би мовити, рідного брата Спрайта. Для цього клацніть правою кнопкою миші на Node2D і виберіть Додати дочірній вузол.

../../_images/signals_04_add_child_node.png

Знайдіть тип вузла Button і додайте його.

../../_images/signals_05_add_button.png

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

../../_images/signals_06_drag_button.png

Якщо ви не бачите маркерів, переконайтеся, що інструмент вибору активний на панелі інструментів.

../../_images/signals_07_select_tool.png

Натисніть і перетягніть саму кнопку, щоб наблизити її до спрайту.

Ви також можете написати мітку на кнопці, відредагувавши її властивість Text в Інспекторі.

../../_images/signals_08_toggle_motion_text.png

Ваше дерево сцени та область перегляду мають виглядати так.

../../_images/signals_09_scene_setup.png

Save your newly created scene. You can then run it with F6.

Підключення сигналів в редакторі

Тут ми хочемо підключити сигнал кнопки "pressed" до нашого спрайта, і ми хочемо викликати нову функцію, яка вмикатиме та вимикає її рух. Нам потрібно мати скрипт, приєднаний до вузла Sprite, що ми зробили в попередньому уроці.

Ви можете підключати сигнали на панелі Вузол. Виберіть вузол Кнопка і в правій частині редактора натисніть вкладку "Вузол" поруч із Інспектором.

../../_images/signals_10_node_dock.png

Панель відображає список сигналів, доступних на вибраному вузлі.

../../_images/signals_11_pressed_signals.png

Двічі клацніть сигнал "pressed", щоб відкрити вікно підключення вузла.

../../_images/signals_12_node_connection.png

Там ви можете підключити сигнал до вузла Sprite. Вузлу потрібен метод приймача, функція, яку Godot буде викликати, коли кнопка випромінює сигнал. Редактор створить її для вас. За умовою ми називаємо ці методи зворотного виклику "_on_НазваВузла_назва_сигналу". Тут це буде "_on_Button_pressed".

Примітка

При підключенні сигналів через панель редактора Вузол можна використовувати два режими. Простий дозволяє підключатися лише до вузлів, до яких приєднано скрипт, і створює в них нову функцію зворотного виклику.

../../_images/signals_advanced_connection_window.png

Розширений перегляд дозволяє підключатися до будь-якого вузла та будь-якої вбудованої функції, додавати аргументи до зворотного виклику та встановлювати параметри. Ви можете змінити режим у правому нижньому кутку вікна, натиснувши перемикач.

Натисніть кнопку З'єднати, щоб завершити підключення сигналу та перейти до робочої області Script. Ви повинні побачити новий метод зі значком підключення на лівому полі.

../../_images/signals_13_signals_connection_icon.png

Якщо натиснути іконку, з’явиться вікно з інформацією про з’єднання. Ця функція доступна лише при підключенні вузлів у редакторі.

../../_images/signals_14_signals_connection_info.png

Давайте замінимо рядок із ключовим словом pass на код, який перемикає рух вузла.

Наш Спрайт рухається завдяки коду в функції _process(). Godot надає метод вмикання та вимикання обробки: Node.set_process(). Інший метод класу Node (Вузла), is_processing(), повертає true, якщо обробка активна. Ми можемо використовувати ключове слово not для інвертування значення.

func _on_Button_pressed():
    set_process(not is_processing())

Ця функція перемикає обробку і, в свою чергу, рух значка вмикається і вимикається при натисканні кнопки.

Перш ніж спробувати гру нам потрібно спростити нашу функцію _process() до самостійного переміщення вузла без очікування на введення гравця. Замініть її наступним кодом, який ми бачили два уроки тому:

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

Ваш повний код Sprite.gd має виглядати так.

extends Sprite

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

Запустіть сцену і натисніть кнопку, щоб побачити як спрайт стартує та зупиняється.

Підключення сигналів за допомогою коду

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

Давайте використаємо тут інший вузол. Godot має вузол таймера Timer, який корисний для реалізації часу зарядки навичок, перезавантаження зброї тощо.

Поверніться до 2D-робочого простору. Ви можете натиснути текст "2D" у верхній частині вікна або скористатися клавішами Ctrl + F1 (Alt + 1 на macOS).

In the Scene dock, right-click on the Sprite node and add a new child node. Search for Timer and add the corresponding node. Your scene should now look like this.

../../_images/signals_15_scene_tree.png

Вибравши вузол таймера, перейдіть до інспектора та перевірте властивість Autostart.

../../_images/signals_18_timer_autostart.png

Клацніть піктограму скрипта поруч із кнопкою Спрайт, щоб повернутися до робочої області скриптів.

../../_images/signals_16_click_script.png

Нам потрібно зробити дві операції для з'єднання вузлів за допомогою коду:

  1. Отримайте посилання на Таймер зі Спрайту.

  2. Викликати метод Таймера connect().

Примітка

Щоб підключитися до сигналу за допомогою коду, потрібно викликати метод connect() з вузла, який ви хочете прослухати. У цьому випадку ми хочемо прослухати сигнал Таймера "timeout".

We want to connect the signal when the scene is intantiated, and we can do that using the Node._ready() built-in function, which is called automatically by the engine when a node is fully instantiated.

Щоб отримати посилання на вузол відносно поточного, використовуємо метод Node.get_node(). Ми можемо зберігати посилання в змінній.

func _ready():
    var timer = get_node("Timer")

Функція get_node() переглядає нащадків Спрайта і отримує вузли за їх назвою. Наприклад, якщо ви перейменували вузол таймера з "Timer" на "BlinkingTimer" ("Блимаючий таймер") у редакторі, вам доведеться змінити виклик на get_node("BlinkingTimer").

Тепер ми можемо підключити Таймер до Спрайта у функції _ready().

func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")

Рядок можна прочитати так: підключаємо сигнал Таймера "timeout" до вузла, до якого приєднаний скрипт (self). Коли таймер випромінює "timeout", ми хочемо викликати функцію "_on_Timer_timeout", яку нам потрібно визначити. Давайте додамо її в нижній частині нашого скрипту і використаємо для перемикання видимості нашого спрайта.

func _on_Timer_timeout():
    visible = not visible

Властивість visible контролює видимість нашого вузла. Рядок visible = not visible перемикає значення. Якщо visible є true, то стає false, і навпаки.

Complete script

That's it for our little moving and blinking Godot icon demo! Here is the complete Sprite.gd file for reference.

extends Sprite

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_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

Власні сигнали

Примітка

This section is a reference on how to define and use your own signals, and does not build upon the project created in previous lessons.

Ви можете визначити власні сигнали в скрипті. Скажімо, ви хочете показати екран завершення гри, коли здоров'я гравця досягає нуля. Для цього ви можете визначити сигнал під назвою "died" ("помер") або "health_depleted" ("здоров'я_вичерпане"), коли їх здоров'я досягає 0.

extends Node2D

signal health_depleted

var health = 10

Примітка

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

Ваші сигнали працюють так само, як і вбудовані: вони з'являються на панелі Вузол, і ви можете підключитися до них, як і до будь-яких інших.

../../_images/signals_17_custom_signal.png

Щоб випромінювати сигнал у скриптах, використовуйте функцію emit_signal().

func take_damage(amount):
    health -= amount
    if health <= 0:
        emit_signal("health_depleted")

Сигнал може при потребі оголошувати один, або кілька, аргументів. Вкажіть назви аргументів між дужками:

extends Node

signal health_changed(old_value, new_value)

Примітка

Аргументи сигналу відображаються у панелі вузла редактора, і Godot може використовувати їх для створення функцій зворотного виклику для вас. Однак ви все одно можете випромінювати будь-яку кількість аргументів, разом із сигналами. Ви повинні випромінювати правильні значення.

Щоб випромінювати значення разом із сигналом додайте їх як додаткові аргументи до функції emit_signal():

func take_damage(amount):
    var old_health = health
    health -= amount
    emit_signal("health_changed", old_health, health)

Підсумок

Будь-який вузол в Godot випромінює сигнали, коли з ним відбувається щось конкретне, наприклад, натискається кнопка. Інші вузли можуть підключатися до окремих сигналів і реагувати на вибрані події.

Сигнали мають багато застосувань. З ними ви можете реагувати на вузол, що входить, чи виходить, з ігрового світу, на зіткнення, на персонажа, що входить, чи залишає область, на елемент інтерфейсу, що змінює розмір, і багато іншого.

Наприклад, Area2D, у вигляді монети, випромінює сигнал body_entered кожного разу, коли стикається з фізичним тілом гравця, що дозволяє вам знати, що гравець її зібрав.

У наступному розділі, Ваша перша 2D гра, ви створите повну 2D гру і використаєте все, чого дізналися до цих пір, на практиці.