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...
Рахунок та повтор
У цій частині ми додамо рахунок, відтворення музики та можливість перезапустити гру.
Ми повинні відстежувати поточний рахунок в змінній і відображати її на екрані за допомогою мінімального інтерфейсу. Для цього ми будемо використовувати текстову мітку.
У головній сцені додайте новий дочірній вузол Control до Main і назвіть його UserInterface. Переконайтеся, що ви перебуваєте на екрані 2D, де ви можете редагувати свій інтерфейс користувача (UI).
Додайте вузол Label і назвіть його ScoreLabel

У Інспекторі в Label Text введіть текст "Score: 0" (або "Рахунок: 0", якщо шрифт підтримує кирилицю).

Крім того, текст за замовчуванням білий, як і фон нашої гри. Нам потрібно змінити його колір, щоб побачити під час виконання.
Прокрутіть вниз до Перевизначення теми, розгорніть Кольори та увімкніть Колір шрифту, щоб відтінити текст у чорний колір (який добре контрастує з білою 3D-сценою)

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

Вузол UserInterface дозволяє нам згрупувати наш UI у гілці дерева сцени та використовувати ресурс теми, який поширюватиметься на всіх своїх дочірніх елементів. Ми будемо використовувати його, щоб встановити шрифт нашої гри.
Створення теми інтерфейсу користувача
Ще раз виберіть вузол Інтерфейс користувача. В Інспекторі створіть новий ресурс теми в Тема -> Тема.

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

За замовчуванням тема має лише кілька властивостей: Базовий масштаб за замовчуванням, Шрифт за замовчуванням і Розмір шрифту за замовчуванням.
Дивись також
Ви можете додати більше властивостей до теми для розробки складних інтерфейсів користувача, але це виходить за рамки цієї серії. Щоб дізнатися більше про створення та редагування тем гляньте на Вступ до графічного інтерфейсу користувача.
Шрифт за замовчуванням очікує файл шрифту, подібного до тих, що є на вашому комп’ютері. Двома поширеними форматами файлів шрифтів є TrueType Font (TTF) і OpenType Font (OTF).
У доку FileSystem розгорніть каталог fonts і клацніть і перетягніть файл Montserrat-Medium.ttf, який ми включили в проект, на Default Font. Текст знову з’явиться в попередньому перегляді теми.
Текст трохи маленький. Встановіть Розмір шрифту за замовчуванням на 22 пікселі, щоб збільшити розмір тексту.

Відстеження рахунку
Далі попрацюємо над рахунком. Додайте новий скрипт до ScoreLabel і визначте змінну score.
extends Label
var score = 0
using Godot;
public partial class ScoreLabel : Label
{
private int _score = 0;
}
Оцінка має збільшуватися на 1 кожного разу, коли ми розчавлюємо монстра. Ми можемо використовувати їхній здавлений сигнал, щоб знати, коли це станеться. Однак, оскільки ми створюємо екземпляри монстрів із коду, ми не можемо підключити сигнал мобу до ScoreLabel через редактор.
Замість цього, ми повинні закладати зв'язок в коді кожен раз, коли створюємо монстра.
Відкрийте скрипт main.gd. Якщо він все ще відкритий, ви можете клацнути його назву в лівій колонці редактора сценаріїв.

Крім того, ви можете двічі клацнути файл main.gd у доку FileSystem.
У нижній частині функції _on_MobTimer_timeout() додайте наступний рядок:
func _on_mob_timer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())
private void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Squashed += GetNode<ScoreLabel>("UserInterface/ScoreLabel").OnMobSquashed;
}
Цей рядок означає, що коли моб видає сигнал squashed, вузол ScoreLabel отримає його та викличе функцію _on_mob_squashed().
Поверніться до сценарію score_label.gd, щоб визначити функцію зворотного виклику _on_mob_squashed().
В ній ми збільшуємо рахунок та оновлюємо відображуваний текст.
func _on_mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = $"Score: {_score}";
}
Другий рядок використовує значення змінної score для заміни заповнювача %s. Під час використання цієї функції Godot автоматично перетворює значення на рядковий текст, що зручно під час виведення тексту в мітках або під час використання функції print().
Дивись також
Ви можете дізнатися більше про форматування рядків тут: Форматований текст GDScript. У C# розгляньте можливість використання рядкової інтерполяції з "$".
Тепер ви можете зіграти в гру і розчавити кілька ворогів, щоб побачити збільшення рахунку.

Примітка
У складній грі ви можете повністю відокремити свій інтерфейс користувача від ігрового світу. У цьому випадку ви не будете відстежувати рахунок через мітку. Замість цього ви можете зберегти його в окремому, виділеному об'єкті. Але в прототипах, або простих проектах, краще, щоб ваш код був простим. Програмування завжди є балансуванням.
Повторна спроба гри
Тепер ми додамо можливість грати знову після смерті. Коли гравець помре, ми відобразимо повідомлення на екрані і дочекаємося підтвердження.
Поверніться до сцени main.tscn, виберіть вузол UserInterface, додайте дочірній вузол ColorRect і назвіть його Retry. Цей вузол заповнює прямокутник однорідним кольором і слугуватиме накладанням для затемнення екрана.
Щоб охопити все вікно перегляду, ви можете скористатися меню Попереднє налаштування прив’язки на панелі інструментів.

Відкрийте його та застосуйте команду Увесь прямокутник.

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

