Керування користувацьким інтерфейсом з коду

Вступ

In this tutorial, you will connect a character to a life bar and animate the health loss.

../../_images/lifebar_tutorial_final_result.gif

Here's what you'll create: the bar and the counter animate when the character takes a hit. They fade when it dies.

Ви навчитеся:

  • How to connect a character to a GUI with signals

  • керувати графічним інтерфейсом з допомогою GDscript

  • анімувати шкалу здоров'я з допомогою вузла Tween node

Якщо ви хочете навчитися налаштовувати інтерфейс, то перегляньте уроки по інтерфейсу користувача:

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

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

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

Примітка

Сигнали є версією Godot шаблону Observer. Вони дозволяють нам надсилати якесь повідомлення. Інші вузли можуть підключитися до об'єкта, який випромінює сигнал і отримувати інформацію. Це потужний інструмент, який ми часто будемо використовувати для інтерфейсу користувача та систем досягнення. Ви можете не використовувати їх скрізь. Зв'язок між двома вузлами додає деяке сполучення між ними. Коли таких прив'язок багато, ними стає важко керувати. Для отримання додаткової інформації перегляньте відео уроки по сигналах на GDquest.

Завантажте та вивчіть стартовий проєкт

Завантажте проект Godot: ui_code_life_bar.zip. Він містить усі ресурси та скрипти, необхідні для початку роботи. Розпакуйте архів .zip, щоб отримати дві теки: start і end.

Завантажте проект start у Godot. На панелі Файлова система двічі клацніть на LevelMockup.tscn, щоб відкрити її. Це макет RPG гри, де 2 персонажі стикаються один з одним. Рожевий ворог атакує і наносить пошкодження зеленому квадрату через регулярні проміжки часу, аж до його загибелі. Не соромтеся спробувати гру: основна бойова механіка вже працює. Але оскільки персонаж не пов'язаний зі шкалою здоров'я, GUI нічого не робить.

Примітка

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

Сцена містить спрайт з фоном гри, графічний інтерфейс та два персонажі.

../../_images/lifebar_tutorial_life_bar_step_tut_LevelMockup_scene_tree.png

Дерево сцени із графічним інтерфейсом, призначеним для відображення його нащадків

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

onready var number_label = $Bars/LifeBar/Count/Background/Number
onready var bar = $Bars/LifeBar/TextureProgress
onready var tween = $Tween
public class Gui : MarginContainer
{
    private Tween _tween;
    private Label _numberLabel;
    private TextureProgress _bar;

    public override void _Ready()
    {
        // C# doesn't have an onready feature, this works just the same.
        _bar = (TextureProgress) GetNode("Bars/LifeBar/TextureProgress");
        _tween = (Tween) GetNode("Tween");
        _numberLabel = (Label) GetNode("Bars/LifeBar/Count/Background/Number");
    }
}
  • number_label відображає кількість здоров'я у вигляді числа. Це вузол Label

  • bar - це сама шкала здоров'я. Це вузол TextureProgress

  • tween це вузол у стилі компонента, який може анімувати та керувати будь-яким значенням, або методом, з будь-якого іншого вузла

Примітка

Проект використовує просту організацію, яка підходить для невеликих ігор.

У корені проекту, в теці res:// ви знайдете LevelMockup. Це головна ігрова сцена і та, з якою ми будемо працювати. Усі компоненти, що складають гру, знаходяться у теці scenes/. Тека assets/ містить ігрові спрайти і шрифт для лічильника HP. У теці scripts/ ви знайдете скрипти ворога, гравця та контролера графічного інтерфейсу.

Клацніть піктограму редагування сцени праворуч від вузла в дереві сцени, щоб відкрити сцену в редакторі. Ви побачите, що LifeBar та EnergyBar є під-сценами.

../../_images/lifebar_tutorial_Player_with_editable_children_on.png

Дерево сцени, зі сценою гравця, встановленою для відображення його нащадків

Налаштування Lifebar (Шкали здоров'я) за допомогою max_health (Максимальне здоров'я) Player

Ми мусимо якось сказати GUI, який у гравця поточний стан здоров'я, щоб оновити текстуру шкали здоров'я та відобразити залишок здоров'я на лічильнику HP у верхньому лівому куті екрана. Для цього ми надсилаємо здоров'я гравця в графічний інтерфейс кожного разу, коли він отримує пошкодження. Потім графічний інтерфейс оновить вузли Lifebar і Number відносно цього значення.

