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.

Sauter et écraser les monstres

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.

Tout d'abord, nous devons modifier quelques paramètres liés aux interactions physiques. Entrez dans le monde des couches de physique.

Contrôler les interactions physiques

Les corps physiques ont accès à deux propriétés complémentaires : les calques et les masques. Les calques définissent sur quel(s) calque(s) physique se trouve un objet.

Les masques contrôlent les calques qu'un corps va écouter et détecter. Cela affecte la détection de collisions. Quand vous voulez que deux corps interagissent, il faut qu'au moins l'un d'entre eux ait un masque correspondant à l'autre.

Si cela vous paraît confus, ne vous inquiétez pas, nous allons voir trois exemples dans une seconde.

Le point important est que vous pouvez utiliser les calques et les masques pour filtrer les interactions physiques, contrôler les performances, et supprimer le besoin de conditions supplémentaires dans votre code.

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

Les calques de physique sont représentés par des nombres, mais nous pouvons leur donner des noms pour garder une trace de qui est quoi.

Définition des noms de layer

Donnons un nom à nos calques de physique. Allez dans Projet -> Paramètres du projet....

image0

Dans le menu de gauche, naviguez jusqu'à Layer Names -> 3D Physics. Vous pouvez voir une liste de calques avec un champ à côté de chacun d'entre eux sur la droite. Vous pouvez définir leur nom ici. Nommez les trois premiers calques player, enemies, et world, respectivement.

image1

Maintenant, nous pouvons les assigner à nos nœuds de physique.

Affectation des layers et des masks

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.

Notez que vous pouvez cliquer sur le bouton "..." à droite des propriétés pour voir une liste de cases à cocher nommées.

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.

Définissez son Collision -> Layer sur "enemies" et désactivez son Collision -> Mask, laissant le masque vide.

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.

Note

Les mobs n'ont pas besoin de masquer le calque "world" car ils se déplacent seulement sur le plan XZ. Nous n'avons pas besoin de leur appliquer de gravité, par design.

Sauter

La mécanique de saut ne nécessite que deux lignes de code. Ouvrez le script Player. Nous avons besoin d'une valeur pour contrôler la force de saut et de mettre à jour _physics_process() pour coder le saut.

Après la ligne qui définit fall_acceleration, en haut du script, ajoutez le 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

    #...

C'est tout ce dont vous avez besoin pour sauter !

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.

Écraser les monstres

Ajoutons ensuite la mécanique d'écrasement. Nous allons faire en sorte que le personnage rebondisse sur les monstres et les tue en même temps.

Nous avons besoin de détecter les collisions avec un monstre et de les différencier des collisions avec le sol. Pour ça, nous pouvons utiliser la fonctionnalité de tags de groupe 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.

Cliquez dessus pour faire apparaître un champ où vous pouvez écrire un nom de tag. Entrez "mob" dans le champ et cliquez sur le bouton "Ajouter".

image7

Une icône apparaît dans le dock Scène pour indiquer que le nœud fait partie d'au moins un groupe.

image8

Nous pouvons maintenant utiliser le groupe depuis le code pour distinguer les collisions des monstres des collisions avec le sol.

Coder la mécanique d'écrasement

Retournez au script Player pour coder l'écrasement et le rebond.

En haut du script, nous avons besoin d'une autre propriété, bounce_impulse. Lorsque nous écrasons un ennemi, nous ne voulons pas nécessairement que le personnage monte aussi haut que lors d'un saut.

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

À chaque itération de la boucle, nous vérifions que nous avons atterri sur un mob. Si c'est le cas, nous le tuons et rebondissons.

Avec ce code, si aucune collision ne s'est produite sur une image donnée, la boucle ne s'exécutera pas.

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

Cela fait beaucoup de nouvelles fonctions. Voici quelques informations supplémentaires à leur sujet.

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").

Note

La méthode is_in_group() est disponible sur chaque 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.

Avec les produits scalaires, quand le résultat est supérieur à 0, les deux vecteurs forment un angle inférieur à 90 degrés. Une valeur plus grande que 0.1 nous indique que nous sommes à peu près au-dessus du monstre.

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.

Ouvrez le script Mob.gd en double-cliquant dessus dans le dock Système de fichiers. En haut du script, nous voulons définir un nouveau signal nommé squashed (écrasé). Et en bas, vous pouvez ajouter la fonction squash, d'où nous émettrons le signal et détruirons le mob.

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

# ...


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

Note

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

Nous utiliserons le signal pour ajouter des points au score dans la prochaine leçon.

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.

Cependant, le joueur ne mourra pas encore. Nous y travaillerons dans la partie suivante.