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.

Стрибання і розчавлювання монстрів

У цій частині ми додамо можливість стрибати та розчавлювати монстрів. У наступному уроці ми змусимо гравця померти, коли монстр вдарить його на землю.

Спершу ми повинні змінити кілька налаштувань, пов'язаних з фізичними взаємодіями. Вступ до світу physics layers.

Контроль фізичних взаємодій

Фізичні тіла мають доступ до двох додаткових властивостей: шарів і масок. Шари визначають, на яких фізичних шарах (шарі) знаходиться об'єкт.

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

Якщо ви заплуталися, не хвилюйтеся, ми побачимо три приклади за секунду.

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

За замовчуванням для всіх фізичних тіл і областей встановлено шар і маску 1. Це означає, що всі вони стикаються один з одним.

Шари фізики представлені числами, але ми можемо дати їм імена, щоб відстежувати їх вміст.

Встановлення назв шарів

Давайте дамо нашим фізичним шарам назву. Перейдіть до Проект -> Параметри проекту.

image0

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

image1

Тепер ми можемо призначити їх нашим фізичним вузлам.

Призначення шарів і масок

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

image2

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

image3

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

Зверніть увагу, що ви можете натиснути кнопку "..." у правій частині властивостей, щоб переглянути список іменованих галочок.

image4

Далі йдуть Гравець і Моб. Відкрийте player.tscn, двічі клацнувши файл у доку FileSystem.

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

image5

Потім відкрийте сцену Mob, двічі клацнувши на mob.tscn і виберіть вузол Mob.

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

image6

Ці налаштування означають, що монстри рухатимуться один крізь одного. Якщо ви хочете, щоб монстри стикалися та ковзали один по одному, увімкніть маску «вороги».

Примітка

Мобам не потрібна маска "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

Усередині _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

    #...

Це все, що вам потрібно, для стрибка!

Метод is_on_floor() є інструментом з класу CharacterBody3D. Він повертає true, якщо тіло зіткнулося з підлогою в цьому кадрі. Ось чому ми застосовуємо силу тяжіння до Гравця: тож ми стикаємося з підлогою, а не паримо над нею, як монстри.

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

Зауважте, що вісь Y позитивна вгору. Це на відміну від 2D, де вісь Y направлена вниз.

Розчавлення монстрів

Давайте додамо далі механізм розчавлення. Ми збираємося змусити персонажа відскакувати від монстрів і вбивати їх одночасно.

Нам потрібно виявити зіткнення з монстром і відрізнити їх від зіткнень з підлогою. Для цього ми можемо використовувати функцію тегування group Godot.

Знову відкрийте сцену mob.tscn та виберіть вузол Mob. Перейдіть до панелі Groups праворуч, щоб побачити список груп, який дозволяє призначати теги вузлам. Натисніть кнопку +, щоб відкрити діалогове вікно Створити нову групу.

image7

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

image8

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

image9

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

image10

Тепер ми можемо використовувати групу в коді, щоб відрізнити зіткнення з монстрами від зіткнень з підлогою.

Кодування механізму розчавлення

Поверніться до скрипта гравця Player, щоб закодувати розчавлення.

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

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
@export var bounce_impulse = 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

Тут багато нових функцій. Ось ще трохи інформації про них.

Обидві функції 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()

Примітка

Коли використовується C#, Godot створює відповідні події автоматично для всих Сигналів з закіньченням EventHandler, дивись C# Signals.

Ми будемо використовувати сигнал, щоб додати бали до рахунку на наступному уроці.

З цим ви зможете вбивати монстрів, стрибаючи на них. Ви можете натиснути F5, щоб спробувати гру та встановити main.tscn як основну сцену вашого проекту.

Однак гравець безсмертний. Ми поправимо це у наступній частині.