Ми могли б зупинитися на відображенні числа, але нам потрібно ініціалізувати max_value шкали для її оновлення в правильних пропорціях. Таким чином перший крок - сказати GUI який max_health має зелений персонаж (Гравець).

Порада

Шкала, TextureProgress, за замовчуванням має max_value 100. Якщо вам не потрібно відображати здоров'я персонажа цифрою, вам не потрібно змінювати його властивість max_value. Замість цього ви надсилаєте відсоток від Player до GUI: health / max_health * 100.

../../_images/lifebar_tutorial_TextureProgress_default_max_value.png

Клацніть піктограму скрипта праворуч від GUI на панелі Сцена, щоб відкрити його скрипт. У функції _ready, ми будемо зберігати max_health нашого Player в новій змінній і використовувати її, щоб встановити max_value bar:

func _ready():
    var player_max_health = $"../Characters/Player".max_health
    bar.max_value = player_max_health
public override void _Ready()
{
    // Add this below _bar, _tween, and _numberLabel.
    var player = (Player) GetNode("../Characters/Player");
    _bar.MaxValue = player.MaxHealth;
}

Давайте розберемося. $"../Characters/Player" це скорочення, яке піднімається на один вузол вище по дереві сцени і виймає звідти вузол Characters/Player. Це дає нам доступ до вузла. Друга частина, .max_health, дає доступ до вузла гравця max_health.

Другий рядок присвоює це значення bar.max_value. Ви можете поєднати два рядки в один, але нам потрібно буде скористатися player_max_health знову далі в уроці.

Player.gd встановлює health в max_health на початку гри, так що ми могли б працювати з ним. Чому ми використовуємо max_health? Є дві причини:

У нас немає гарантії, що health завжди буде рівною max_health: майбутня версія гри може завантажити рівень, в якому гравець вже втратив певну кількість здоров'я.

Примітка

Коли ви відкриваєте сцену в грі, Godot створює вузли по черзі, дотримуючись порядку їх розміщення в Сцені, зверху вниз. GUI та Player не є частиною однієї гілки вузла. Щоб переконатися, що вони обидва існують, коли ми отримуємо доступ до них, ми повинні використовувати функцію _ready. Godot викличе _ready вже після завантаження всіх вузлів, перед початком гри. Це ідеальна функція для того, щоб налаштувати все і підготувати ігрову сесію. Дізнайтеся більше про _ready: Написання скриптів (продовження)

Оновлення здоров'я з допомогою сигнала, коли гравець отримує удар

Наш графічний інтерфейс готовий отримувати оновлення значень health від Player. Для цього ми будемо використовувати сигнали.

Примітка

