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" :
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.
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.
using Godot;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
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".
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
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_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
.
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".
Répétez ces étapes pour ajouter trois autres contrôles :
move_left
lié à la touche flèche gauche.move_up
lié à la touche flèche haut.Et
move_down
lié à la touche flèche vers le bas.
Votre onglet contrôles devrait ressembler à ceci :
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()
public override void _Process(float delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.x += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.x -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.y -= 1;
}
var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->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)
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
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
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
if (velocity.x != 0) {
_animated_sprite->set_animation("walk");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_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
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.FlipH = 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()
Hide();
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
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
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 :
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.
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)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->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
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
Maintenant que le joueur fonctionne, nous allons travailler sur l'ennemi dans la prochaine leçon.