Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

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

In this part, we'll add the ability to jump and squash the monsters. In the next lesson, we'll make the player die when a monster hits them on the ground.

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

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

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

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

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

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

By default, all physics bodies and areas are set to both layer and mask 1. This means they all collide with each other.

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

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

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

image0

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

image1

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

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

In the Main scene, select the Ground node. In the Inspector, expand the Collision section. There, you can see the node's layers and masks as a grid of buttons.

image2

The ground is part of the world, so we want it to be part of the third layer. Click the lit button to toggle off the first Layer and toggle on the third one. Then, toggle off the Mask by clicking on it.

image3

As mentioned before, the Mask property allows a node to listen to interaction with other physics objects, but we don't need it to have collisions. Ground doesn't need to listen to anything; it's just there to prevent creatures from falling.

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

image4

Next up are the Player and the Mob. Open player.tscn by double-clicking the file in the FileSystem dock.

Select the Player node and set its Collision -> Mask to both "enemies" and "world". You can leave the default Layer property as it is, because the first layer is the "player" layer.

image5

Then, open the Mob scene by double-clicking on mob.tscn and select the Mob node.

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

image6

These settings mean the monsters will move through one another. If you want the monsters to collide with and slide against each other, turn on the "enemies" 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

Inside _physics_process(), add the following code before the move_and_slide() codeblock.

func _physics_process(delta):
    #...

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = jump_impulse

    #...

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

The is_on_floor() method is a tool from the CharacterBody3D class. It returns true if the body collided with the floor in this frame. That's why we apply gravity to the Player: so we collide with the floor instead of floating over it like the monsters.

If the character is on the floor and the player presses "jump", we instantly give them a lot of vertical speed. In games, you really want controls to be responsive and giving instant speed boosts like these, while unrealistic, feels great.

Notice that the Y axis is positive upwards. That's unlike 2D, where the Y axis is positive downwards.

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

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

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

Open the scene mob.tscn again and select the Mob node. Go to the Node dock on the right to see a list of signals. The Node dock has two tabs: Signals, which you've already used, and Groups, which allows you to assign tags to nodes.

Щёлкните по нему, чтобы открыть поле, в котором можно написать имя тега. Введите в поле "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

Then, after the Jumping codeblock we added above in _physics_process(), add the following loop. With move_and_slide(), Godot makes the body move sometimes multiple times in a row to smooth out the character's motion. So we have to loop over all collisions that may have happened.

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

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

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 the collision is with ground
        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

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

The functions get_slide_collision_count() and get_slide_collision() both come from the CharacterBody3D class and are related to move_and_slide().

get_slide_collision() returns a KinematicCollision3D object that holds information about where and how the collision occurred. For example, we use its get_collider property to check if we collided with a "mob" by calling is_in_group() on it: collision.get_collider().is_in_group("mob").

Примечание

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

To check that we are landing on the monster, we use the vector dot product: Vector3.UP.dot(collision.get_normal()) > 0.1. The collision normal is a 3D vector that is perpendicular to the plane where the collision occurred. The dot product allows us to compare it to the up direction.

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

After handling the squash and bounce logic, we terminate the loop early via the break statement to prevent further duplicate calls to mob.squash(), which may otherwise result in unintended bugs such as counting the score multiple times for one kill.

We are calling one undefined function, mob.squash(), so we have to add it to the Mob class.

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

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

# ...


func squash():
    squashed.emit()
    queue_free()

Примечание

When using C#, Godot will create the appropriate events automatically for all Signals ending with EventHandler, see C# Signals.

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

With that, you should be able to kill monsters by jumping on them. You can press F5 to try the game and set main.tscn as your project's main scene.

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