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...
Стрибання і розчавлювання монстрів
У цій частині ми додамо можливість стрибати та розчавлювати монстрів. У наступному уроці ми змусимо гравця померти, коли монстр вдарить його на землю.
Спершу ми повинні змінити кілька налаштувань, пов'язаних з фізичними взаємодіями. Вступ до світу physics layers.
Контроль фізичних взаємодій
Фізичні тіла мають доступ до двох додаткових властивостей: шарів і масок. Шари визначають, на яких фізичних шарах (шарі) знаходиться об'єкт.
Маски контролюють шари, які тіло буде слухати і виявляти. Маски впливають на виявлення зіткнень. Коли ви хочете, щоб два тіла взаємодіяли, вам потрібно принаймні одне з маскою, відповідною іншому.
Якщо ви заплуталися, не хвилюйтеся, ми побачимо три приклади за секунду.
Важливим моментом є те, що ви можете використовувати шари та маски для фільтрації взаємодій фізики, контролю продуктивності та усунення необхідності додаткових умов у вашому коді.
За замовчуванням для всіх фізичних тіл і областей встановлено шар і маску 1. Це означає, що всі вони стикаються один з одним.
Шари фізики представлені числами, але ми можемо дати їм імена, щоб відстежувати їх вміст.
Встановлення назв шарів
Давайте дамо нашим фізичним шарам назву. Перейдіть до Проект -> Параметри проекту.

У лівому меню перейдіть до розділу Layer Names -> 3D Physics. Праворуч ви можете побачити список шарів з полем поруч з кожним з них. В цьому полі ви можете встановити їхні імена. Назвіть перші три шари player, enemies, і world відповідно.

Тепер ми можемо призначити їх нашим фізичним вузлам.
Призначення шарів і масок
У сцені Main виберіть вузол Ground. У Інспекторі розгорніть розділ Зіткнення. Там ви можете побачити шари та маски вузла у вигляді сітки кнопок.

Земля є частиною світу, тому ми хочемо, щоб вона була частиною третього шару. Натисніть освітлену кнопку, щоб вимкнути перший Шар і ввімкнути третій. Потім вимкніть Маску, натиснувши на неї.

Як згадувалося раніше, властивість Mask дозволяє вузлу слухати взаємодію з іншими фізичними об’єктами, але нам не потрібно, щоб він мав зіткнення. Ground не потрібно нічого слухати; це лише для того, щоб запобігти падінню істот.
Зверніть увагу, що ви можете натиснути кнопку "..." у правій частині властивостей, щоб переглянути список іменованих галочок.

Далі йдуть Гравець і Моб. Відкрийте player.tscn, двічі клацнувши файл у доку FileSystem.
Виберіть вузол Гравець і встановіть його Зіткнення -> Маска як «вороги», так і «світ». Ви можете залишити типову властивість Layer як є, тому що перший шар є шаром «гравець».

Потім відкрийте сцену Mob, двічі клацнувши на mob.tscn і виберіть вузол Mob.
Встановіть йому Collision -> Layer на "enemies", а маску зіткнення Collision -> Mask, залишіть порожньою.

Ці налаштування означають, що монстри рухатимуться один крізь одного. Якщо ви хочете, щоб монстри стикалися та ковзали один по одному, увімкніть маску «вороги».
Примітка
Мобам не потрібна маска "world", тому що вони рухаються тільки на площині XZ. Ми не застосовуємо до них ніякої гравітації.
Стрибки
Сам механізм стрибка вимагає всього двох рядків коду. Відкрийте скрипт гравця Player. Нам потрібно значення, щоб контролювати силу стрибка і оновлення _physics_process(), щоб закодувати стрибок.
Після рядка, який визначає fall_acceleration, у верхній частині скрипту, додайте jump_impulse.
#...
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20
// Don't forget to rebuild the project so the editor knows about the new export variable.
// ...
// Vertical impulse applied to the character upon jumping in meters per second.
[Export]
public int JumpImpulse { get; set; } = 20;
Усередині _physics_process() додайте наступний код перед move_and_slide() кодовим блоком.
func _physics_process(delta):
#...
# Jumping.
if is_on_floor() and Input.is_action_just_pressed("jump"):
target_velocity.y = jump_impulse
#...
public override void _PhysicsProcess(double delta)
{
// ...
// Jumping.
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
{
_targetVelocity.Y = JumpImpulse;
}
// ...
}
Це все, що вам потрібно, для стрибка!
Метод is_on_floor() є інструментом з класу CharacterBody3D. Він повертає true, якщо тіло зіткнулося з підлогою в цьому кадрі. Ось чому ми застосовуємо силу тяжіння до Гравця: тож ми стикаємося з підлогою, а не паримо над нею, як монстри.
Якщо персонаж лежить на підлозі і гравець натискає «стрибок», ми миттєво надаємо йому велику вертикальну швидкість. В іграх ви справді хочете, щоб елементи керування були чуйними, і миттєве збільшення швидкості, хоч і нереалістичне, відчувається чудово.
Зауважте, що вісь Y позитивна вгору. Це на відміну від 2D, де вісь Y направлена вниз.
Розчавлення монстрів
Давайте додамо далі механізм розчавлення. Ми збираємося змусити персонажа відскакувати від монстрів і вбивати їх одночасно.
Нам потрібно виявити зіткнення з монстром і відрізнити їх від зіткнень з підлогою. Для цього ми можемо використовувати функцію тегування group Godot.
Знову відкрийте сцену mob.tscn та виберіть вузол Mob. Перейдіть до панелі Groups праворуч, щоб побачити список груп, який дозволяє призначати теги вузлам. Натисніть кнопку +, щоб відкрити діалогове вікно Створити нову групу.

