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 (Проект -> Настройки проекта).
В левом меню перейдите вниз по ссылке Layer Names -> 3D Physics. Справа вы увидите список слоёв с полем рядом с каждым из них. Там вы можете задать их имена. Назовите первые три слоя player, enemies, и world, соответственно.
Теперь мы можем назначить их нашим физическим узлам.
Назначение слоев и масок¶
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.
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.
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.
Обратите внимание, что вы можете нажать кнопку "..." в правой части свойств, чтобы увидеть список именованных флажков.
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.
Then, open the Mob scene by double-clicking on mob.tscn
and select the
Mob
node.
Установите его Collision -> Layer на "enemies" и снимите его Collision -> Mask, оставив маску пустой.
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
// 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;
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
#...
public override void _PhysicsProcess(double delta)
{
// ...
// Jumping.
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
{
_targetVelocity.Y = JumpImpulse;
}
// ...
}
Это всё, что вам нужно для прыжка!
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.
В панели Scene появляется значок, указывающий на то, что узел является частью по крайней мере одной группы.
Теперь мы можем использовать группу из кода, чтобы отличить столкновения с монстрами от столкновений с полом.
Кодирование механики давки¶
Вернитесь к скрипту 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;
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
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;
}
}
}
}
Это большое количество новых функций. Вот дополнительная информация о них.
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()
// 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();
}
Примечание
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.
Однако игрок еще не умрёт. Мы поработаем над этим в следующей части.