Передумови
Скачайте проєкт-основу та працюйте у ньому.
Основне Завдання
Створіть ворожого краба, який переслідує гравця та б'ється, як EnemyCrab.
- 1Базовий клас
Створіть
PathfindingGameEnemy2D(Code/Objects), як у теорії, і натисніть Build - помилок бути не повинно. - 2Краб
Створіть
CrabHostile.csіCrabHostile.tscn(Scenes/Enemies/CrabHostile) за кроками з теорії. Не забудьтеPathFindTimer- без нього краб не оновлюватиме шлях. - 3Рівень
Додайте краба в
Level1(Objects/Enemies, позиція (754, 225)). Дружнього краба видаліть.
Краб має поводитися так:
Завдання 2
Перше перевизначення: краб повідомляє про атаку.
Коли краб кусає гравця, у панелі Output має з'являтися повідомлення, наприклад CrabHostile вкусив гравця!.
Атакою займається метод AttackPlayer() з PathfindingGameEnemy2D. Він virtual, тож у CrabHostile його можна перевизначити - додати свою дію, не чіпаючи базовий клас.
Як перевизначити метод
- Усередині класу
CrabHostileнапишіть заголовок методу, так само як у базовому класі, але замістьvirtual-override. Порада: наберітьoverrideі пробіл - редактор покаже список методів, які можна перевизначити. - Першим рядком викличте
base.AttackPlayer(player)- це звичайна атака з базового класу (анімація + шкода гравцю). Без цього рядка краб перестане кусатися! - Після нього - ваш код.
protected override void AttackPlayer(Player player)
{
base.AttackPlayer(player); // спершу - звичайна атака з PathfindingGameEnemy2D
// ваш код тут
}Вивести повідомлення: GD.Print("текст");. Ім'я вузла краба - властивість Name: GD.Print($"{Name} вкусив гравця!");
Завдання 3
Розлючений краб: після кожного укусу він стає швидшим.
У тому самому AttackPlayer() із завдання 2 збільшуйте швидкість краба на 15 після кожної атаки та виводьте нову швидкість в Output.
Швидкість - це поле Speed з PathfindingAgent2D (у сцені краба воно дорівнює 60). Воно public, тож у CrabHostile до нього можна звертатися просто за іменем:
Speed += 15f; // те саме, що Speed = Speed + 15f;
GD.Print($"Нова швидкість: {Speed}");Ускладнення: не швидше за 150
Mathf.Min(a, b) повертає менше з двох чисел. Так швидкість зростає, але ніколи не перевищить 150:
Speed = Mathf.Min(Speed + 15f, 150f);Завдання 4
Краб перевертається догори лапками, коли помирає.
Смерть запускає метод StartDying() з PathfindingGameEntity2D. Перевизначте його в CrabHostile, так само як AttackPlayer() у завданні 2, і переверніть спрайт по вертикалі.
protected override void StartDying()
{
base.StartDying(); // анімація смерті та зупинка руху
// ваш код тут
}Перевернути спрайт: AnimSprite.FlipV = true; - так само, як FlipH (віддзеркалення вліво-вправо), лише по вертикалі. AnimSprite - це AnimatedSprite2D краба, базовий клас уже знайшов його за вас.
Що станеться, якщо прибрати рядок base.StartDying();? Спробуйте, а потім поверніть його.
Завдання 5
Зробіть краба міцнішим і покажіть, що удар влучив.
Задайте крабу Health = 3. При кожному ударі на мить змінюйте колір спрайту через Modulate (як у завданні 3 теми 19).
Один стрибок іноді зараховується двічі - це нормально.
Tween - плавна зміна властивості
Tween - це об'єкт Godot, який сам, кадр за кадром, плавно змінює властивість вузла від поточного значення до потрібного за заданий час. Ваш код при цьому не чекає: метод відпрацьовує одразу, а зміна відбувається паралельно з грою.
1. Створення та одна зміна
Tween tween = CreateTween(); // CreateTween() є в кожного Node - зокрема в самого краба
tween.TweenProperty(AnimSprite, "modulate", Colors.White, 0.2f); // за 0.2 с стати білим| Аргумент | Приклад | Що означає |
|---|---|---|
| Вузол | | Чию властивість змінюємо. |
| Назва властивості | | Назва так, як її пише Godot: з маленької літери ("modulate", "position", "scale"). Не "Modulate" - з такою назвою твін нічого не змінить. |
| Кінцеве значення | | До чого прийти. Тип має збігатися з типом властивості: Modulate - це Color. |
| Тривалість | | У секундах. |
Tween змінює значення від поточного. Тому щоб «блимнути», спершу миттєво задайте колір удару, а потім твіном плавно поверніть білий:
AnimSprite.Modulate = new Color(1f, 0.3f, 0.3f); // миттєво: червонуватий
Tween tween = CreateTween();
tween.TweenProperty(AnimSprite, "modulate", Colors.White, 0.2f); // плавно назадModulate множить кольори спрайту: Colors.White (1, 1, 1) - звичайний вигляд, new Color(1f, 0.3f, 0.3f) - червоний лишається, зелений і синій приглушуються.
2. Кілька змін підряд
Кожен наступний TweenProperty() на тому самому твіні починається тоді, коли закінчився попередній:
Tween tween = CreateTween();
tween.TweenProperty(AnimSprite, "modulate", Colors.Red, 0.1f); // 0.1 с: до червоного
tween.TweenProperty(AnimSprite, "modulate", Colors.White, 0.1f); // потім 0.1 с: назад до білого3. Зупинка попереднього твіна
Якщо удари йдуть один за одним, новий твін почнеться, поки старий ще працює, і обидва змінюватимуть той самий колір. Зберігайте твін у полі класу та зупиняйте старий методом Kill():
private Tween hitTween; // поле класу
// ...у методі, що запускає мигання:
hitTween?.Kill(); // ?. - бо під час першого удару твіна ще немає (null)
hitTween = CreateTween();
hitTween.TweenProperty(AnimSprite, "modulate", Colors.White, 0.2f);Твін, створений через CreateTween(), належить вузлу: якщо краб зникне з рівня посеред мигання, твін зупиниться разом із ним.
Інший спосіб: звичайний C# (async / await)
Те саме можна зробити без Tween - через async / await зі стандартної бібліотеки C#. Колір тоді змінюється не плавно, а стрибком: червоний → пауза → білий.
using System.Threading.Tasks; // на початку файлу
private async void FlashRed()
{
AnimSprite.Modulate = new Color(1f, 0.3f, 0.3f);
await Task.Delay(200); // 200 мілісекунд (не секунд!)
if (!IsInstanceValid(this)) { return; } // краб міг зникнути, поки ми чекали
AnimSprite.Modulate = Colors.White;
}asyncу заголовку методу дозволяє писати всередині ньогоawait.await Task.Delay(200)призупиняє лише цей метод, а не гру: краб далі ходить, гравець стрибає. Через 200 мс метод продовжиться з наступного рядка.- Після
awaitGodot продовжує метод у головному потоці гри, тож із вузлами можна працювати як завжди. - Поки метод чекав, краб міг померти й зникнути (
QueueFree()). Звернення до видаленого вузла кидаєObjectDisposedException, тому спершу перевіряємоIsInstanceValid(this).Tweenцю перевірку робить за вас. async void- лише для методів «запустив і забув», як-от обробник події. Викликається як звичайний метод:FlashRed();
Мінус цього способу: якщо другий удар прийде раніше, ніж мине пауза, перший виклик поверне білий колір завчасно. Для цього завдання це не страшно.
Підказка
Перевизначте OnAgentReady() у CrabHostile і підпишіться на Damaged. Спершу викличте base.OnAgentReady().
Завдання 6
Зона агресії: краб переслідує гравця, лише коли той близько.
Додайте в CrabHostile змінну AggroDistance. Перевизначте GetDestination(): якщо гравець далі за AggroDistance - поверніть null, інакше - результат базового методу.
Краб має зупинятися, коли гравець виходить із зони. Чи так це, якщо краб уже йде до гравця? Поясніть і виправте.
Що знадобиться
Змінна для інспектора (як Health чи Damage у PathfindingGameEnemy2D):
[ExportGroup("Aggro")]
[Export] public float AggroDistance = 250f; // px| Виклик | Повертає | Навіщо |
|---|---|---|
| Vector2? | Позиція гравця, або null, якщо гравця на рівні немає. Це те, що метод повертав до вашого перевизначення. |
| float | Відстань у пікселях від краба до гравця. |
| нічого (void) | Обриває шлях, яким краб іде зараз. Параметрів немає. Знадобиться для другої частини завдання. |
Vector2? - це Vector2, який може бути null. Перевірка: if (targetPosition == null). Саме значення: targetPosition.Value - лише після перевірки на null, інакше буде виняток.
Як перевизначити GetDestination()
PathfindingGameEntity2D уже перевизначив GetDestination() (повертає позицію цілі). У CrabHostile ви перевизначаєте його ще раз: та сама сигнатура зі словом override, а base.GetDestination() викликає версію з батьківського класу:
protected override Vector2? GetDestination()
{
Vector2? targetPosition = base.GetDestination(); // позиція гравця або null
// TODO: якщо гравця немає (null) - поверніть null
// TODO: якщо гравець далі за AggroDistance - поверніть null
return targetPosition;
}Сигнатура має повністю збігатися з базовою: protected, тип Vector2?, без параметрів - інакше Build видасть помилку. Ви вже перевизначали методи в завданнях 2-4 - тут усе так само.
Підказка
Подивіться, навіщо PathfindingGameFriendly2D викликає ClearCurrentPath().
Завдання 7
Хто кого б'є? Спрямуйте ворожого краба на дружнього.
Робіть це завдання на копії проєкту: ви змінюєте спільний базовий клас.
Поверніть дружнього краба на рівень і призначте його ворожому як TargetOverride (інспектор, група Targeting). Що відбувається, коли краби зустрічаються? Чому?
Потім зробіть так, щоб ворог атакував будь-який IDamagable, а не лише Player. Дружній краб теж має реалізувати IDamagable (завдання 4 теми 20).
Пастка
Area2D бачить і свого ж краба - без перевірки body == this краб атакує сам себе.
build info
- compiler
- 2.0.5.1 · 4054EF9BF6C4510A
- built
- 2026-11-08 14:31:25 UTC
WebUniverse