Un didacticiel complet sur la flamme (ou comment créer des jeux avec flutter)

introduction

Bonjour à tous! Je suis Luan et bienvenue dans ce premier tutoriel complet sur Flame.

Flame est un moteur de jeu minimaliste Flutter qui fournit quelques modules pour créer un jeu basé sur Canvas.

Dans ce tutoriel, nous allons créer un jeu très simple, dans lequel les cases tomberont et le but est de les détruire avant qu'elles ne touchent le bas de l'écran.

Voici à quoi ressemblera le jeu

Vous pouvez vérifier le jeu vous-même pour voir ce que nous faisons pour installer ce fichier APK ou depuis le Play Store.

Cela nous permettra de couvrir toutes les fonctionnalités fournies par le framework et de montrer comment effectuer les opérations les plus élémentaires: rendu, images-objets, audio, texte, animations, etc.

Pourquoi avons-nous choisi ce jeu? En plus d'être très simple mais complet pour l'exploration du cadre, un bon facteur est que nous avons les ressources pour le faire!

Pour ce jeu, nous allons utiliser les ressources suivantes: un sprite de caisse, un sprite d’explosion (avec animation), un son explosif, un son 'miss' (pour quand la case n’est pas touchée), une musique de fond et jolie police pour le rendu de la partition.

Il est difficile de trouver de bonnes ressources disponibles dans le commerce sur Internet, mais nous avons tout trouvé (sauf la police de caractères) dans ce site génial. Ils sont vraiment géniaux, absolument recommandés.

Pour l'instant, les feuilles de sprite ne sont pas prises en charge. Nous devons donc tout d'abord convertir toutes les ressources en formats appropriés. En outre, nous devons convertir tout l’audio au format MP3 (OGG est également pris en charge; WAV ne l’est pas). Cela étant fait, vous pouvez télécharger un ensemble contenant tout ce dont vous aurez besoin en termes de ressources.

Il convient également de mentionner que tout ce qui est décrit ici est une version complète et pleinement fonctionnelle de GitHub. Vous pouvez toujours jeter un coup d'oeil en cas de doute. En outre, l'exemple a été créé en suivant ces étapes et en prenant des commits fréquents sous forme d'instantanés. Tout au long du didacticiel, je lierai les commits spécifiques qui font avancer le référentiel vers l’étape en question. Cela vous permettra de faire des points de contrôle et de parcourir l’ancien code pour voir tout ce qui n’était pas clair.

Une dernière chose; vous pouvez consulter la documentation complète de Flame ici. Si vous avez des questions, des suggestions, des bugs, n'hésitez pas à ouvrir un problème ou à me contacter.

En dehors de cela, vous devrez également installer Flutter et Dart. Si cela vous convient, vous pouvez également utiliser IntelliJ IDEA, qui est un très bon endroit pour écrire votre code. Pour installer ce matériel, vous pouvez consulter une multitude de didacticiels, comme celui-ci.

Les bases

Donc, pour le moment, je suppose que tout est prêt. Alors, lancez ce qui suit et ouvrez-le!

La première chose à faire est d’ajouter la dépendance à la flamme. Accédez à votre fichier pubspec.yaml et assurez-vous que les listes de clés de dépendances flammes:

Ici, nous utilisons la dernière version, 0.5.0, mais vous pouvez aussi en choisir une nouvelle si elle est disponible.

Votre fichier main.dart contient déjà beaucoup de choses: une méthode ‘main’ qui doit être conservée; et un appel ultérieur à la méthode runApp. Cette méthode utilise les widgets et autres composants Flutter utilisés pour créer des écrans d'application. Puisque nous créons un jeu, nous allons tout dessiner sur le canevas et nous n’utiliserons pas ces composants; alors dépouillez tout ça.

Notre méthode principale est maintenant vide et nous allons ajouter deux choses. d'abord, une configuration:

L'importation flame.dart donne accès à la classe statique Flame, qui est simplement le détenteur de plusieurs autres classes utiles. Nous en utiliserons plus tard. Pour le moment, nous appelons deux méthodes dans cette classe Flame.

Le dernier est explicite, il désactive une partie de la journalisation depuis le plugin audioplayers. Nous allons bientôt ajouter de l’audio au jeu. Si cela ne fonctionne pas, c’est là que vous devez commenter pour pouvoir résoudre ce problème. Mais nous y arriverons éventuellement.

