Coder le joueur

Dans cette leçon, nous ajouterons le mouvement du joueur, l'animation, et la détection des collisions.

Pour ce faire, nous devons ajouter des fonctionnalités que nous ne pouvons pas obtenir à partir d'un nœud intégré, nous allons donc ajouter un script. Cliquez sur le nœud Player et cliquez sur le bouton "Attacher un script" :

../../_images/add_script_button.png

Dans la fenêtre de réglages du script, vous pouvez laisser les paramètres par défaut. Cliquez simplement sur "Créer" :

Note

Si vous créez un script en C# ou un autre langage, sélectionnez le language dans le menu déroulant langage avant de cliquer sur créer.

../../_images/attach_node_window.png

Note

Si c'est la première fois que vous rencontrez du GDScript, merci de lire Langages de script avant de continuer.

Commencez par déclarer les variables membres dont cet objet aura besoin :

extends Area2D

export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

L'utilisation du mot-clé export sur la première variable SPEED nous permet de définir sa valeur dans l'inspecteur. Cela peut être pratique pour les valeurs que vous voulez pouvoir ajuster de la même manière que les propriétés intégrées d'un nœud. Cliquez sur le nœud Player et vous verrez maintenant apparaître la section "Script Variables" dans l'inspecteur. Notez que, si vous changez la valeur ici, cela remplacera la valeur écrite dans le script.

Avertissement

Si vous utilisez C#, vous devez (re)compiler les assemblages du projet chaque fois que vous voulez voir de nouvelles variables d'exportation ou des nouveaux signaux. Cette compilation peut être déclenchée manuellement en cliquant sur le mot "Mono" au bas de la fenêtre de l'éditeur pour afficher le panneau Mono, puis en cliquant sur le bouton "Compiler Projet".

../../_images/export_variable.png

La fonction _ready() est appelée lorsqu'un nœud entre dans l'arbre de scène, ce qui est un bon moment pour trouver la taille de la fenêtre de jeu :

func _ready():
    screen_size = get_viewport_rect().size

Maintenant nous pouvons utiliser la fonction _process() pour définir ce que le joueur va faire. _process() est appelée à chaque image, nous l'utiliserons donc pour mettre à jour les éléments de notre jeu qui vont changer souvent. Pour le joueur, nous devons faire ce qui suit :

  • Vérifier les entrées.

  • Se déplacer dans la direction donnée.

  • Jouer l'animation appropriée.

Tout d'abord, nous devons vérifier les entrées - le joueur appuie-t-il sur une touche ? Pour ce jeu, nous avons 4 entrées de direction à vérifier. Les actions d'entrées sont définies dans les Paramètres du projet sous "Contrôles". Vous pouvez définir des événements personnalisés et leur affecter différentes touches, des événements souris ou d'autres entrées. Pour ce jeu, nous allons associer les touches fléchées aux quatre directions.

Cliquez sur Projet -> Paramètres du projet... pour ouvrir la fenêtre des paramètres du projet et cliquez sur l'onglet Contrôles en haut. Tapez "move_right" dans la barre supérieure et cliquez sur le bouton "Ajouter" pour ajouter l'action move_right.

../../_images/input-mapping-add-action.png

Nous devons attribuer une touche à cette action. Cliquez sur l'icône "+" à droite, puis sur l'option "Touche" dans le menu déroulant. Une boîte de dialogue vous demande de saisir la touche souhaitée. Appuyez sur la flèche droite de votre clavier et cliquez sur "Ok".

../../_images/input-mapping-add-key.png

Répétez ces étapes pour ajouter trois autres contrôles :

  1. move_left lié à la touche flèche gauche.

  2. move_up lié à la touche flèche haut.

  3. Et move_down lié à la touche flèche vers le bas.

Votre onglet contrôles devrait ressembler à ceci :

../../_images/input-mapping-completed.png

Cliquez sur le bouton "Fermer" pour fermer les paramètres du projet.

Note

Nous n'avons associé qu'une seule touche à chaque action d'entrée, mais vous pouvez associer plusieurs touches, boutons de joystick ou boutons de souris à la même action d'entrée.

Vous pouvez détecter si une touche est pressée en utilisant Input.is_action_pressed(), qui retourne true s'il elle est pressée ou false si elle ne l'est pas.

func _process(delta):
    var velocity = Vector2.ZERO # The player's movement vector.
    if Input.is_action_pressed("move_right"):
        velocity.x += 1
    if Input.is_action_pressed("move_left"):
        velocity.x -= 1
    if Input.is_action_pressed("move_down"):
        velocity.y += 1
    if Input.is_action_pressed("move_up"):
        velocity.y -= 1

    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.stop()

Nous commençons par régler velocity à (0, 0) - par défaut le joueur ne doit pas bouger. Ensuite nous vérifions chaque entrées et les ajoutons ou les soustrayons à la velocity pour obtenir une direction totale. Par exemple, si vous maintenez right et down en même temps, le vecteur velocity résultant sera (1, 1). Dans ce cas, puisque nous ajoutons un mouvement horizontal et un mouvement vertical, le joueur se déplacerait plus vite que s'il se déplaçait horizontalement.

Nous pouvons empêcher cela si nous normalisons la vitesse, ce qui signifie que nous réglons sa longueur à 1, et la multiplions par la vitesse désirée. Cela signifie qu'il n'y a plus de mouvement diagonal rapide.

Astuce

Si vous n'avez jamais utilisé les mathématiques vectorielles auparavant, ou si vous avez besoin d'un rafraîchissement, vous pouvez voir une explication de l'utilisation des vecteurs dans Godot à Mathématiques des vecteurs. C'est bon à savoir mais ça ne sera pas nécessaire pour le reste de ce tutoriel.

