paint-brush
Comment ajouter un HUD à votre jeu Flamepar@eugene-kleshnin
1,767 lectures
1,767 lectures

Comment ajouter un HUD à votre jeu Flame

par Eugene Kleshnin8m2023/03/20
Read on Terminal Reader

Trop long; Pour lire

C'est la dernière partie de ma série en 4 parties où j'apprends à créer un jeu de plateforme simple avec le moteur Flame. Dans cette partie, nous allons ajouter des pièces, qui pourraient être collectées par le personnage, un HUD pour afficher le nombre de pièces dont dispose le joueur et un écran de victoire.
featured image - Comment ajouter un HUD à votre jeu Flame
Eugene Kleshnin HackerNoon profile picture
0-item

C'est la dernière partie de ma série en 4 parties où j'apprends à créer un jeu de plateforme simple avec le moteur Flame. Nous savons déjà comment ajouter un personnage de joueur animé que j'ai nommé The Boy, comment créer un niveau de jeu déroulant à l'aide de l'éditeur Tiled et comment ajouter de la gravité et des sauts à l'aide de la détection de collision (parties 1 , 2 , 3 ).


Dans cette partie, nous allons ajouter des pièces, qui pourraient être collectées par le personnage, un HUD pour afficher le nombre de pièces dont dispose le joueur, et un écran de victoire, que nous allons afficher une fois toutes les pièces collectées.


Ajouter des pièces

Nous devons indiquer au jeu où générer des pièces (ou tout autre objet de jeu d'ailleurs). Comme vous l'avez peut-être deviné, nous allons utiliser l'éditeur Tiled pour ajouter un autre calque d'objets, de la même manière que nous avons ajouté des plates-formes, mais avec deux différences :


  1. Avec les plates-formes, nous avons intégré des images de sprites dans les données de niveau. Cette méthode n'est pas très adaptée aux pièces, car on souhaite supprimer l'objet, une fois que le joueur l'a récupéré. Nous allons donc simplement ajouter des points d'apparition, pour savoir où les pièces doivent apparaître dans le jeu, et le rendu se fera à l'aide de composants Flame.


  2. Pour les plates-formes, nous avons utilisé des rectangles de n'importe quelle taille, mais pour les pièces, nous allons ajouter des points d'apparition de la taille d'une tuile. Cependant, si vous souhaitez ajouter une ligne de pièces l'une après l'autre (salut Mario), vous pouvez facilement le faire en modifiant le code du jeu et en tenant compte de la taille d'un point d'apparition lors de l'ajout d'objets pièces. Mais pour les besoins de cette série, nous supposons que les points d'apparition des pièces sont 1x1.


Ouvrez notre niveau dans l'éditeur en mosaïque et créez un nouveau calque d'objet appelé Coins. Ensuite, à l'aide de l'outil Rectangulaire, ajoutez plusieurs points d'apparition sur la carte pour que nous puissions ajouter des composants de pièces à l'aide du moteur de jeu. Mon niveau ressemble maintenant à ceci :


Points d'apparition pour les pièces


Je dois ajouter que notre jeu est plutôt simple et nous savons que ces rectangles vides se transformeront en pièces au cours du jeu. Mais si nous voulons ajouter plus de types d'objets, il deviendra difficile de les distinguer. Heureusement, Tiled a un outil pour cela, appelé "Insert Tile" qui peut ajouter des repères visuels pour chaque objet, mais ces images ne seront pas rendues dans le jeu.


Très bien, enregistrez le niveau et revenez à l'IDE. Ajoutons notre classe Coin au dossier /objects/ .

 class Coin extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { late final SpriteAnimation spinAnimation; late final SpriteAnimation collectAnimation; Coin(Vector2 position) : super(position: position, size: Vector2.all(48)); @override Future<void> onLoad() async { spinAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(16), stepTime: 0.12, ), ); collectAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.range( start: 4, end: 7, amount: 8, textureSize: Vector2.all(16), stepTimes: List.filled(4, 0.12), loop: false ), ); animation = spinAnimation; final hitbox = RectangleHitbox() ..collisionType = CollisionType.passive; add(hitbox); return super.onLoad(); } }


Nous avons 2 animations différentes pour la rotation et la collecte et un RectangleHitbox , pour vérifier les collisions avec le joueur plus tard.


Ensuite, revenez à game.dart et modifiez la méthode spawnObjects pour générer nos pièces :

 final coins = tileMap.getLayer<ObjectGroup>("Coins"); for (final coin in coins!.objects) { add(Coin(Vector2(coin.x, coin.y))); }


Lancez le jeu et voyez les pièces ajoutées :

Le jeu a ajouté CoinComponent pour chaque point d'apparition


Faisons-les maintenant disparaître lorsque le joueur les récupère.


Revenez à coin.dart et ajoutez la méthode collect :

 void collect() { animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; }


Lorsque cette méthode est appelée, nous allons basculer l'animation de rotation vers celle de collecte et une fois terminée, nous allons supprimer ce composant du jeu.


Ensuite, accédez à la classe theboy.dart et remplacez la méthode onCollisionStart :

 @override void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) { if (other is Coin) { other.collect(); } super.onCollisionStart(intersectionPoints, other); }


La raison pour laquelle nous utilisons onCollisionStart au lieu de onCollision est que nous voulons que le rappel de collision ne se déclenche qu'une seule fois.