Існує багато корисних вбудованих сигналів, таких як enter_tree та exit_tree, які випромінюють всі вузли, коли вони, відповідно, створюються та знищуються. Ви також можете створити власний сигнал за допомогою ключового слова signal. На вузлі Player ви знайдете два сигнали, які ми створили для вас: died (помер) і health_changed (зміна здоров'я).

Чому ми не отримуємо безпосередньо вузол Player у функції _process та не дивимось на значення здоров'я? Доступ до вузлів таким чином створює щільне з'єднання між ними. Якщо ви зробите це економно, це може спрацювати. Але по мірі збільшення вашої гри у вас може ставати все більше зв'язків. Якщо ви отримуєте вузли таким чином, ваші зв'язки швидко стануть заплутаними. Більше того: вам потрібно постійно відстежувати зміни стану у функції _process. Ця перевірка відбувається 60 разів на секунду, і ви, швидше за все, погіршите гру через порядок виконання коду.

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

Примітка

Шаблон спостерігача Observer, від якого походить сигнали, все ще додає трохи зв'язки між гілками вузлів. Але це зазвичай легше і безпечніше, ніж отримати доступ до вузлів безпосередньо для спілкування між двома окремими класами. Для батьківського вузла може бути нормально отримати значення від його нащадків. Але вам краще віддати перевагу сигналам, якщо ви працюєте з двома окремими гілками. Прочитайте шаблони програмування ігор для отримання додаткової інформації Observer шаблон. Повна книга доступна в Інтернеті безкоштовно.

Зважаючи на це, давайте підключимо GUI до Player. Клацніть на вузлі Player в сцені, щоб вибрати його. Перейдіть до Інспектора та натисніть на вкладку Вузол. Це місце де підключаються вузли для прослуховування обраного вами.

У першому розділі перелічені спеціальні сигнали, визначені у Player.gd:

  • died випромінюється, коли персонаж помер. Ми скористаємося ним, щоб приховати інтерфейс користувача.

  • health_changed випромінюється, коли персонаж отримує удар.

../../_images/lifebar_tutorial_health_changed_signal.png

Ми підключаємось до сигналу health_changed

Виберіть health_changed і натисніть кнопку Приєднати в правому нижньому куті, щоб відкрити вікно З'єднання сигналу з методом. Ліворуч ви можете вибрати вузол, який буде прослуховувати цей сигнал. Виберіть вузол GUI. Права частина екрана дозволяє упакувати додаткові значення за допомогою сигналу. Про це ми вже подбали в Player.gd. Як правило, я рекомендую не додавати занадто багато аргументів, використовуючи це вікно, оскільки це менш зручно, ніж з коду.

../../_images/lifebar_tutorial_connect_signal_window_health_changed.png

Вікно З'єднання сигналу з обраним вузлом GUI

Порада

Ви можете з'єднати вузли з коду. Однак підключення з редактора має дві переваги:

  1. Godot може записати нові функції зворотного виклику для вас у підключеному скрипті

  2. На панелі Сцена поруч із вузлом, який випромінює сигнал, з’являється піктограма випромінювача

У нижній частині вікна ви знайдете шлях до обраного вами вузла. Нас цікавить другий ряд під назвою "Метод у вузлі". Це метод на вузлі GUI, який викликається, коли сигнал випромінюється. Цей метод отримує значення, надіслані разом із сигналом, і дозволяє обробити їх. (Якщо ви подивитеся праворуч, є перемикач "Зробити функцію", який за замовчуванням увімкнено. ((Нема в версії Godot 3.2))) Клацніть кнопку З'єднати в нижній частині вікна. Godot створює метод всередині вузла GUI. Одразу відкривається редактор скриптів з курсором всередині нової функції _on_Player_health_changed.

Примітка

При підключенні вузлів з редактора, Godot генерує ім'я методу по такій схемі: _on_НазваВипромінювача_назва_сигнала. Якщо ви вже написали метод, параметр "Зробити функцію" збереже його. Ви можете замінити ім'я на інше.

../../_images/lifebar_tutorial_godot_generates_signal_callback.png

Godot пише для вас метод зворотного виклику і дає його вам

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

func _on_Player_health_changed(player_health):
    pass
public void OnPlayerHealthChanged(int playerHealth)
{
}

Примітка

Движок не перетворює PascalCase в snake_case, для прикладів C# ми будемо використовувати PascalCase для імен методів, і camelCase для параметрів методу, дотримуючись таким чином офіційних умов іменування в C#.

../../_images/lifebar_tutorial_player_gd_emits_health_changed_code.png

У Player.gd, коли Player випромінює сигнал health_changed, він також надсилає значення його здоров'я

Всередині _on_Player_health_changed викличемо другу функцію під назвою update_health і передамо їй змінну player_health.

Примітка

Ми могли б безпосередньо оновити значення здоров’я на LifeBar та Number. Існує дві причини використовувати цей метод:

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

  2. Ми використаємо цей метод трохи пізніше

Створіть новий update_health метод нижче _on_Player_health_changed. Він приймає new_value в якості єдиного аргумента:

func update_health(new_value):
    pass
public void UpdateHealth(int health)
{
}

Цей метод буде:

  • встановлювати text вузла Number на new_value конвертоване в текст

  • встановлювати value в TextureProgress на new_value

func update_health(new_value):
    number_label.text = str(new_value)
    bar.value = new_value
public void UpdateHealth(int health)
{
    _numberLabel.Text = health.ToString();
    _bar.Value = health;
}

Порада

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

Також викличте update_health в кінці функції _ready, щоб ініціалізувати text вузла Number з правильним значенням на початку гри. Натисніть F5, щоб перевірити гру: шкала здоров'я оновлюється з кожною атакою!

../../_images/lifebar_tutorial_LifeBar_health_update_no_anim.gif

І Number, і TextureProgress оновлюються коли Player отримує удар

Анімація втрати здоров'я за допомогою вузла Tween

Наш інтерфейс функціональний, але він може використовувати деяку анімацію. Це хороша можливість познайомитися з вузлом Tween, важливим інструментом для анімації властивостей. Tween анімує все, що завгодно, від початку до кінця, протягом певної тривалості. Наприклад, він може анімувати здоров'я TextureProgress від поточного до нового значення health пошкодженого Player.

Сцена GUI вже містить вузол Tween, який було збережено в змінній tween. Давайте зараз скористаємося цим. Ми повинні внести деякі зміни в update_health.

Ми будемо використовувати метод вузла Tween interpolate_property. Він бере сім аргументів:

  1. Посилання на вузол, властивість якого належить анімувати

  2. Ідентифікатор властивості в виді тексту

  3. Вихідне значення

  4. Кінцеве значення

  5. Тривалість анімації в секундах

  6. Тип переходу

  7. Послаблення для використання в поєднанні з рівнянням.

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

Клацніть піктограму скрипту поруч із вузлом GUI, щоб відкрити його знову. Вузол Number потребує тексту, щоб оновитися, а Bar потребує десяткового, або цілого числа. Ми можемо використовувати interpolate_property для анімації числа, але не для тексту напряму. Ми будемо використовувати його для анімації нової змінної GUI під назвою animated_health.

У верхній частині скрипту визначте нову змінну, назвіть її animated_health та встановіть її значення на 0. Поверніться назад до методу update_health та очистіть його вміст. Давайте анімуємо значення animated_health. Викличте метод interpolate_property вузла Tween:

func update_health(new_value):
    tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6)