La première ligne est plus complexe. En gros, certaines fonctionnalités cruciales de Flutter sont absentes car nous n’utilisons pas la méthode runApp. Cet appel à enableEvents fait un peu une solution de contournement pour obtenir l'essentiel pour chaque application, sans avoir besoin d'utiliser des widgets.

Enfin, nous devons commencer notre jeu. Pour ce faire, nous allons ajouter une classe supplémentaire à la liste d'importation, la classe Game. Cette classe fournit l'abstraction nécessaire pour créer n'importe quel jeu: une boucle de jeu. Il doit être sous-classé pour que vous soyez obligé d'implémenter la base de tout jeu: une méthode de mise à jour, appelée à tout moment et prenant le temps écoulé depuis la dernière mise à jour, et une méthode de rendu, qui doit savoir état actuel du jeu. La classe Game doit résoudre les rouages ​​de la boucle (vous pouvez y jeter un coup d’œil, bien sûr, c’est très simple), et il vous suffit d’appeler pour commencer, pour commencer.

Point de contrôle: 599f809

Pour le moment, le rendu ne fait rien, donc, si vous le démarrez, il devrait s’exécuter, mais vous donner un écran noir. Sucré! Nous avons donc obtenu une application fonctionnelle sans aucun gadget, etc., et une toile vierge pour commencer à dessiner notre application.

Rendu des formes

Et comment dessine-t-on? Dessine un simple rectangle pour le voir en action. Ajoutez les éléments suivants à votre méthode de rendu:

Comme vous pouvez le constater, nous définissons ici un rectangle basé sur les positions de l’écran. L'image suivante montre comment les règles sont orientées. Fondamentalement, l'origine est dans le coin supérieur gauche et l'axe augmente vers la droite et vers le bas.

Notez également que la plupart des méthodes de dessin utilisent une peinture. Une peinture n'est pas simplement une couleur, mais peut être une dégradé ou d'autres textures. Normalement, vous voulez soit une couleur unie ou aller directement à un Sprite. Nous avons donc simplement défini la couleur dans la peinture sur une occurrence de Color.

La couleur représente une seule couleur ARGB; vous le créez avec un entier que vous pouvez écrire au format hexadécimal pour en faciliter la lecture; c’est au format A (alpha, transparence, normalement 0xFF), puis deux chiffres pour R, V et B dans cet ordre.

Il existe également une collection de couleurs nommées. c’est cependant dans l’emballage matériel. Méfiez-vous simplement d'importer le module Couleurs, afin de ne pas utiliser accidentellement quelque chose d'autre dans l'emballage du matériel.

Donc, super, maintenant nous avons un carré!

Point de contrôle: 4eff3bf