Les pièces disparaissent maintenant lors d'une collision avec The Boy. Ajoutons l'interface utilisateur pour suivre le nombre de pièces collectées.


Ajout du HUD

Le HUD, ou affichage tête haute, est simplement une barre d'état qui affiche toutes les informations sur le jeu : points de vie, munitions, etc. Nous allons afficher une icône de pièce pour chaque pièce collectée.


Par souci de simplicité, je vais stocker le nombre de pièces dans une variable, mais pour des interfaces plus complexes, envisagez d'utiliser un package flame_bloc , qui vous permet de mettre à jour et d'observer l'état du jeu de manière pratique.


Ajoutez une nouvelle classe qui contiendra la logique HUD : lib/hud.dart

 class Hud extends PositionComponent with HasGameRef<PlatformerGame> { Hud() { positionType = PositionType.viewport; } void onCoinsNumberUpdated(int total) { final coin = SpriteComponent.fromImage( game.images.fromCache(Assets.HUD), position: Vector2((50 * total).toDouble(), 50), size: Vector2.all(48)); add(coin); } }


Deux choses intéressantes ici :


  1. Nous définissons positionType sur PositionType.viewport pour coller notre HUD au coin de l'écran. Si nous ne le faisons pas, en raison du mouvement de la caméra, le HUD se déplacera avec le niveau.
  2. La méthode onCoinsNumberUpdated sera appelée chaque fois que le joueur récupère la pièce. Il utilise le paramètre total pour calculer le décalage de l'icône de pièce suivante, puis ajoute un nouveau sprite de pièce à la position calculée.


Ensuite, revenez au fichier game.dart et ajoutez de nouvelles variables de classe :

 int _coins = 0; // Keeps track of collected coins late final Hud hud; // Reference to the HUD, to update it when the player collects a coin


Ajoutez ensuite le composant Hud au bas de la méthode onLoad :

 hud = Hud(); add(hud);


Et ajoutez une nouvelle méthode :

 void onCoinCollected() { _coins++; hud.onCoinsNumberUpdated(_coins); }


Enfin, appelez-le à partir de la méthode collect de Coin :

 void collect() { game.onCoinCollected(); animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; }

Génial, notre HUD affiche maintenant le nombre de pièces que nous avons collectées !


Le HUD dans le coin supérieur gauche affiche les pièces collectées


Écran de victoire

La dernière chose que je veux ajouter est l'écran Win, qui s'affichera une fois que le joueur aura récupéré toutes les pièces.


Ajoutez un nouveau const à la classe PlatformerGame :

 late int _totalCoins;


Et attribuez-lui le nombre de pièces que nous avons dans le niveau. Ajoutez cette ligne au bas de la méthode spawnObjects :

 _totalCoins = coins.objects.length;


Et ajoutez ceci au bas de la méthode onCoinCollected .


Notez que vous devrez peut-être ajouter import 'package:flutter/material.dart'; manuellement.

 if (_coins == _totalCoins) { final text = TextComponent( text: 'U WIN!', textRenderer: TextPaint( style: TextStyle( fontSize: 200, fontWeight: FontWeight.bold, color: Colors.white, ), ), anchor: Anchor.center, position: camera.viewport.effectiveSize / 2, )..positionType = PositionType.viewport; add(text); Future.delayed(Duration(milliseconds: 200), () => { pauseEngine() }); }


Ici, je vérifie si le compteur de pièces est égal au nombre de pièces dans le niveau, et s'il ajoute une étiquette Win en haut de l'écran de jeu. Ensuite, mettez le jeu en pause pour arrêter le mouvement du joueur. J'ai également ajouté un délai de 200 ms, pour que l'étiquette soit rendue avant la pause.


Ça devrait ressembler à ça:

L'écran Win s'affiche lors de la collecte de la dernière pièce


Et c'est le jeu ! Bien sûr, cela ne ressemble pas à un jeu fini maintenant, mais avec tout ce que j'ai expliqué, il devrait être assez facile d'ajouter plus de niveaux, d'ennemis ou d'autres objets de collection.


Résumé

Cette partie conclut la série.


Le moteur Flame a beaucoup plus à offrir que je n'ai pas couvert, y compris le moteur physique Forge2D, les particules, les effets, les menus du jeu, l'audio, etc. Mais après avoir terminé cette série, j'ai compris à quoi ressemble le moteur et je comprendre comment créer des jeux plus complexes.


Flame est un outil puissant mais facile à utiliser et à apprendre. Il est modulaire, ce qui permet d'apporter d'autres choses sympas comme Box2D et il est activement maintenu. Mais l'un des plus grands avantages de Flame est qu'il est construit sur Flutter, ce qui signifie qu'il fournit un support multiplateforme avec un peu de travail supplémentaire. Cependant, être une extension Flutter signifie que tous les problèmes de Flutter persistent également dans Flame. Par exemple, le bogue d'anticrénelage de Flutter est ouvert depuis plusieurs années sans résolution, et vous le remarquerez peut-être également dans le jeu que nous avons créé. Mais dans l'ensemble, c'est un excellent outil pour créer des jeux qui valent la peine d'être essayés.


Autres histoires de la série :

Le code complet de ce tutoriel, vous pouvez le trouver dans mon github

Ressources

À la fin de chaque partie, j'ajouterai une liste de créateurs géniaux et de ressources dont j'ai appris.