Nous vérifions également si le joueur se déplace afin de pouvoir appeler play() ou stop() sur l'AnimatedSprite.

Astuce

$ est un raccourci pour get_node(). Dans le code ci-dessus, $AnimatedSprite.play() est donc identique à get_node("AnimatedSprite").play().

En GDScript, $ retourne le nœud au chemin relatif depuis ce nœud, ou retourne null si le nœud n'est pas trouvé. Puisque AnimatedSprite est un enfant du nœud courant, nous pouvons utiliser $AnimatedSprite.

Maintenant que nous avons une direction de mouvement, nous pouvons mettre à jour la position du joueur. Nous pouvons aussi utiliser clamp() pour l'empêcher de quitter l'écran. Clamping une valeur signifie la limiter à une plage donnée. Ajoutez ce qui suit au bas de la fonction _process (assurez-vous que ce n'est pas indenté sous le else) :

position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)

Astuce

Le paramètre delta de la fonction _process() fait référence à la longueur d'une trame - le temps que l'image précédente a mis pour se terminer. Utiliser cette valeur assure que le mouvement restera constant même si le taux d'images par seconde varie.

Cliquez sur "Lancer la scène" (F6, Cmd + R on macOS) et vérifiez que vous pouvez déplacer le joueur autour de l'écran dans toutes les directions.

Avertissement

Si vous obtenez une erreur le panneau "Debugger" qui dit

Attempt to call function 'play' in base 'null instance' on a null instance

cela signifie probablement que vous avez mal orthographié le nom du nœud AnimatedSprite. Les noms de nœuds sont sensibles à la casse et $NodeName doit correspondre au nom que vous voyez dans l'arbre de scènes.

Choisir les animations

Maintenant que le joueur peut se déplacer, nous devons changer l'animation que l'AnimatedSprite joue en fonction de la direction. Nous avons une animation "walk", qui montre le joueur marchant vers la droite. Cette animation doit être retournée horizontalement en utilisant la propriété flip_h pour le mouvement vers la gauche. Nous avons aussi l'animation "up", qui doit être retournée verticalement avec flip_v pour le mouvement vers le bas. Rajoutez ce code à la fin de notre fonction _process() :

if velocity.x != 0:
    $AnimatedSprite.animation = "walk"
    $AnimatedSprite.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite.animation = "up"
    $AnimatedSprite.flip_v = velocity.y > 0

Note

Les affectations booléennes dans le code ci-dessus sont un raccourci courant pour les programmeurs. Puisque nous faisons un test de comparaison (booléen) et aussi assignons une valeur booléenne, nous pouvons faire les deux en même temps. Considérez ce code par rapport à l'affectation booléenne d'une ligne ci-dessus :

if velocity.x < 0:
    $AnimatedSprite.flip_h = true
else:
    $AnimatedSprite.flip_h = false

Relancez la scène et vérifiez que les animations sont correctes dans chacune des directions.

Astuce

Une erreur courante consiste ici à mal taper les noms des animations. Les noms des animations dans le panneau SpriteFrames doivent correspondre à ce que vous tapez dans le code. Si vous avez nommé l'animation "Walk", vous devez également utiliser un "W" majuscule dans le code.

Lorsque vous êtes sûr que le mouvement fonctionne correctement, ajoutez cette ligne à _ready(), afin que le joueur soit caché au début du jeu :

hide()

Préparation pour les collisions

Nous voulons que Player détecte quand il est touché par un ennemi, mais nous ne n’avons pas encore créé d'ennemis ! Ce n'est pas grave, car nous allons utiliser la fonctionnalité signal de Godot pour le faire fonctionner.

Ajoutez ce qui suit en haut du script, après extends Area2D :

signal hit

Ceci définit un signal personnalisé appelé "hit" que notre joueur émettra lorsqu'il entre en collision avec un ennemi. Nous utiliserons Area2D pour détecter la collision. Sélectionnez le nœud Player et cliquez sur l'onglet "Nœud" à côté de l'onglet Inspecteur pour voir la liste des signaux que le joueur peut émettre :

../../_images/player_signals.png

Notez que notre signal personnalisé "hit" est là aussi ! Puisque nos ennemis vont être des nœuds RigidBody2D, nous avons besoin du signal body_entered(body: Node).Celui-ci sera émis lorsqu'un objet percute le joueur. Cliquez sur "Connecter..." et la fenêtre "Connecter un signal" apparaîtra. Nous n'avons pas besoin de modifier ces paramètres, alors cliquez à nouveau sur "Connect". Godot va automatiquement créer une fonction dans le script de votre joueur.

../../_images/player_signal_connection.png

Notez l'icône verte indiquant qu'un signal est connecté à cette fonction. Ajoutez ce code à la fonction :

func _on_Player_body_entered(body):
    hide() # Player disappears after being hit.
    emit_signal("hit")
    # Must be deferred as we can't change physics properties on a physics callback.
    $CollisionShape2D.set_deferred("disabled", true)

Chaque fois qu'un ennemi frappe le joueur, le signal sera émis. Nous devons désactiver la collision du joueur afin de ne pas déclencher le signal hit plus d'une fois.

Note

Désactiver la forme de la zone de collision peut provoquer une erreur si cela se produit pendant le traitement des collisions par le moteur. L'utilisation de set_deferred() indique à Godot d'attendre pour désactiver la forme jusqu'à ce que l'on puisse le faire en toute sécurité.

La dernière étape consiste à ajouter une fonction que nous pouvons appeler pour réinitialiser le joueur au début d'une nouvelle partie.

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false

Maintenant que le joueur fonctionne, nous allons travailler sur l'ennemi dans la prochaine leçon.