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.

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.

image0

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.

|image1|

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.

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.

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.

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.

Establece su propiedad Colisión -> Capa en "enemies" y desactiva su propiedad Colisión -> Máscara, dejando la máscara vacía.

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.

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

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

    #...

¡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.

image7

Aparece un icono en el panel de escenas para indicar que el nodo forma parte de al menos un grupo.

image8

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

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

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()

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.