Рахунок та повтор¶
У цій частині ми додамо рахунок, відтворення музики та можливість перезапустити гру.
Ми повинні відстежувати поточний рахунок в змінній і відображати її на екрані за допомогою мінімального інтерфейсу. Для цього ми будемо використовувати текстову мітку.
У головній сцені додайте новий вузол керування Control в якості нащадка Main та назвіть його UserInterface. Ви автоматично потрапите на 2D-екран, де зможете редагувати свій інтерфейс користувача (UI).
Додайте вузол Label і перейменуйте його на ScoreLabel.
У Інспекторі в Label Text введіть текст "Score: 0" (або "Рахунок: 0", якщо шрифт підтримує кирилицю).
Крім того, текст за замовчуванням білий, як і фон нашої гри. Нам потрібно змінити його колір, щоб побачити під час виконання.
Прогорніть униз до розділу Theme Overrides, розгорніть Colors та клацніть чорне поле поруч із пунктом Font Color, щоб тонувати текст.
Виберіть темний тон, щоб він добре контрастував з 3D-сценою.
Нарешті, клацніть і перетягніть текст у вікні перегляду, щоб перемістити його до верхнього лівого кута.
Вузол UserInterface дозволяє нам групувати наш інтерфейс користувача у гілку дерева сцени та використовувати тематичний ресурс, який буде поширюватися на всіх його нащадків. Ми будемо використовувати його, щоб встановити шрифт нашої гри.
Створення теми інтерфейсу користувача¶
Ще раз виберіть вузол UserInterface. У Інспекторі створіть нову тему в Theme -> Theme.
Натисніть на неї, щоб відкрити редактор тем у нижній панелі. Він дає вам попередній перегляд того, як будуть виглядати всі вбудовані елементи інтерфейсу користувача у вашій темі.
Зображення
За замовчуванням тема має лише одну властивість, Default Font (Шрифт за замовчуванням).
Дивись також
Ви можете додати більше властивостей до теми для розробки складних інтерфейсів користувача, але це виходить за рамки цієї серії. Щоб дізнатися більше про створення та редагування тем гляньте на Introduction to GUI skinning.
Клацніть властивість Default Font і створіть новий DynamicFont.
Розгорніть DynamicFont, натиснувши на нього, і розгорніть розділ Font. Там ви побачите порожнє поле Font Data.
This one expects a font file like the ones you have on your computer. DynamicFont supports the following formats:
TrueType (
.ttf
)відкритий тип(
.otf
)Веб-формат відкритого шрифту 1 (
.woff
)Web Open Font Format 2 (
.woff2
, починаючи з Godot 3.5)
In the FileSystem dock, expand the fonts
directory and click and drag the
Montserrat-Medium.ttf
file we included in the project onto the Font Data.
The text will reappear in the theme preview.
Текст трохи малий. Установіть Settings -> Size у 22
пікселі, щоб збільшити розмір тексту.
Відстеження рахунку¶
Давайте попрацюємо тепер над рахунком. Прикріпіть новий скрипт до ScoreLabel і визначте змінну score
.
extends Label
var score = 0
public class ScoreLabel : Label
{
private int _score = 0;
}
Рахунок повинен збільшуватися на 1
кожен раз, коли ми чавимо монстра. Ми можемо використовувати їхній сигнал squashed
, щоб знати, коли це станеться. Однак, оскільки ми створюємо екземпляри монстрів в коді, то не можемо зробити прив'язку у редакторі.
Замість цього, ми повинні закладати зв'язок в коді кожен раз, коли створюємо монстра.
Відкрийте скрипт Main.gd
. Якщо він все ще відкритий, ви можете натиснути на його назву в лівому стовпці редактора скриптів.
Крім того, ви можете двічі клацнути файл Main.gd
на панелі Файлова система.
У нижній частині функції _on_MobTimer_timeout()
додайте наступний рядок.
func _on_MobTimer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
public void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
Цей рядок означає, що коли монстр випромінює сигнал squashed
, вузол ScoreLabel отримує його і викликає функцію _on_Mob_squashed()
.
Поверніться до скрипта ScoreLabel.gd
, щоб визначити функцію _on_Mob_squashed()
.
В ній ми збільшуємо рахунок та оновлюємо відображуваний текст.
func _on_Mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = string.Format("Score: {0}", _score);
}
Другий рядок використовує значення змінної score
замість %s
. При використанні цієї функції Godot автоматично перетворює значення в текст, що зручно при виведенні тексту в мітках, або при використанні функції print()
.
Дивись також
Ви можете дізнатися більше про форматування рядків тут: Форматований текст GDScript.
Тепер ви можете зіграти в гру і розчавити кілька ворогів, щоб побачити збільшення рахунку.
Примітка
У складній грі ви можете повністю відокремити свій інтерфейс користувача від ігрового світу. У цьому випадку ви не будете відстежувати рахунок через мітку. Замість цього ви можете зберегти його в окремому, виділеному об'єкті. Але в прототипах, або простих проектах, краще, щоб ваш код був простим. Програмування завжди є балансуванням.
Повторна спроба гри¶
Тепер ми додамо можливість грати знову після смерті. Коли гравець помре, ми відобразимо повідомлення на екрані і дочекаємося підтвердження.
Поверніться до головної сцени Main, виберіть вузол UserInterface, додайте вузол ColorRect як дочірній вузол і назвіть його Retry. Цей вузол заповнює прямокутник рівномірним кольором і буде служити тінню для затемнення екрана.
Щоб він охоплював весь вид, можна скористатися меню Макет на панелі інструментів.
Відкрийте його та застосуйте команду Увесь прямокутник.
Нічого не відбувається. Ну, майже нічого: тільки чотири зелених шпильки переміщуються до кутів коробки виділення.
Це пов'язано з тим, що вузли інтерфейсу користувача (всі з зеленою піктограмою) працюють з прив'язками та полями відносно розміру рамки предка. Тут вузол UserInterface має невеликий розмір, а вузол Retry обмежений ним.
Виберіть UserInterface і застосуйте до нього Макет -> Увесь прямокутник. Вузол Retry тепер повинен охоплювати увесь вид.
Давайте змінимо його колір, щоб він затемнив ігрову зону. Натисніть Retry, і в Інспекторі встановіть для його Color щось темне та прозоре. Для цього в палітрі кольорів перетягніть повзунок A ліворуч. Він керує альфа-каналом кольору, тобто його непрозорістю.
Далі додайте мітку Label як дочірню частину Retry та дайте їй текст в Text "Натисніть клавішу Enter, щоб повторити спробу."
Щоб перемістити його та закріпити в центрі екрана, застосуйте Макет -> За центром.
Кодування можливості повторної спроби¶
Тепер ми можемо перейти до коду, щоб показати і приховати вузол Retry, коли гравець помирає і грає знову.
Відкрийте скрипт Main.gd
. Спершу, ми хочемо приховати затінення на початку гри. Додайте цей рядок до функції _ready()
.
func _ready():
#...
$UserInterface/Retry.hide()
public override void _Ready()
{
// ...
GetNode<Control>("UserInterface/Retry").Hide();
}
Потім, коли гравець отримує удар, ми показуємо затінення.
func _on_Player_hit():
#...
$UserInterface/Retry.show()
public void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
Нарешті, коли відображається вузол Retry, нам потрібно прослухати введення гравця і перезапустити гру, якщо він натискають enter. Для цього ми використовуємо вбудований зворотний виклик _unhandled_input()
.
Якщо гравець натиснув попередньо визначену дію введення ui_accept
, і Retry відображається, перезавантажуємо поточну сцену.
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: автозавантаження.
Щоб відтворити звук, все, що вам потрібно зробити, це додати вузол AudioStreamPlayer до сцени та прикріпити до нього аудіофайл. Коли ви запустите сцену, він почне відтворюватися автоматично. Однак, коли ви перезавантажуєте сцену, звукові вузли також скидаються, і музика починається з самого початку.
Ви можете використовувати функцію автозавантаження, щоб Godot автоматично завантажував вузол, або сцену, на початку гри, за межами поточної сцени. Ви також можете використовувати його для створення глобально доступних об'єктів.
Створіть нову сцену, перейшовши в меню Сцена та натиснувши кнопку Нова сцена.
Натисніть кнопку Інший вузол, щоб створити AudioStreamPlayer і перейменувати його на MusicPlayer.
Ми включили музичний саундтрек House In a Forest Loop.ogg
до каталогу art/
. Клацніть і перетягніть його до властивості Stream у Інспекторі. Крім того, увімкніть автовідтворення Autoplay, щоб музика відтворювалася автоматично на початку гри.
Збережіть сцену під назвою MusicPlayer.tscn
.
Для автоматичного завантаження сцени перейдіть до меню Проєкт -> Параметри проєкту на вкладку AutoLoad (Автозавантаження).
У полі Path потрібно ввести шлях до сцени. Клацніть піктограму папки, щоб відкрити файловий браузер, і двічі клацніть на MusicPlayer.tscn
. Потім натисніть кнопку Додати праворуч, щоб зареєструвати вузол.
Якщо ви запустите гру зараз, музика буде відтворюватися автоматично. І навіть коли ви програєте і почнете знову, вона продовжуватиметься.
Перш ніж ми завершимо цей урок, ось короткий погляд на те, як все працює всередині. Коли ви запускаєте гру, ваша панель Сцена змінюється, щоб дати вам дві вкладки: Віддалений і Локальний.
Вкладка Віддалений дозволяє візуалізувати дерево вузлів вашої запущеної гри. Там ви побачите головний вузол Main і все, що містить сцена, і вставлені екземпляри монстрів внизу.
У верхній частині знаходяться автоматично завантажений MusicPlayer і кореневий вузол, який є вікном перегляду вашої гри.
І це все для цього уроку. У наступній частині ми додамо анімацію, щоб гра виглядала набагато приємніше.
Ось повний скрипт Main.gd
для довідки.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
$UserInterface/Retry.hide()
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
get_tree().reload_current_scene()
func _on_MobTimer_timeout():
var mob = mob_scene.instance()
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
func _on_Player_hit():
$MobTimer.stop()
$UserInterface/Retry.show()
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
GetNode<Control>("UserInterface/Retry").Hide();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
GetTree().ReloadCurrentScene();
}
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
public void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Control>("UserInterface/Retry").Show();
}
}