Tuer le joueur

Nous pouvons tuer les ennemis en leur sautant dessus, mais le joueur ne peut pas encore mourir. Réglons ça.

Nous voulons que le fait d'être touché par un ennemi soit détecté différemment de l'écrasement. Nous voulons que le joueur meure quand il se déplace sur le sol, mais pas quand il est dans les airs. Nous pourrions utiliser des maths vectorielles pour distinguer les deux types de collisions. Mais à la place, nous utiliserons un nœud Area, qui fonctionne bien pour les hitboxes.

Hitbox avec le nœud Area

Retournez dans la scène Player et ajoutez un nouveau nœud Area. Nommez-le MobDetector. Ajoutez-lui un nœud CollisionShape comme enfant.

image0

Dans l'Inspecteur, assignez-lui une forme de cylindre.

image1

Voici une astuce que vous pouvez utiliser pour que les collisions ne se produisent que lorsque le joueur est au sol ou à proximité. Vous pouvez réduire la hauteur du cylindre et le déplacer en haut du personnage. De cette façon, quand le joueur saute, la forme sera trop haute pour que les ennemis puissent entrer en collision avec elle.

image2

Vous voulez également que le cylindre soit plus large que la sphère. De cette façon, le joueur se fera toucher avant d'entrer en collision et d'être poussé au-dessus de la boîte de collision du monstre.

Plus le cylindre est large, plus le joueur se fera tuer facilement.

Ensuite, sélectionnez à nouveau le nœud MobDetector, et dans l'Inspecteur, désactivez sa propriété Monitorable. Ainsi, les autres nœuds physiques ne pourront pas détecter la zone. La propriété complémentaire Monitoring lui permet de détecter les collisions. Ensuite, enlevez le Collision -> Layer et définissez le masque sur le calque "enemies".

image3

Quand les zones détectent une collision, elles émettent des signaux. Nous en connecterons un au nœud Player. Dans l'onglet Nœud, double-cliquez sur le signal body_entered et connectez-le au Player.

image4

Le MobDetector émettera body_entered quand un nœud KinematicBody ou RigidBody entrera dans la zone. Comme le masque ne contient que les calques physiques des "enemies", il détectera seulement les nœuds Mob.

Au niveau du code, nous allons faire deux choses : émettre un signal que nous utiliserons plus tard pour mettre fin au jeu et détruire le joueur. Nous pouvons envelopper ces opérations dans une fonction die() qui nous aide à mettre un terme descriptif sur le code.

# Emitted when the player was hit by a mob.
# Put this at the top of the script.
signal hit


# And this function at the bottom.
func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

Essayez le jeu à nouveau en appuyant sur F5. Si tout est configuré correctement, le personnage devrait mourir quand un ennemi lui fonce dessus.

Cependant, notez que cela dépend entièrement de la taille et de la position des formes de collision du Player et du Mob. Vous devrez peut-être les déplacer et les redimensionner pour obtenir une meilleure sensation de jeu.

Arrêter le jeu

Nous pouvons utiliser le signal hit du Player pour mettre fin à la partie. Tout ce que nous avons à faire est de le connecter au nœud Main et d'arrêter le MobTimer en réaction.

Ouvrez Main.tscn, sélectionnez le nœud Player, et dans le dock Nœud, connectez son signal hit au nœud Main.

image5

Récupérez et arrêtez le timer dans la fonction _on_Player_hit().

func _on_Player_hit():
    $MobTimer.stop()

Si vous essayez le jeu maintenant, les monstres cesseront d'apparaître lorsque vous mourrez, et ceux qui restent quitteront l'écran.

Vous pouvez vous féliciter : vous avez réalisé le prototype d'un jeu 3D complet, même si il est encore un peu brouillon.

À partir de là, nous ajouterons un score, l'option de réessayer la partie, et vous verrez comment rendre le jeu beaucoup plus vivant grâce à des animations minimalistes.

Point de contrôle du code

Voici les scripts complets pour les nœuds Main, Mob, et Player, pour référence. Vous pouvez les utiliser pour comparer et vérifier votre code.

En commençant par Main.gd.

extends Node

export(PackedScene) var mob_scene


func _ready():
    randomize()


func _on_MobTimer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instance()

    # Choose a random location on the SpawnPath.
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    # And give it a random offset.
    mob_spawn_location.unit_offset = randf()

    # Communicate the spawn location and the player's location to the mob.
    var player_position = $Player.transform.origin
    mob.initialize(mob_spawn_location.translation, player_position)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)


func _on_Player_hit():
    $MobTimer.stop()

Le suivant est Mob.gd.

extends KinematicBody

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

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)


func initialize(start_position, player_position):
    look_at_from_position(start_position, player_position, Vector3.UP)
    rotate_y(rand_range(-PI / 4, PI / 4))

    var random_speed = rand_range(min_speed, max_speed)
    velocity = Vector3.FORWARD * random_speed
    velocity = velocity.rotated(Vector3.UP, rotation.y)


 func squash():
    emit_signal("squashed")
    queue_free()


func _on_VisibilityNotifier_screen_exited():
    queue_free()

Finalement, le script le plus long, Player.gd.

extends KinematicBody

# Emitted when a mob hit the player.
signal hit

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 20
# Vertical impulse applied to the character upon bouncing over a mob in meters per second.
export var bounce_impulse = 16

var velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

    velocity.x = direction.x * speed
    velocity.z = direction.z * speed

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

    velocity.y -= fall_acceleration * delta
    velocity = move_and_slide(velocity, Vector3.UP)

    for index in range(get_slide_count()):
        var collision = get_slide_collision(index)
        if collision.collider.is_in_group("mob"):
            var mob = collision.collider
            if Vector3.UP.dot(collision.normal) > 0.1:
                mob.squash()
                velocity.y = bounce_impulse


func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

Rendez-vous dans la prochaine leçon pour ajouter le score et l'option pour rejouer.