Création du jeu «Like Coins» sur Godot Engine. 2e partie

J'espère que vous en avez assez d'attendre la deuxième partie de l'article, qui traite des aspects du développement de jeux en utilisant le moteur Godot, en utilisant l'exemple du jeu Like Coins? Beaucoup de tout «savoureux» et «sain» a été préparé à l'ordre du jour. Faites immédiatement une réservation pour que dans cet article nous terminions le jeu démarré précédemment, dont vous pouvez lire le début ici - Création du jeu "Like Coins" sur le moteur Godot. Partie 1 , mais la série d'articles se poursuivra, car il y avait tellement de matériel qui m'a fait en mettre de côté, mais nous y reviendrons certainement plus tard. Que le "gamedev" commence!


Scène "Main"


Dans la partie précédente de l'article, nous nous sommes arrêtés sur la scène principale ( Main ), et à partir de là, nous continuerons peut-être. Nous supprimons tout ce qui a été ajouté plus tôt (si vous avez ajouté quelque chose bien sûr pour vérifier comment tout fonctionne), si rien n'a été chargé dans la scène, alors nous devrions ajouter Node , qui sera le parent des nœuds répertoriés ci-dessous, qui à son tour devrait également être ajouté à la scène:


ColorRect ("Arrière-plan") - remplissez la couleur d'arrière-plan;
Player - l'objet "Player" (j'espère que vous n'êtes pas confus parce que j'appelle la scène Player un objet?);
Node ("Container") - "container" pour le stockage temporaire des pièces;
Position2D ("PlayerStart") - au début du jeu définit la position initiale de l'objet "Player";
Timer ("GameTimer") - compteur de limite de temps;


Sélectionnez ColorRect et dans la barre d'outils, sélectionnez: Layout -> Full Rect pour l'étirer sur toute la zone de l'écran (à l'avenir, nous recourrons souvent à cette fonction, je vous conseille donc d'étudier vous-même d'autres opérations spécifiées dans la liste des Layout ), pour ce nœud, dans la propriété "Couleur", spécifiez la couleur de remplissage souhaitée. Vous pouvez faire de même avec TextureRect , mais au lieu de remplir, vous devrez charger l'image via la propriété "Texture". Pour Position2D , dans la propriété "position", spécifiez les valeurs de "x" et "y" - cela servira de position initiale pour le Player . Bien sûr, avec l'aide du script, vous pouvez définir les valeurs de positionnement directement dans le Player lui-même, mais nous apprenons non seulement à développer des jeux, mais aussi à étudier "Godot", par conséquent, l'examen de différentes options pour résoudre un problème ne sera pas superflu.


Script pour "Main"


Ajoutez un script pour Node et imprimez ce qui suit:


 extends Node #PackedScene        export (PackedScene) var Coin export (int) var playtime var level #  var score # var left_time #     var window_size #   var playing = false #    

Les propriétés "Coin" et "playtime" seront affichées dans l' Inspector . Faites glisser la scène "Coin.tscn" vers la propriété "Coin" et réglez "playtime" sur "40" (la durée du jeu en secondes).


Lorsque le jeu démarre, chaque fois il doit y avoir une initialisation - préparation au travail, détermination des paramètres requis pour un fonctionnement de haute qualité et sans erreur de l'application. Il s'agit d'une étape obligatoire, vous devez donc vous en occuper en premier.


 func _ready(): randomize() #        window_size = get_viewport().get_visible_rect().size #    $Player.window_size = window_size #    "" $Player.hide() #   

Notez que lorsque vous spécifiez le nom de l'objet Player , le symbole «$» est utilisé - il s'agit de «sucre syntaxique» qui vous permet d'accéder directement au nœud dans la scène actuelle, une bonne alternative à la get_node("Node1") (bien que l'utilisation de ce dernier ne soit pas interdite). Si "Node1" a un descendant de "Node2", vous pouvez également utiliser cette méthode - $Node1/Node2 . Notez que le remplissage automatique fonctionne très bien dans Godot, alors ne le négligez pas. L'utilisation d'un espace dans les noms des nœuds n'est pas souhaitable, mais reste valide, dans ce cas, utilisez des guillemets - $"My best Node1" .


Nouveau jeu


Pour démarrer une nouvelle partie, nous allons déterminer la fonction correspondante pour cela, que nous pouvons ensuite appeler, par exemple, en appuyant sur un bouton.


 func new_game(): playing = true #   level = 1 score = 0 time_left = playtime $Player.start($PlayerStart.position) $Player.show() $GameTimer.start() #    spawn_coins() #  

La fonction "start ()" dont l'argument est $PlayerStart.position déplacera le joueur à l'emplacement de départ, et la fonction "spawn_coins ()" est responsable, comme vous pouvez le deviner, de générer des pièces sur le terrain de jeu.


 func spawn_coins(): for i in range(4 + level): var c = Coin.instance() $CoinContainer.add_child(c) c.window_size = window_size c.position = Vector2(rand_range(0, window_size.x), rand_range(0, window_size.y)) 

