Використання сигналів

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

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

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

Примітка

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

Тепер ми будемо використовувати сигнал, для керування рухом іконки Godot з попереднього уроку (Обробка вводу гравця).

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

Щоб додати кнопку до нашої гри ми створимо нову сцену "main", яка буде містити і кнопку, і сцену Sprite.tscn, створену в попередніх уроках.

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

../../_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 в Інспекторі. Введіть "Toggle motion".

../../_images/signals_08_toggle_motion_text.png

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

../../_images/signals_09_scene_setup.png

Збережіть вашу новостворену сцену. Ви можете її запустити натиснувши 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).

На панелі Сцена клацніть правою кнопкою мишки на вузлі Sprite і додайте новий дочірній вузол. Знайдіть Timer і додайте відповідний вузол. Тепер ваша сцена повинна виглядати так.

../../_images/signals_15_scene_tree.png

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

../../_images/signals_18_timer_autostart.png

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

../../_images/signals_16_click_script.png

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

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

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

Примітка

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

Ми хочемо підключити сигнал, одразу після створення екземпляра сцени, і ми можемо зробити це за допомогою вбудованої функції Node._ready(), яка автоматично викликається рушієм, щойно вузол буде повністю створений.

Щоб отримати посилання на вузол відносно поточного, використовуємо метод 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, і навпаки.

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

Завершений скрипт

Ось і все для нашої маленької рухомої і миготливої іконки Godot! Ось повний файл Sprite.gd для порівняння.

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

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

Примітка

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

Ви можете визначити власні сигнали в скрипті. Скажімо, ви хочете показати екран завершення гри, коли здоров'я гравця досягає нуля. Для цього ви можете визначити сигнал під назвою "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 гру і використаєте все, чого дізналися до цих пір, на практиці.