Score et rejouer¶
Dans cette partie, nous ajouterons le score, la lecture de la musique et la possibilité de redémarrer le jeu.
Nous devons garder la trace du score actuel dans une variable et l'afficher à l'écran en utilisant une interface minimale. Nous allons utiliser une étiquette de texte pour ce faire.
Dans la scène principale, ajoutez un nouveau nœud Control comme enfant de Main et nommez-le UserInterface. Vous serez automatiquement amené à l'écran 2D, où vous pourrez modifier votre interface utilisateur (IU).
Ajoutez un nœud Label et renommez-le en ScoreLabel.
Dans l'Inspecteur, définissez le Text du Label avec un texte générique tel que "Score : 0".
En outre, le texte est blanc par défaut, comme l'arrière-plan de notre jeu. Nous devons changer sa couleur pour le voir au moment de l'exécution.
Faites défiler l'écran jusqu'à Couleurs personnalisées, développez "Couleurs" et cliquez sur la case noire à côté de Couleur de la police pour teinter le texte.
Choisissez un ton sombre pour qu'il contraste bien avec la scène 3D.
Enfin, cliquez et faites glisser le texte dans la fenêtre d'affichage pour l'éloigner du coin supérieur gauche.
Le nœud UserInterface nous permet de regrouper notre interface utilisateur dans une branche de l'arbre de scène et d'utiliser une ressource de thème qui se propagera à tous ses enfants. Nous allons l’utiliser pour définir la police de caractères de notre jeu.
Création d'un thème d'interface utilisateur¶
Une fois encore, sélectionnez le nœud UserInterface. Dans l'Inspecteur, créez une nouvelle ressource de thème dans Theme -> Theme.
Cliquez dessus pour ouvrir l'éditeur de thème dans le panneau inférieur. Il vous donne un aperçu de l'apparence de tous les widgets intégrés à l'interface utilisateur avec votre ressources de thème.
Par défaut, un thème ne possède qu'une seule propriété, la Default Font.
Voir aussi
Vous pouvez ajouter d'autres propriétés à la ressource de thème pour concevoir des interfaces utilisateur complexes, mais cela dépasse le cadre de cette série. Pour en savoir plus sur la création et l'édition de thèmes, consultez Introduction à l'habillage des interfaces graphiques.
Cliquez sur la propriété Default Font et créez une nouvelle DynamicFont.
Développez la DynamicFont en cliquant dessus et développez sa section Font. Vous y verrez un champ vide Font Data.
Celui-ci attend un fichier de police comme ceux que vous avez sur votre ordinateur. DynamicFont supporte les formats suivants :
TrueType (
.ttf
)OpenType(
.otf
)Web Open Font Format 1 (
.woff
)Web Open Font Format 2 (
.woff2
, depuis Godot 3.5)
Dans le dock FileSystem, développez le répertoire fonts
et cliquez et faites glisser le fichier Montserrat-Medium.ttf
que nous avons inclus dans le projet sur le Font Data. Le texte réapparaîtra dans l'aperçu du thème.
Le texte est un peu petit. Réglez le paramètre Settings -> Size sur 22
pixels pour augmenter la taille du texte.
Garder une trace du score¶
Travaillons ensuite sur le score. Attachez un nouveau script au ScoreLabel et définissez la variable score
.
extends Label
var score = 0
public class ScoreLabel : Label
{
private int _score = 0;
}
Le score devrait augmenter de 1
chaque fois que nous écrasons un monstre. Nous pouvons utiliser leur squashed
signal pour savoir quand cela se produit. Cependant, comme nous instancions les monstres à partir du code, nous ne pouvons pas faire la connexion dans l'éditeur.
Au lieu de cela, nous devons établir la connexion depuis le code à chaque fois que nous créons un monstre.
Ouvrez le script Main.gd
. S'il est toujours ouvert, vous pouvez cliquer sur son nom dans la colonne de gauche de l'éditeur de script.
Vous pouvez également double-cliquer sur le fichier Main.gd
dans le dock FileSystem.
Au bas de la fonction _on_MobTimer_timeout()
, ajoutez la ligne suivante.
func _on_MobTimer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
public void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
Cette ligne signifie que lorsque le mob émet le signal squashed
, le noeud ScoreLabel le recevra et appellera la fonction _on_Mob_squashed()
.
Retournez au script ScoreLabel.gd
pour définir la fonction de rappel _on_Mob_squashed()
.
Là, nous incrémentons le score et mettons à jour le texte affiché.
func _on_Mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = string.Format("Score: {0}", _score);
}
La deuxième ligne utilise la valeur de la variable score
pour remplacer le caractère générique %s
. En utilisant cette fonction, Godot convertit automatiquement les valeurs en texte, ce qui est pratique pour sortir du texte dans les étiquettes ou en utilisant la fonction print()
.
Voir aussi
Vous pouvez en savoir plus sur le formatage des chaînes de caractères ici : Chaînes de format GDScript.
Vous pouvez maintenant jouer le jeu et écraser quelques ennemis pour voir le score augmenter.
Note
Dans un jeu complexe, vous voudrez peut-être séparer complètement votre interface utilisateur de l'univers du jeu. Dans ce cas, vous ne garderez pas trace du score sur l'étiquette. Au lieu de cela, vous voudrez peut-être le stocker dans un objet distinct et dédié. Mais pour le prototypage ou lorsque votre projet est simple, il est bon de garder votre code simple. La programmation est toujours un exercice d'équilibre.
Réessayer le jeu¶
Nous allons maintenant ajouter la possibilité de rejouer après être mort. Lorsque le joueur meurt, nous affichons un message à l'écran et nous attendons une entrée.
Revenez à la scène Main, sélectionnez le noeud UserInterface, ajoutez un noeud ColorRect comme enfant de celui-ci et nommez-le Retry. Ce nœud remplit un rectangle avec une couleur uniforme et servira de superposition pour assombrir l'écran.
Pour qu'il s'étende sur toute la fenêtre, vous pouvez utiliser le menu Layout de la barre d'outils.
Ouvrez-le et appliquez la commande Full Rect.
Rien ne se passe. Enfin, presque rien : seules les quatre broches vertes se déplacent vers les coins de la boîte de sélection.
En effet, les nœuds d'interface utilisateur (tous ceux qui ont une icône verte) fonctionnent avec des ancres et des marges relatives à la boîte englobante de leur parent. Ici, le nœud UserInterface a une petite taille et le nœud Retry est limité par celle-ci.
Sélectionnez UserInterface et appliquez Layout -> Full Rect à celle-ci également. Le nœud Retry devrait maintenant couvrir la totalité de la fenêtre.
Changeons sa couleur pour qu'il assombrisse la zone de jeu. Sélectionnez Retry et dans l'Inspecteur, définissez sa Couleur sur quelque chose de sombre et de transparent. Pour ce faire, dans le sélecteur de couleurs, faites glisser le curseur A vers la gauche. Il contrôle le canal alpha de la couleur, c'est-à-dire son opacité.
Ensuite, ajoutez un Label en tant qu'enfant de Retry et donnez-lui le Text "Press Enter to retry."
Pour le déplacer et l'ancrer au centre de l'écran, appliquez-lui Layout -> Center.
Coder l'option réessayer¶
Nous pouvons maintenant nous diriger vers le code pour afficher et cacher le nœud Retry lorsque le joueur meurt et rejoue.
Ouvrez le script Main.gd
. Tout d'abord, nous voulons cacher l'overlay au début du jeu. Ajoutez cette ligne à la fonction _ready()
.
func _ready():
#...
$UserInterface/Retry.hide()
public override void _Ready()
{
// ...
GetNode<Control>("UserInterface/Retry").Hide();
}
Ensuite, quand le joueur est touché, nous montrons la surimpression.
func _on_Player_hit():
#...
$UserInterface/Retry.show()
public void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
Enfin, lorsque le noeud Retry est visible, nous devons écouter l'entrée du joueur et redémarrer le jeu s'il appuie sur Entrée. Pour ce faire, nous utilisons le callback intégré _unhandled_input()
.
Si le joueur a appuyé sur l'action d'entrée prédéfinie ui_accept
et que Retry est visible, nous rechargeons la scène actuelle.
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
# This restarts the current scene.
get_tree().reload_current_scene()
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
// This restarts the current scene.
GetTree().ReloadCurrentScene();
}
}
La fonction get_tree()
nous donne accès à l'objet global SceneTree, qui nous permet de recharger et de redémarrer la scène actuelle.
Ajouter de la musique¶
Pour ajouter une musique qui joue continuellement en arrière-plan, nous allons utiliser une autre fonctionnalité de Godot : autoloads.
Pour lire de l'audio, il suffit d'ajouter un nœud AudioStreamPlayer à votre scène et d'y attacher un fichier audio. Lorsque vous démarrez la scène, elle peut jouer automatiquement. Cependant, lorsque vous rechargez la scène, comme nous le faisons pour jouer à nouveau, les nœuds audio sont également réinitialisés, et la musique reprend depuis le début.
Vous pouvez utiliser la fonction autoload pour que Godot charge automatiquement un nœud ou une scène au début du jeu, en dehors de la scène actuelle. Vous pouvez également l'utiliser pour créer des objets accessibles à tous.
Créez une nouvelle scène en allant dans le menu Scène et en cliquant sur Nouvelle scène.
Cliquez sur le bouton Autre nœud pour créer un AudioStreamPlayer et le renommer en MusicPlayer.
Nous avons inclus une bande sonore dans le répertoire art/
, House In a Forest Loop.ogg
. Cliquez et faites-la glisser sur la propriété Stream dans l'Inspector. Activez également l'option Autoplay pour que la musique soit jouée automatiquement au début du jeu.
Enregistrez la scène sous le nom de MusicPlayer.tscn
.
Nous devons l'enregistrer en tant qu'autoload. Allez dans le menu Projet -> Paramètres du projet... et cliquez sur l'onglet Autoload.
Dans le champ Chemin, vous voulez entrer le chemin d'accès à votre scène. Cliquez sur l'icône du dossier pour ouvrir le navigateur de fichiers et double-cliquez sur MusicPlayer.tscn
. Ensuite, cliquez sur le bouton Add à droite pour enregistrer le nœud.
Si vous lancez le jeu maintenant, la musique jouera automatiquement. Et même si vous perdez et réessayez, elle continue.
Avant de conclure cette leçon, voici un bref aperçu de son fonctionnement. Lorsque vous lancez le jeu, votre dock Scene change pour vous donner deux onglets : Remote et Local.
L'onglet Remote vous permet de visualiser l'arbre de nœuds de votre jeu en cours. Vous y verrez le nœud Main et tout ce que la scène contient ainsi que les mobs instanciés en bas.
Au sommet se trouvent le MusicPlayer autochargé et un noeud root, qui est le viewport de votre jeu.
Et c'est tout pour cette leçon. Dans la prochaine partie, nous ajouterons une animation pour rendre le jeu plus agréable à regarder et à toucher.
Voici le script complet Main.gd
pour référence.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
$UserInterface/Retry.hide()
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
get_tree().reload_current_scene()
func _on_MobTimer_timeout():
var mob = mob_scene.instance()
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
func _on_Player_hit():
$MobTimer.stop()
$UserInterface/Retry.show()
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
GetNode<Control>("UserInterface/Retry").Hide();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
GetTree().ReloadCurrentScene();
}
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
public void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Control>("UserInterface/Retry").Show();
}
}