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...
Saltando y aplastando monstruos¶
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.
Primero, tenemos que cambiar algunas configuraciones relacionadas con las interacciones físicas. Ingresa al mundo de las capas de física.
Controlando las interacciones fisicas¶
Los cuerpos físicos tienen acceso a dos propiedades complementarias: capas y máscaras. Las capas definen en qué capa(s) física(s) se encuentra un objeto.
Las máscaras controlan las capas que un cuerpo escuchará y detectará. Esto afecta a la detección de colisiones. Cuando quieras que dos cuerpos interactúen, necesitas que al menos uno tenga una máscara correspondiente al otro.
Si estés confundido, no preocupe, exploraramos tres ejemplos en uno momento.
El punto más importante es que pueda usar capas y mascarillas para filtrar interacciones de fisicas, controlar rendimiento, y eliminar requisitos por condiciones adicionales en su código.
By default, all physics bodies and areas are set to both layer and mask 1
.
This means they all collide with each other.
Las capas físicas se representan mediante números, pero podemos darles nombres para llevar un registro de qué es qué.
Ajustando el nombre de las capas¶
Vamos a darle nombre a nuestras capas físicas. Ve a Proyecto -> Configuración del Proyecto.
En el menú de la izquierda, ve hacia abajo hasta Nombres de Capas -> Física 3D. Verás una lista de capas con un campo junto a cada una de ellas a la derecha. Puedes establecer sus nombres allí. Nombra las primeras tres capas como player, enemies y world, respectivamente.
Ahora podemos asignarles estas capas a nuestros nodos físicos.
Asignando capas y máscaras¶
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.
Ten en cuenta que puedes hacer clic en el botón "..." en el lado derecho de las propiedades para ver una lista de casillas de verificación con nombres.
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.
Establece su propiedad Colisión -> Capa en "enemies" y desactiva su propiedad Colisión -> Máscara, dejando la máscara vacía.
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.
Nota
Los enemigos no necesitan tener en cuenta la capa "world" porque solo se mueven en el plano XZ. No les aplicamos gravedad a propósito.
Saltando¶
La mecánica de salto en sí misma solo requiere dos líneas de código. Abre el script Player. Necesitamos un valor para controlar la fuerza del salto y actualiza _physics_process()
para codificar el salto.
Después de la línea que define fall_acceleration
, en la parte superior del script, agrega el 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;
}
// ...
}
¡Eso es todo lo que necesitas para saltar!
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.
Aplastando monstruos¶
A continuación, agreguemos la mecánica de aplastamiento. Vamos a hacer que el personaje rebote sobre los monstruos y los mate al mismo tiempo.
Necesitamos detectar las colisiones con un monstruo y diferenciarlas de las colisiones con el suelo. Para hacerlo, podemos usar la función de etiquetado de grupos de Godot.
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.
Haz clic en él para revelar un campo donde puedes escribir un nombre de etiqueta. Ingresa "mob" en el campo y haz clic en el botón Agregar.
Aparece un icono en el panel de escenas para indicar que el nodo forma parte de al menos un grupo.
Ahora podemos usar el grupo desde el código para distinguir las colisiones con los monstruos de las colisiones con el suelo.
Codificando la mecánica de aplastar¶
Regresa al script Player para programar el aplastamiento y el rebote.
En la parte superior del script, necesitamos otra propiedad llamada bounce_impulse
. Al aplastar a un enemigo, no queremos necesariamente que el personaje se eleve tan alto como cuando salta.
# 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.
En cada iteración del bucle, comprobamos si hemos aterrizado sobre un enemigo. Si es así, lo eliminamos y rebotamos.
Con este código, si no ocurren colisiones en un fotograma determinado, el bucle no se ejecutará.
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
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.
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;
}
}
}
}
Eso es un montón de nuevas funciones. Aquí tienes más información sobre ellas.
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")
.
Nota
El método is_in_group()
está disponible en cada 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.
Con los productos escalares, cuando el resultado es mayor que 0
, los dos vectores están en un ángulo menor a 90 grados. Un valor mayor a 0.1
nos indica que estamos aproximadamente encima del monstruo.
We are calling one undefined function, mob.squash()
, so we have to add it to
the Mob class.
Abre el script Mob.gd
haciendo doble clic en él en el panel Sistema de Archivos. En la parte superior del script, queremos definir una nueva señal llamada squashed
. Y en la parte inferior, puedes agregar la función squash, donde emitimos la señal y destruimos el enemigo.
# 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();
}
Utilizaremos la señal para agregar puntos a la puntuación en la siguiente lección.
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.
Sin embargo, el jugador aún no morirá. Trabajaremos en eso en la próxima parte.