La fonction range(4 + level) renverra un tableau avec une gamme donnée dont la valeur est égale à la somme du nombre de pièces et de la valeur du niveau actuel. Une plage peut contenir un argument, comme dans notre cas, soit deux arguments soit trois arguments (le troisième argument sera une étape de tableau). Dans cette fonction, nous créons plusieurs instances de l'objet "Coin" et ajoutons CoinContainer comme éléments enfants (j'espère que vous n'avez pas oublié que nous avons déjà accès à l'objet, grâce à PackedScene ). N'oubliez pas que chaque fois que vous créez une instance d'un nouveau nœud (la méthode instance() ), elle doit être ajoutée à l'arborescence à l'aide de add_child() . Ensuite, nous définissons la zone pour une éventuelle apparition de pièces afin qu'elles n'apparaissent pas accidentellement derrière l'écran, puis attribuons une position au hasard. La dernière ligne ne semble pas un peu esthétique, je suggère donc de la simplifier en recourant au Singleton.


Singleton


Le deuxième prénom du Singleton est «Startup». Déjà suggestif, non? Je vous le dis, un singleton fonctionne comme suit: un script dans lequel nous pouvons écrire tout ce que nous voulons (à partir de la déclaration de variables et se terminant par des "commutateurs" de scènes, y compris leur chargement et leur déchargement) est chargé en premier, avec l'application en cours d'exécution, et tout son contenu est accessible depuis n'importe quel points de projet. D'une certaine manière, il s'agit d'une sorte de référentiel global personnalisé de «tout» disponible à tout moment.


Notez que le projet a son propre référentiel global, dont nous pouvons également utiliser le contenu, et vous pouvez y accéder en utilisant ProjectSettings.get_setting(name) , où name est le nom du paramètre requis.

Maintenant, pour utiliser quelque chose du référentiel "_G", il suffit de l'appeler par son nom, puis de spécifier la méthode à appeler, ou tout ce que nous avons là. Donc, créez un script vide et écrivez-y la fonction indiquée ci-dessous:


 extends Node func rand(): var rrand = Vector2(rand_range(40, 760), rand_range(40, 540)) return rrand #   


Ensuite, enregistrez-le et accédez aux paramètres du projet: Project -> Project Settings -> AutoLoad . Nous sélectionnons notre script nouvellement créé, lui donnons un nom, par exemple, "_G", et retournons à la fonction "spawn_coins ()" pour ajuster légèrement le délai, en le remplaçant par le code suivant:


  ... c.position = _G.rand() 

Maintenant, il vaut la peine de vérifier ce qui s'est passé en plaçant "spawn_coins ()" dans le bloc "_ready ()" et en exécutant l'application sur F5. Et n'oubliez pas de sélectionner Main.tscn comme scène principale, si pour une raison quelconque vous avez fait une erreur de choix, vous pouvez changer la scène principale manuellement, pour cela, vous devez aller dans les paramètres du projet: General -> Run -> MainScene . Ça marche? Continuez ensuite.


Combien de pièces reste-t-il?


Continuons. Ensuite, vous devez vérifier combien il reste de pièces pour transférer le joueur au niveau suivant, lui donner un petit «bonus» sous la forme d'une augmentation de temps de 5 secondes, puis réapparaître les pièces.


 func _process(delta): #   ?     ? if playing and $CoinContainer.get_child_count() == 0: #    level += 1 # ""   time_left += 5 #  spawn_coins() 

Interface utilisateur


Toute notre interface sera composée des éléments suivants: indicateur de score, niveau actuel, heure, nom du jeu et un bouton qui déclenchera le lancement du jeu. Créez une scène ( HUD.tscn ) avec un parent CanvasLayer (vous permet de dessiner une interface utilisateur au-dessus du terrain de jeu). Pour l'avenir, je dirai qu'il n'est pas très pratique de gérer les éléments de l'interface utilisateur, du moins pour moi, c'est vrai, mais une liste assez large d'éléments et un développement actif inspirent une atmosphère positive dans un avenir radieux pour le développement de cet aspect du moteur.



Dans Godot, il existe des "nœuds de contrôle" qui vous permettent de formater automatiquement les éléments enfants par rapport aux paramètres spécifiés du parent. Chaque type de "nœuds de contrôle" possède des propriétés spéciales qui contrôlent la façon dont ils contrôlent l'emplacement de leurs descendants. Un représentant vivant de ce type est MarginContainer , qui doit être ajouté à la scène. En utilisant Layout -> Top Wide étirez-le en haut de la fenêtre et dans les propriétés de cet objet, dans la section Margin , spécifiez les retraits à partir des bords: gauche, haut et droite. MarginContainer doit avoir trois Label enfants avec les noms suivants: ScoreLabel , LevelLabel et TimeLabel . Ajoutez-les à la scène. À l'aide de la propriété Align , alignez-les à gauche, au centre et à droite. Il reste à ajouter un autre Label ( Messagelabel ), en le plaçant au centre, le tout aussi à l'aide de Layout , et un peu plus bas le bouton ( StartButton ).