Введіть «mob» у поле Ім’я та натисніть кнопку Ок.

Група «mob» тепер відображається в розділі Групи сцен.

У панелі Сцена з'являється значок, який вказує на те, що вузол є частиною принаймні однієї групи.
![]()
Тепер ми можемо використовувати групу в коді, щоб відрізнити зіткнення з монстрами від зіткнень з підлогою.
Кодування механізму розчавлення
Поверніться до скрипта гравця Player, щоб закодувати розчавлення.
У верхній частині скрипту нам потрібна інша властивість, bounce_impulse. Нам не потрібно, щоб при розчавленні ворога персонаж піднімався так високо, як при стрибках.
# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
@export var bounce_impulse = 16
// Don't forget to rebuild the project so the editor knows about the new export variable.
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
[Export]
public int BounceImpulse { get; set; } = 16;
Потім після кодового блоку Jumping, який ми додали вище в _physics_process(), додайте наступний цикл. За допомогою move_and_slide() Godot змушує тіло рухатися іноді кілька разів поспіль, щоб згладити рухи персонажа. Тож ми повинні пройти циклом усі зіткнення, які могли статися.
У кожній ітерації петлі ми перевіряємо, чи приземлилися ми на монстра. Якщо так, то ми вбиваємо його і відскакуємо.
За допомогою цього коду, якщо на даному кадрі не сталося зіткнень, цикл не буде запущений.
func _physics_process(delta):
#...
# Iterate through all collisions that occurred this frame
for index in range(get_slide_collision_count()):
# We get one of the collisions with the player
var collision = get_slide_collision(index)
# If there are duplicate collisions with a mob in a single frame
# the mob will be deleted after the first collision, and a second call to
# get_collider will return null, leading to a null pointer when calling
# collision.get_collider().is_in_group("mob").
# This block of code prevents processing duplicate collisions.
if collision.get_collider() == null:
continue
# If the collider is with a mob
if collision.get_collider().is_in_group("mob"):
var mob = collision.get_collider()
# we check that we are hitting it from above.
if Vector3.UP.dot(collision.get_normal()) > 0.1:
# If so, we squash it and bounce.
mob.squash()
target_velocity.y = bounce_impulse
# Prevent further duplicate calls.
break
public override void _PhysicsProcess(double delta)
{
// ...
// Iterate through all collisions that occurred this frame.
for (int index = 0; index < GetSlideCollisionCount(); index++)
{
// We get one of the collisions with the player.
KinematicCollision3D collision = GetSlideCollision(index);
// If the collision is with a mob.
// With C# we leverage typing and pattern-matching
// instead of checking for the group we created.
if (collision.GetCollider() is Mob mob)
{
// We check that we are hitting it from above.
if (Vector3.Up.Dot(collision.GetNormal()) > 0.1f)
{
// If so, we squash it and bounce.
mob.Squash();
_targetVelocity.Y = BounceImpulse;
// Prevent further duplicate calls.
break;
}
}
}
}
Тут багато нових функцій. Ось ще трохи інформації про них.
Обидві функції get_slide_collision_count() і get_slide_collision() походять із класу CharacterBody3D і пов’язані з move_and_slide().
get_slide_collision() повертає об’єкт KinematicCollision3D, який містить інформацію про те, де і як сталося зіткнення. Наприклад, ми використовуємо його властивість get_collider, щоб перевірити, чи зіткнулися ми з "мобом", викликавши для нього is_in_group(): collision.get_collider().is_in_group("mob") .
Примітка
Метод is_in_group() доступний на кожному Node.
Щоб переконатися, що ми приземляємось на монстра, ми використовуємо векторний скалярний добуток: Vector3.UP.dot(collision.get_normal()) > 0,1. Нормаль зіткнення — це тривимірний вектор, перпендикулярний до площини, де відбулося зіткнення. Скалярний добуток дозволяє нам порівняти його з напрямком вгору.
З точковими добутками, коли результат більший 0, два вектори знаходяться під кутом менше 90 градусів. Вище 0.1 говорить нам, що ми приблизно вище монстра.
Після обробки логіки стиснення та відскоку ми достроково завершуємо цикл за допомогою оператора break, щоб запобігти подальшим дубльованим викликам mob.squash(), які інакше можуть призвести до ненавмисних помилок, таких як підрахунок кратних результатів разів за одне вбивство.
Ми викликаємо одну невизначену функцію, mob.squash(), тому нам потрібно додати її до класу Mob.
Відкрийте скрипт mob.gd, двічі клацнувши його в доку FileSystem. У верхній частині скрипта ми хочемо визначити новий сигнал під назвою squashed. А внизу можна додати функцію сквош, де ми видаємо сигнал і знищуємо моба.
# Emitted when the player jumped on the mob.
signal squashed
# ...
func squash():
squashed.emit()
queue_free()
// Don't forget to rebuild the project so the editor knows about the new signal.
// Emitted when the player jumped on the mob.
[Signal]
public delegate void SquashedEventHandler();
// ...
public void Squash()
{
EmitSignal(SignalName.Squashed);
QueueFree();
}
Примітка
Коли використовується C#, Godot створює відповідні події автоматично для всих Сигналів з закіньченням EventHandler, дивись C# Signals.
Ми будемо використовувати сигнал, щоб додати бали до рахунку на наступному уроці.
З цим ви зможете вбивати монстрів, стрибаючи на них. Ви можете натиснути F5, щоб спробувати гру та встановити main.tscn як основну сцену вашого проекту.
Однак гравець безсмертний. Ми поправимо це у наступній частині.