// Add this to the top of your class.
private float _animatedHealth = 0;

public void UpdateHealth(int health)
{
    _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
        Tween.EaseType.In);
}

Давайте розберемо цей виклик:

tween.interpolate_property(self, "animated_health", ...

Ми націлюємося на animated_health у self, тобто в вузлі GUI. interpolate_property вузла Tween приймає ім'я властивості у вигляді тексту. Ось чому ми пишемо "animated_health".

... _health", animated_health, new_value, 0.6 ...

Початковою точкою є поточне значення шкали. Нам ще належить кодувати цю частину, але це буде animated_health. Кінцевою точкою анімації є health Player (життя гравця) після health_changed (зміни_життя): це new_value. І 0.6 це тривалість анімації в секундах.

Анімація не буде відтворюватися, поки ми не активуємо вузол Tween``з допомогою ``tween.start(). Це потрібно зробити лише один раз, якщо вузол не активний. Додайте цей код після останнього рядка:

if not tween.is_active():
    tween.start()
if (!_tween.IsActive())
{
    _tween.Start();
}

Примітка

Хоча ми могли б анімувати властивість health (здоров'я) на Player (гравцеві), ми не повинні цього робити. Персонажі повинні втрачати життя миттєво при попаданні під удар. Це набагато спрощує керування їх станами. Вам завжди краще зберігати анімацію в окремому контейнері даних, або вузлі. Вузол tween ідеально підходить для коду контролювання анімації. Для анімації ручної роботи перегляньте AnimationPlayer.

Призначення animated_health LifeBar

Тепер змінна animated_health анімована, але ми більше не оновлюємо вузли Bar та Number. Давайте виправимо це.

Поки що метод update_health виглядає приблизно так:

func update_health(new_value):
    tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6)
    if not tween.is_active():
        tween.start()
public void UpdateHealth(int health)
{
    _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
        Tween.EaseType.In);

    if(!_tween.IsActive())
    {
        _tween.Start();
    }
}

У цьому конкретному випадку, оскільки number_label бере текст, нам потрібно використовувати метод _process для її анімації. Тепер оновимо вузли Number і TextureProgress, як і раніше, але всередині _process:

func _process(delta):
    number_label.text = str(animated_health)
    bar.value = animated_health
public override void _Process(float delta)
{
    _numberLabel.Text = _animatedHealth.ToString();
    _bar.Value = _animatedHealth;
}

Примітка

number_label та bar - це змінні, які зберігають посилання на вузли Number та TextureProgress.

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

../../_images/lifebar_tutorial_number_animation_messed_up.gif

Гладка анімація, проте недоладне число

Ми можемо виправити обидві проблеми шляхом заокруглення animated_health. Використовуйте локальну змінну з назвою round_value для зберігання заокругленого значення animated_health. Потім призначте значення number_label.text та bar.value:

func _process(delta):
    var round_value = round(animated_health)
    number_label.text = str(round_value)
    bar.value = round_value