Це пояснюється тим, що вузли інтерфейсу користувача (всі ті, що мають зелену піктограму) працюють із прив’язками та полями відносно обмежувальної рамки батьківського елемента. Тут вузол UserInterface має невеликий розмір, і вузол Retry обмежений ним.
Виберіть UserInterface і також застосуйте Anchor Preset -> Full Rect до нього. Тепер вузол Повторити має охоплювати все вікно перегляду.
Давайте змінимо його колір, щоб він затемнив ігрову зону. Виберіть Повторити і в Інспекторі встановіть його Колір на щось темне та прозоре. Для цього в палітрі кольорів перетягніть повзунок A ліворуч. Він контролює альфа-канал кольору, тобто його непрозорість/прозорість.

Далі додайте Label як дочірній елемент Retry і надайте йому Текст «Натисніть Enter, щоб повторити». Щоб перемістити його та закріпити в центрі екрана, застосуйте до нього Попереднє налаштування -> Центр.

Кодування можливості повторної спроби
Тепер ми можемо перейти до коду, щоб показати або приховати вузол Повторити, коли гравець помирає та грає знову.
Відкрийте скрипт main.gd. По-перше, ми хочемо приховати накладання на початку гри. Додайте цей рядок до функції _ready().
func _ready():
$UserInterface/Retry.hide()
public override void _Ready()
{
GetNode<Control>("UserInterface/Retry").Hide();
}
Потім, коли гравець отримує удар, ми показуємо затінення.
func _on_player_hit():
#...
$UserInterface/Retry.show()
private void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
Нарешті, коли видно вузол Повторити, нам потрібно прослухати введення гравця та перезапустити гру, якщо він натисне Enter. Для цього ми використовуємо вбудований зворотний виклик _unhandled_input(), який запускається при будь-якому введенні.
Якщо гравець натиснув попередньо визначену дію введення ui_accept і Повторити видно, ми перезавантажуємо поточну сцену.
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
# This restarts the current scene.
get_tree().reload_current_scene()
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
// This restarts the current scene.
GetTree().ReloadCurrentScene();
}
}
Функція get_tree() дає нам доступ до глобального об'єкта SceneTree, що дозволяє перезавантажити і перезапустити поточну сцену.
Додавання музики
Щоб додати музику, яка постійно відтворюється у фоновому режимі, ми збираємося використовувати іншу функцію в Godot: autoloads.
Щоб відтворити аудіо, все, що вам потрібно зробити, це додати вузол AudioStreamPlayer до вашої сцени та прикріпити до нього аудіофайл. Коли ви запускаєте сцену, вона може відтворюватися автоматично. Однак, коли ви перезавантажуєте сцену, як ми робимо, щоб відтворити знову, аудіовузли також скидаються, і музика починає відтворюватися спочатку.
Ви можете використовувати функцію автозавантаження, щоб Godot автоматично завантажував вузол, або сцену, на початку гри, за межами поточної сцени. Ви також можете використовувати його для створення глобально доступних об'єктів.
Створіть нову сцену, перейшовши до меню Сцена і натиснувши Нова сцена або за допомогою піктограми + поруч з поточною відкритою сценою.

Натисніть кнопку Інший вузол, щоб створити AudioStreamPlayer і перейменувати його на MusicPlayer.

Ми включили музичний саундтрек House In a Forest Loop.ogg до каталогу art/. Клацніть і перетягніть його до властивості Stream у Інспекторі. Крім того, увімкніть автовідтворення Autoplay, щоб музика відтворювалася автоматично на початку гри.

Збережіть сцену як music_player.tscn.
Ми повинні зареєструвати його як автозавантаження. Перейдіть до меню Проект -> Параметри проекту… і натисніть вкладку Глобальні -> Автозавантаження.
У полі Шлях потрібно ввести шлях до сцени. Натисніть піктограму папки, щоб відкрити браузер файлів, і двічі клацніть music_player.tscn. Потім натисніть кнопку Додати праворуч, щоб зареєструвати вузол.

music_player.tscn тепер завантажується в будь-яку сцену, яку ви відкриваєте або відтворюєте. Отже, якщо ви запустите гру зараз, музика відтворюватиметься автоматично в будь-якій сцені.
Перш ніж ми завершимо цей урок, ось короткий погляд на те, як все працює всередині. Коли ви запускаєте гру, ваша панель Сцена змінюється, щоб дати вам дві вкладки: Віддалений і Локальний.

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

У верхній частині знаходиться автозавантажуваний MusicPlayer і вузол root, який є вікном перегляду вашої гри.
І це все для цього уроку. У наступній частині ми додамо анімацію, щоб гра виглядала набагато приємніше.
Ось повний скрипт main.gd для довідки.
extends Node
@export var mob_scene: PackedScene
func _ready():
$UserInterface/Retry.hide()
func _on_mob_timer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on the SpawnPath.
# We store the reference to the SpawnLocation node.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# And give it a random offset.
mob_spawn_location.progress_ratio = randf()
var player_position = $Player.position
mob.initialize(mob_spawn_location.position, player_position)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
# We connect the mob to the score label to update the score upon squashing one.
mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())
func _on_player_hit():
$MobTimer.stop()
$UserInterface/Retry.show()
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
# This restarts the current scene.
get_tree().reload_current_scene()
using Godot;
public partial class Main : Node
{
[Export]
public PackedScene MobScene { get; set; }
public override void _Ready()
{
GetNode<Control>("UserInterface/Retry").Hide();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
// This restarts the current scene.
GetTree().ReloadCurrentScene();
}
}
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on the SpawnPath.
// We store the reference to the SpawnLocation node.
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
// And give it a random offset.
mobSpawnLocation.ProgressRatio = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").position;
mob.Initialize(mobSpawnLocation.Position, playerPosition);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
// We connect the mob to the score label to update the score upon squashing one.
mob.Squashed += GetNode<ScoreLabel>("UserInterface/ScoreLabel").OnMobSquashed;
}
private void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Control>("UserInterface/Retry").Show();
}
}