Прыжки и раздавливание монстров

В этой части мы добавим возможность прыгать, чтобы раздавить монстров. В следующем уроке мы сделаем так, чтобы игрок умирал, когда монстр ударяет его о землю.

Во-первых, мы должны изменить несколько настроек, связанных с физическими взаимодействиями. Войдите в мир <doc_physics_introduction_collision_layers_and_masks>`.

Управление физическими взаимодействиями

Физические тела имеют доступ к двум дополнительным свойствам: слоям и маскам. Слои определяют, на каком физическом слое (слоях) находится объект.

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

Если это вас смущает, не волнуйтесь, через секунду мы увидим три примера.

Важным моментом является то, что вы можете использовать слои и маски для фильтрации физического взаимодействия, контроля производительности и устранения необходимости в дополнительных условиях в вашем коде.

По умолчанию все физические тела и области установлены как на слой, так и на маску 0. Это означает, что все они сталкиваются друг с другом.

Физические слои представлены числами, но мы можем дать им имена, чтобы отслеживать, что есть что.

Установка имен слоев

Давайте дадим нашим физическим слоям имя. Перейдите в раздел Project -> Project Settings (Проект -> Настройки проекта).

image0

В левом меню перейдите вниз по ссылке Layer Names -> 3D Physics. Справа вы увидите список слоёв с полем рядом с каждым из них. Там вы можете задать их имена. Назовите первые три слоя player, enemies, и world, соответственно.

image1

Теперь мы можем назначить их нашим физическим узлам.

Назначение слоев и масок

В сцене Main выберите узел Ground. В инспекторе разверните раздел Collision. Там вы увидите слои и маски узла в виде сетки кнопок.

image2

Земля является частью мира, поэтому мы хотим, чтобы она была частью третьего слоя. Нажмите на кнопку с подсветкой, чтобы выключить первый слой Layer и включить третий. Затем отключите Mask, щёлкнув по ней.

image3

Как я уже упоминал, свойство Mask позволяет узлу слушать взаимодействие с другими физическими объектами, но нам не нужно, чтобы у него были столкновения. Свойству Ground не нужно ничего слушать; оно просто находится там, чтобы предотвратить падение существ.

Обратите внимание, что вы можете нажать кнопку "..." в правой части свойств, чтобы увидеть список именованных флажков.

image4

Далее следуют Player и Mob. Откройте Player.tscn, дважды щёлкнув по файлу в панели FileSystem.

Выберите узел Player и установите его свойство Collision -> Mask на "enemies" и "world". Вы можете оставить свойство по умолчанию Layer, так как первый слой - это слой "игрока" (player).

image5

Затем откройте сцену Mob, дважды щёлкнув на Mob.tscn и выбрав узел Mob.

Установите его Collision -> Layer на "enemies" и снимите его Collision -> Mask, оставив маску пустой.

image6

Эти настройки означают, что монстры будут двигаться друг сквозь друга. Если вы хотите, чтобы монстры сталкивались и скользили друг по другу, включите маску "enemies".

Примечание

Мобам не нужно маскировать слой "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"):
        velocity.y += jump_impulse

    #...

Это всё, что вам нужно для прыжка!

Метод is_on_floor() является инструментом класса KinematicBody. Он возвращает значение true, если тело столкнулось с полом в этом кадре. Вот почему мы применяем гравитацию к Player : так мы сталкиваемся с полом, а не парим над ним, как монстры.

Если персонаж стоит на полу и игрок нажимает кнопку "прыжок", мы мгновенно придаем ему большую вертикальную скорость. В играх очень важно, чтобы управление было отзывчивым, а такие мгновенные прибавки скорости, хотя и нереалистичны, но ощущаются отлично.

Обратите внимание, что ось Y положительно направлена вверх. Это отличается от 2D, где ось Y положительно направлена вниз.

Раздавить монстров

Далее добавим механику давки. Мы заставим персонажа подпрыгивать над монстрами и одновременно убивать их.

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

Снова откройте сцену Mob.tscn и выберите узел Mob. Перейдите в панель Node справа, чтобы увидеть список сигналов. В панели Node есть две вкладки: Signals, которую вы уже использовали, и Groups, которая позволяет присваивать узлам теги.

Щёлкните по нему, чтобы открыть поле, в котором можно написать имя тега. Введите в поле "mob" и нажмите кнопку Add.

image7

В панели Scene появляется значок, указывающий на то, что узел является частью по крайней мере одной группы.

image8

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

Кодирование механики давки

Вернитесь к скрипту Player, чтобы закодировать давку и отскок.

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

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
export var bounce_impulse = 16

Затем, в нижней части _physics_process(), добавьте следующий цикл. В move_and_slide(), Godot заставляет тело двигаться иногда несколько раз подряд, чтобы сгладить движение персонажа. Поэтому мы должны перебрать все столкновения, которые могли произойти.

В каждой итерации цикла мы проверяем, приземлились ли мы на моба. Если да, то мы убиваем его и отскакиваем.

При таком коде, если на данном кадре не произошло ни одного столкновения, цикл не будет выполняться.

func _physics_process(delta):
    #...
    for index in range(get_slide_count()):
        # We check every collision that occurred this frame.
        var collision = get_slide_collision(index)
        # If we collide with a monster...
        if collision.collider.is_in_group("mob"):
            var mob = collision.collider
            # ...we check that we are hitting it from above.
            if Vector3.UP.dot(collision.normal) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                velocity.y = bounce_impulse

Это большое количество новых функций. Вот дополнительная информация о них.

Обе функции get_slide_count() и get_slide_collision() происходят из класса KinematicBody и связаны с move_and_slide().

get_slide_collision() возвращает объект KinematicCollision, который содержит информацию о том, где и как произошло столкновение. Например, мы используем его свойство collider, чтобы проверить, столкнулись ли мы с "мобом", вызвав на него is_in_group(): collision.collider.is_in_group("mob").

Примечание

Метод is_in_group() доступен для каждого Node.

Чтобы проверить, что мы приземляемся на монстра, мы используем векторное скалярное произведение (dot product): Vector3.UP.dot(collision.normal) > 0.1. Нормаль столкновения - это трёхмерный вектор, перпендикулярный плоскости, в которой произошло столкновение. Скалярное произведение позволяет нам сравнить его с направлением вверх.

При использовании скалярных произведений, когда результат больше, чем 0, два вектора находятся под углом менее 90 градусов. Значение больше, чем 0.1 говорит нам о том, что мы находимся примерно над монстром.

Мы вызываем одну неопределённую функцию, mob.squash(). Мы должны добавить её в класс Mob.

Откройте скрипт Mob.gd, дважды щёлкнув на нём в панели FileSystem. В верхней части скрипта мы хотим определить новый сигнал с именем squashed. А внизу можно добавить функцию давки, в которой мы испускаем сигнал и уничтожаем моба.

# Emitted when the player jumped on the mob.
signal squashed

# ...


func squash():
    emit_signal("squashed")
    queue_free()

Мы будем использовать сигнал для добавления очков к счёту в следующем уроке.

С помощью этого вы сможете убивать монстров, прыгая на них. Вы можете нажать F5, чтобы попробовать игру и установить Main.tscn в качестве основной сцены вашего проекта.

Однако игрок еще не умрёт. Мы поработаем над этим в следующей части.