public override void _Process(float delta)
{
    var roundValue = Mathf.Round(_animatedHealth);
    _numberLabel.Text = roundValue.ToString();
    _bar.Value = roundValue;
}

Спробуйте гру ще раз, щоб побачити приємну анімацію.

../../_images/lifebar_tutorial_number_animation_working.gif

Заокруглюючи animated_health ми вбиваємо двох зайців одним пострілом

Порада

Кожен раз, коли гравець приймає удар, GUI викликає _on_Player_health_changed, який в свою чергу викликає update_health. Це послідовно оновлює анімацію number_label та bar в _process. Анімація шкали життя, яка показує, що здоров'я поступово знижується, є хитрістю. Це оживляє GUI. Якщо Player отримує 3 пошкодження, гравець помирає, а шкала стає порожня.

Зникнення шкали при смерті гравця

Коли зелений персонаж вмирає, він відтворює анімацію смерті і згасає. На даний момент нам більше не треба показувати інтерфейс. Давайте, при смерті персонажа, шкалу також сховаємо. Ми повторно використаємо той самий вузол Tween, який паралельно управляє декількома анімаціями.

First, the GUI needs to connect to the Player's died signal to know when it died. Press Ctrl + F1 to jump back to the 2D Workspace. Select the Player node in the Scene dock and click on the Node tab next to the Inspector.

Знайдіть сигнал died, виберіть його та натисніть кнопку Приєднати.

../../_images/lifebar_tutorial_player_died_signal_enemy_connected.png

Сигнал вже має бути підключеним до Enemy

У вікні З'єднання сигналу знову підключіться до вузла GUI. В полі Метод-отримувач має бути _on_Player_died. Натисніть «З'єднати» внизу вікна. Це перенесе вас до файлу GUI.gd в робочій області скриптів.

../../_images/lifebar_tutorial_player_died_connecting_signal_window.png

Ви повинні отримати такі значення у вікні З'єднання сигналу

Примітка

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

Щоб анімувати зникнення елемента інтерфейсу, ми повинні використовувати його властивість modulate. modulate це Color, який множить кольори наших текстур.

Примітка

modulate походить від класу CanvasItem, від нього успадковуються всі 2D та UI-вузли. Він дозволяє перемикати видимість вузла, призначати йому шейдер і змінювати його за допомогою кольору modulate.

modulate приймає значення Color з 4-ох каналів: червоний, зелений, синій та альфа. Якщо затемнити будь-який з перших трьох каналів, він затемнить інтерфейс. Якщо ми знизимо альфа-канал, наш інтерфейс стане прозорим.

Ми збираємось анімувати значення кольорів: від білого з альфа 1, тобто з повною непрозорістю, до чисто білого із значенням альфа 0, повною прозорістю. Додамо дві змінні у верхній частині методу _on_Player_died та назвемо їх start_color та end_color. Використовуйте конструктор Color() для побудови двох значень Color.

func _on_Player_died():
    var start_color = Color(1.0, 1.0, 1.0, 1.0)
    var end_color = Color(1.0, 1.0, 1.0, 0.0)
public void OnPlayerDied()
{
    var startColor = new Color(1.0f, 1.0f, 1.0f);
    var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
}

Color(1.0, 1.0, 1.0) відповідає білому. Четвертим аргументом, відповідно 1.0 і 0.0 в start_color і end_color, є альфа-канал.

Потім нам доведеться знову викликати метод interpolate_property вузла Tween:

tween.interpolate_property(self, "modulate", start_color, end_color, 1.0)
_tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
  Tween.EaseType.In);

Цього разу ми змінимо властивість modulate і попросимо її анімувати з start_color до end_color. Тривалість становить одну секунду, з лінійним переходом. Знову ж таки, оскільки перехід лінійний, плавність значення не має. Ось повний метод _on_Player_died:

func _on_Player_died():
    var start_color = Color(1.0, 1.0, 1.0, 1.0)
    var end_color = Color(1.0, 1.0, 1.0, 0.0)
    tween.interpolate_property(self, "modulate", start_color, end_color, 1.0)
public void OnPlayerDied()
{
    var startColor = new Color(1.0f, 1.0f, 1.0f);
    var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);

    _tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
        Tween.EaseType.In);
}

І це все. Тепер ви можете запустити гру, щоб побачити кінцевий результат!

../../_images/lifebar_tutorial_final_result.gif

Кінцевий результат. Вітаємо з успіхом!

Примітка

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