Nous savons également comment les règles fonctionnent, mais nous ne connaissons pas les dimensions de l’écran! Comment allons-nous dessiner quelque chose dans les trois autres coins sans cette information? Ne craignez pas, car Flame dispose d’une méthode pour extraire la dimension réelle de l’écran (c’est parce qu’il existe un problème documenté à ce sujet.

Fondamentalement, la méthode async

Notez que le mot-clé wait, tout comme en JavaScript, ne peut être utilisé que dans une fonction asynchrone. Assurez-vous donc de définir votre async principal (Flutter ne s’inquiète pas).

Le prochain point de contrôle extraira les dimensions une fois dans la méthode principale et les stockera dans notre classe Game, car nous en aurons besoin de manière répétée.

Point de contrôle: a1f9df3

Rendu des sprites

Enfin, nous savons dessiner n’importe quelle forme, n’importe où sur l’écran. Mais nous voulons des sprites! Le prochain point de contrôle ajoute certains des actifs que nous allons utiliser dans le dossier d’actifs approprié:

Point de contrôle: 92ebfd9

Et la suivante fait une chose cruciale que vous ne pouvez pas oublier: tout ajouter à votre fichier pubsepc.yaml. Lors de la création de votre code, Dart ne regroupera que les ressources que vous spécifiez ici.

Point de contrôle cf5975f

Enfin, nous sommes prêts à dessiner notre sprite. Pour ce faire, Flame vous permet d’exposer une méthode Flame.images.load ("chemin depuis le dossier des images") qui renvoie une promesse pour l’image chargée, qui peut ensuite être dessinée avec la méthode canvas.drawImage.

Cependant, dans le cas du dessin d’une caisse, c’est très simple, car on peut utiliser la classe SpriteComponent, comme ceci:

La classe de composants abstraite est une interface avec deux méthodes, rendu et mise à jour, tout comme notre jeu. L’idée est que le jeu puisse être composé de composants dont les méthodes de rendu et de mise à jour sont appelées dans les méthodes du jeu. SpriteComponent est une implémentation qui rend un sprite, étant donné son nom et sa taille (carré ou rectangulaire), la position (x, y) et l'angle de rotation. Il sera approprié de réduire ou d'agrandir l'image pour l'adapter à la taille souhaitée.

Dans ce cas, nous chargeons le fichier ‘crate.png’, qui doit figurer dans le dossier assets / images, et une classe Crate qui dessine des blocs de 128x128 pixels, avec un angle de rotation de 0.

Nous ajoutons ensuite une propriété Crate au jeu, l'instancions en haut de l'écran, centrée horizontalement, et la restituons dans notre boucle de jeu:

Cela rendra notre caisse! Impressionnant! Le code est assez succinct et facile à lire.

Point de contrôle 7603ca4

Mise à jour de l'état dans la boucle de jeu

Notre caisse est presque arrêtée dans les airs. Nous voulons le déplacer! Chaque caisse va tomber à une vitesse constante, vers le bas. Nous devons le faire dans notre méthode de mise à jour; changez simplement la position Y de la caisse unique que nous avons:

Cette méthode prend le temps (en secondes) de la dernière mise à jour. Normalement, cela sera très petit (ordre de 10 ms). Donc, la vitesse est une constante dans ces unités; dans notre cas, SPEED = 100 pixels / seconde.

Point de contrôle: 452dc40

Traitement des entrées

Hourra! Les caisses tombent et disparaissent, mais vous ne pouvez pas interagir avec elles. Ajoutons une méthode pour détruire les caisses que nous touchons. Pour cela, nous allons utiliser un événement window. L'objet window est disponible dans tous les projets Flutter globalement, et il possède quelques propriétés utiles. Nous allons enregistrer sur la méthode principale un événement onPointerDataPacket, c'est-à-dire lorsque l'utilisateur tapera sur l'écran:

Nous extrayons simplement la coordonnée (x, y) du clic et le passons directement à notre jeu; De cette façon, le jeu peut gérer le clic sans se soucier des détails des événements.

Pour rendre les choses plus intéressantes, imaginons également que la classe Game ait une liste de caisses au lieu d’une seule. Après tout, c’est ce que nous voulons. Nous remplaçons les méthodes de rendu et de mise à jour par un forEach sur les caisses et la nouvelle méthode d'entrée devient:

Point de contrôle: 364a6c2

Rendu de plusieurs sprites

Il est un point crucial à mentionner ici, et concerne la méthode de rendu. Lorsque nous rendons une caisse, l'état du canevas est traduit et pivoté de façon arbitraire afin de permettre le dessin. Puisque nous allons dessiner plusieurs caisses, nous devons réinitialiser le canevas entre chaque tirage. Cela est fait avec les méthodes save, qui sauvegardent l'état actuel, et restaure, qui restaure l'état précédemment sauvegardé, en le supprimant.

C'est une remarque importante, car c'est la source de nombreux bugs étranges. Peut-être devrions-nous le faire automatiquement dans chaque rendu? Je ne sais pas, vous en pensez quoi?

Maintenant, nous voulons plus de caisses! Comment faire ça? Eh bien, la méthode de mise à jour peut être notre minuterie. Nous voulons donc qu’une nouvelle caisse soit ajoutée à la liste (générée) toutes les secondes. Nous avons donc créé une autre variable dans la classe Game pour accumuler le delta times (t) de chaque appel de mise à jour. Quand il dépasse 1, il est réinitialisé et une nouvelle caisse apparaît:

N'oubliez pas de conserver la mise à jour précédente pour que les caisses n'arrêtent pas de tomber. De plus, nous modifions la vitesse à 250 pixels / seconde, pour rendre les choses un peu plus intéressantes.

Point de contrôle: 3932372

Rendu d'animations

Cela devrait être un GIF, non? Nous travaillons sur une configuration pour de meilleures captures d'écran et GIF pour ce tutoriel!

Nous connaissons maintenant les bases de la gestion et du rendu des sprites. Passons à l’étape suivante: les explosions! Quel jeu est bon sans ‘em? L'explosion est une bête d'un genre différent, car elle comporte une animation. Les animations dans Flame se font simplement en rendant différentes choses en fonction du tick actuel. De la même manière que nous avons ajouté une minuterie faite à la main aux boîtes de réapparition, nous allons ajouter une propriété lifeTime pour chaque explosion. De plus, Explosion n’héritera pas de SpriteComponent, car ce dernier ne peut avoir qu’un seul Sprite. Nous allons étendre la superclasse, PositionComponent, et implémenter le rendu avec Flame.image.load.

Comme chaque explosion comporte de nombreux cadres et qu’ils doivent être dessinés en conséquence, nous allons pré-charger chaque cadre une fois et enregistrer dans une variable statique de la classe Explosion; ainsi:

Notez que nous chargeons chacune de nos 7 images d'animation, dans l'ordre. Ensuite, dans la méthode de rendu, nous faisons une logique simple pour décider quel cadre dessiner:

Notez que nous dessinons "à la main", en utilisant drawImageRect, comme expliqué précédemment. Ce code est similaire à ce que SpriteComponent fait sous le capot. Notez également que, si l'image ne figure pas dans le tableau, rien n'est dessiné. Ainsi, après TIME secondes (nous la fixons à 0,75 ou 750 ms), rien n'est rendu.

C’est bien, mais nous ne souhaitons pas polluer notre réseau d’explosions par des explosions explosées, nous avons donc ajouté une méthode destroy () qui renvoie, en fonction de la durée de vie, s’il faut détruire l’objet explosif.

Enfin, nous mettons à jour notre jeu en ajoutant une liste d’explosion, en les rendant sur la méthode de rendu et en les mettant à jour ensuite. Ils doivent être mis à jour pour augmenter leur durée de vie. Nous prenons également le temps de reformuler ce qui était auparavant dans la méthode Game.update, c’est-à-dire, fait tomber les boîtes, pour qu’il soit dans la méthode Crate.update, car c’est la responsabilité de Crate. Maintenant, la mise à jour du jeu ne concerne que les délégués. Enfin, dans la mise à jour, nous devons supprimer de la liste ce qui a été détruit. Pour cela, List fournit une méthode très utile, removeWhere:

Nous avons déjà utilisé cela sur la méthode de saisie pour supprimer les cases touchées du tableau. Il y a aussi où nous allons créer une explosion.

Jetez un coup d'œil au point de contrôle pour plus de détails.

Point de contrôle: d8c30ad

Lecture audio

Lors du prochain commit, nous allons enfin jouer de l'audio! Pour ce faire, vous devez ajouter le fichier au dossier des ressources, dans actifs / audio /. Il doit s'agir d'un fichier MP3 ou OGG. Ensuite, n'importe où dans votre code, lancez:

Où filename.mp3 est le nom du fichier à l'intérieur. Dans notre cas, nous allons jouer le son explosion.mp3 lorsque nous cliquons dans une boîte.

De plus, commençons à accorder de la ponctuation. Nous ajoutons une variable de points pour contenir le nombre actuel de points. Cela commence par zéro; nous obtenons 10 points par case sur laquelle nous avons cliqué et en perdons 20 lorsque la case a touché le sol.

Nous avons maintenant l'obligation de traiter les boîtes emballées. Sur la base de ce que nous avons fait à la classe Explosion, nous ajoutons une méthode de destruction pour la caisse, qui va retourner si elles sont hors de l'écran. Cela commence à devenir un modèle! Si détruit, nous supprimons du tableau et ajustons les points.

Pour l'instant, la notation fonctionne, mais elle n'est affichée nulle part. cela viendra bientôt.

L'audio ne fonctionnera pas dans ce prochain point de contrôle car j'ai oublié d'ajouter les fichiers et de les mettre dans le fichier pubspec.yaml; c'est fait dans le commit suivant.

Point de contrôle: 43a7570

Maintenant nous voulons plus de sons! Les audioplayers (gardez à l’esprit les solutions que la Flame utilise) vous permettent de jouer plusieurs sons à la fois, comme vous l’auriez peut-être déjà remarqué si vous avez frénétiquement cliqué, mais nous allons maintenant utiliser cela à notre avantage, en jouant un son raté, lorsque la frappe le sol (méthode de destruction de la caisse), et une musique de fond.

Afin de jouer la musique de fond sur une boucle, utilisez la méthode loop, qui fonctionne exactement comme avant:

Dans ce commit, nous corrigeons également la condition de destruction des caisses, que nous avions manquée dans le commit précédent (car il n'y avait aucun moyen de savoir, il y a maintenant du son).

Point de contrôle: f575150

Rendre le texte

Maintenant que nous avons tout l’audio que nous voulons (arrière-plan, musique, effets sonores, MP3, OGG, boucle, simultanément), passons au rendu du texte. Nous avons besoin de voir ce score, après tout. Pour ce faire, vous devez créer un objet de paragraphe et utiliser le drawParagraph du canevas. Cela nécessite beaucoup de configuration et c’est une API assez déroutante, mais elle peut être réalisée comme suit:

Point de contrôle: e09221e

Ceci est dessiné dans la police par défaut et vous pouvez utiliser la propriété fontFamily pour spécifier une police système commune différente. cependant, dans votre jeu, vous voudrez probablement en ajouter un personnalisé.

Je me suis donc dirigé vers 1001fonts.com et j'ai obtenu cette jolie police Halo sans publicité en tant que TTF. Encore une fois, il suffit de déposer le fichier dans assets / fonts, mais maintenant, il doit être importé différemment dans le fichier pubspec.yaml. Au lieu d’ajouter un actif supplémentaire, il existe une balise de police dédiée, commentée par défaut avec des instructions complètes sur la procédure d’ajout des polices. Alors, donnez-lui un nom et faites quelque chose comme:

Cette couche d’abstraction supplémentaire provient de Flutter elle-même et vous permet d’ajouter plusieurs fichiers à la même police (pour définir des caractères gras, des tailles plus grandes, etc.). Maintenant, revenons à notre paragraphe, nous venons d'ajouter la propriété fontFamily: 'Halo' au constructeur TextStyle.

Courez et vous verrez la jolie police Halo!

Point de contrôle: 3155bda

Cette méthode décrite vous donnera plus de contrôle si vous voulez plusieurs styles dans le même paragraphe, par exemple. Mais si vous voulez, comme dans ce cas, un paragraphe simple et stylé, utilisez simplement l’assistant Flame.util.text pour le créer:

Cette ligne unique remplace les 4 précédentes et expose les fonctionnalités les plus importantes. Le texte (premier argument) est requis et tout le reste est optionnel, avec des valeurs par défaut raisonnables.

Pour la couleur, nous utilisons à nouveau l’assistant Colors.white, mais nous pouvons également utiliser la nouvelle couleur (0xFFFFFFFF) si vous souhaitez une couleur spécifique.

Point de contrôle: 952a9df

Et voila! Un jeu complet avec rendu de sprite, rendu de texte, audio, boucle de jeu, gestion des événements et des états.

Libération

Votre jeu est-il prêt à sortir?

Suivez simplement ces quelques étapes simples du didacticiel Flutter.

Ils sont tous très simples, comme vous pouvez le constater dans ce dernier point de contrôle, à l’exception de la partie Icons, qui pourrait causer un peu de maux de tête. Ma recommandation est de créer une grosse version (512 ou 1024 px) de votre icône et d'utiliser le site Web Make App Icon pour générer un zip avec tout ce dont vous avez besoin (iOS et Android).

Point de contrôle: 2974f29

Quoi d'autre?

Avez-vous apprécié Flame? Si vous avez des suggestions, des bugs, des questions, des demandes de fonctionnalités ou autre chose, n'hésitez pas à me contacter!

Voulez-vous améliorer votre jeu et en apprendre davantage? Pourquoi ne pas ajouter un serveur avec Firebase et Google Sign In? Que diriez-vous de mettre des annonces? Pourquoi ne pas configurer un menu principal et plusieurs écrans?

Bien sûr, il y a beaucoup à améliorer - ceci n'est qu'un exemple de jeu. Mais cela aurait dû donner une idée de base des concepts de base du développement de jeux avec Flutter (avec ou sans Flame).

J'espère que tout le monde a apprécié!

Publié à l'origine sur GitHub.