Maintenant, rendons l'interface réactive, nous devons mettre à jour l'heure, le nombre de pièces collectées et mettre en évidence le niveau actuel. Ajoutez un script pour le nœud HUD .


 extends CanvasLayer signal start_game func update_score(value): $MarginContainer/ScoreLabel.text = str(value) func update_level(value): if len(str(value)) == 1: $MarginContainer/TimeLabel.text = "0: 0" + str(value) else: $MarginContainer/TimeLabel.text = "0: " + str(value) func update_timer(value): $MarginContainer/TimeLabel.txt = str(value) 

Pour MessageLabel nous avons besoin d'une minuterie afin de changer le texte du message pendant une courte période. Ajoutez un nœud Timer et remplacez son nom par MessageTimer . Dans l'inspecteur, définissez le temps d'attente sur 2 secondes et cochez la case dans le champ One Shot . Cela garantit que la minuterie ne s'exécute qu'une seule fois au démarrage.


 func show_message(text): $MessageLabel.text = text $MessageLabel.show() $MessageTimer.start() 

Connectez le signal timeout() à "MessageTimer" et ajoutez ce qui suit:


 func _on_MessageTimer_timeout(): $MessageLabel.hide() 

Sur l'onglet "Node" pour StartButton , connectez le signal pressed() . Lorsque vous cliquez sur le bouton StartButton il devrait disparaître avec MessageLabel , puis envoyer un signal à la scène principale, où nous réussirons ensuite à l'intercepter en même temps en glissant la fonction à exécuter - "new_game ()". Nous implémentons cela en utilisant le code ci-dessous. N'oubliez pas que le bouton de la propriété Text de définir tout appel de texte pour démarrer le jeu.


 func _on_StartButton_pressed(): $StartButton.hide() $MessageLabel.hide() emit_signal("start_game") 

Pour finir enfin avec l'interface, nous allons écrire la dernière fonction finale - la fonction d'afficher un message sur la fin du jeu. Dans cette fonction, nous avons besoin que l'inscription "Game Over" soit affichée pendant pas plus de deux secondes, puis disparaisse, ce qui est possible grâce à la fonction "show_message ()". Cependant, vous devez à nouveau afficher le bouton de démarrage d'un nouveau jeu, dès qu'un message vous informant que le jeu est terminé disparaît. yield() suspendra l'exécution de la fonction jusqu'à ce qu'un signal de MessageTimer soit reçu, et la réception d'un signal de MessageTimer concernant son exécution, la fonction continuera à s'exécuter, nous ramenant à son état d'origine afin que nous puissions recommencer une nouvelle partie.


 func show_game_over(): show_message("Game Over") yield($MessageTimer, "timeout") $StartButton.show() $MessageLabel.text = "LIKE COINS!" $MessageLabel.show() 

Fin?


Configurons la rétroaction entre le HUD et le Main . Ajoutez la scène HUD à la scène principale et connectez le signal GameTimer via timeout() sur la scène principale en ajoutant ce qui suit:


 func _on_GameTimer_timeout(): time_left -= 1 #  $HUD.update_timer(time_left) #   if time_left <= 0: game_over() #     

Connectez ensuite les signaux pickup() et die() du lecteur.


 func _on_Player_pickup(): score += 1 $HUD.update_score(score) func _on_Player_die(): game_over() 

À la fin du jeu, plusieurs autres choses devraient se produire qui ne devraient pas être négligées. Écrivez le code suivant et je vais vous expliquer.


 func game_over(): playing = false $GameTimer.stop() for coin in $CoinContainer.get_children(): coin.queue_free() $HUD.show_game_over() $Player.die() 

Cette fonction arrêtera le jeu, puis les pièces restantes seront show_game_over() et les pièces restantes seront show_game_over() , puis show_game_over() sera appelé pour le HUD . L'étape suivante consiste à démarrer l'animation et à arrêter le processus d'exécution du nœud Player .


Enfin, vous devez activer StartButton , qui doit être connecté à la fonction new_game() . Cliquez sur le nœud HUD et dans la boîte de dialogue de connexion, vous devez cliquer sur Make Function to Off (cela empêchera la création d'une nouvelle fonction) et dans le champ Method In Node , spécifiez le nom de la fonction à connecter - new_game . Cela connectera le signal à une fonction existante, plutôt que d'en créer une nouvelle.


La touche finale consiste à supprimer new_game() de la fonction _ready() et à ajouter les deux lignes suivantes à la fonction new_game() :


 ... $HUD.update_score(score) $HUD.update_timer(time_left) 

Maintenant, nous pouvons dire avec confiance que le jeu est prêt, maintenant il est assez «jouable», mais sans effets. Nous examinerons ce dernier dans le prochain article, en accordant une attention énorme à diverses «décorations» afin de diversifier le gameplay et d'explorer davantage les possibilités de «Godot». Par conséquent, n'oubliez pas de suivre la sortie des articles. Bonne chance!

Source: https://habr.com/ru/post/fr430800/


All Articles