Un tutorial complet sobre la flama (o com fer jocs amb Flutter)

introducció

Hola a tothom! Sóc Luan i us dono la benvinguda a aquest primer tutorial complet de Flame.

Flame és un motor de jocs Flutter minimalista que proporciona uns quants mòduls per crear un joc basat en llenços.

En aquest tutorial, crearem un joc molt senzill on caiguin caixes i l'objectiu és destruir-les abans que arribin a la part inferior de la pantalla.

Així serà el joc

Podeu consultar el joc vosaltres mateixos per veure què fem, instal·lant aquest APK o instal·lant-lo a Play Store.

D’aquesta manera podem cobrir totes les funcionalitats que proporciona el framework i demostrar com fer les operacions més bàsiques: renderització, sprites, àudio, text, animació i molt més.

Per què hem escollit aquest joc? A més de ser molt senzill i complet per explorar el marc, és un bon factor que tinguem els recursos necessaris per fer-ho.

Per a aquest joc farem servir els recursos següents: un sprite de caixa, un sprite d’explosió (amb animació), un so d’explosió, un so de missatge (en cas que no es faci un toc), música de fons bon tipus de lletra per representar la partitura.

Trobar bons recursos disponibles comercialment a Internet és difícil de trobar, però hem trobat de tot (excepte el tipus de lletra) en aquest fantàstic lloc web. Són realment fantàstics, totalment recomanables.

Com que Sprite Sheets no és compatible actualment, tots els recursos primer s'han de convertir en formats adequats. També hem de convertir tot l’àudio a MP3 (també s’admeten OGG; WAV no). Després, podeu descarregar un paquet amb tots els recursos que necessiteu aquí.

També val la pena esmentar que tot el que es descriu aquí es fixa com una versió completa completament funcional a GitHub. Sempre podeu mirar si teniu dubtes. L'exemple es va crear seguint exactament aquests passos i utilitza confirmacions freqüents com a instantànies. Durant el tutorial, enllaçaré els compromisos específics que faran avançar el dipòsit al nivell en qüestió. D’aquesta manera podeu crear punts de control i cercar a través del codi antic per veure qualsevol cosa que no estigui clara.

Una última cosa; Podeu trobar la documentació completa de Flame aquí. Si teniu cap pregunta, suggeriment o error, no dubteu en posar-vos en contacte amb mi.

També haureu d’instal·lar flutter i dards. Si us convé, també podeu utilitzar IntelliJ IDEA. Aquest és un molt bon lloc per començar a escriure el vostre codi. Per instal·lar aquestes coses, podeu consultar diversos tutorials com aquest.

Conceptes bàsics

Per tant, per ara suposo que ho teniu tot a punt. Només cal executar-lo i obrir-lo.

El primer que heu de fer és afegir la dependència de la flama. Aneu al fitxer pubspec.yaml i assegureu-vos que les llistes de claus de dependència són brillants:

Aquí estem utilitzant la versió més recent, 0.5.0, però podeu triar-ne una de nova si està disponible.

Ara el fitxer main.dart ja conté moltes coses: un mètode "principal" que cal conservar; i una trucada posterior al mètode runApp. Aquest mètode utilitza widgets i altres components de Flutter que s’utilitzen per crear pantalles d’aplicacions. Com que estem fent un joc, dibuixarem tot sobre el llenç i no utilitzarem aquests components. Per tant, elimineu-ho tot.

El nostre mètode principal ara està buit i afegirem dues coses; Primer una configuració:

La importació flame.dart dóna accés a la classe de Flame estàtica que només conté algunes classes útils. Més endavant en farem servir més. Ara mateix estem trucant a dos mètodes en aquesta classe de Flame.

L'última ordre s'explica per si mateixa i desactiva part del registre del connector del reproductor d'àudio. En breu afegirem àudio al joc. Si això no funciona, haureu de comentar-ho per solucionar els errors. Però ho aconseguirem algun dia.

La primera línia és més complexa. Bàsicament, com que no fem servir el mètode runApp, falten algunes funcions importants de Flutter. Aquesta trucada per enableEvents us permet evitar el problema que cal per a qualsevol aplicació sense haver d’utilitzar ginys.

Finalment hem de començar el nostre joc. Per fer-ho, afegim una altra classe a la llista d’importació, la classe de jocs. Aquesta classe proporciona l'abstracció necessària per crear un joc: un bucle de joc. Cal subclassar-lo perquè hàgiu d’implementar la base de cada joc: un mètode d’actualització que s’anomena sempre que sigui convenient i que pren el temps transcorregut des de l’última actualització i un mètode de renderització que ha de saber com el que es dibuixa és l'estat actual del joc. El funcionament intern del bucle ha de ser resolt per la classe de joc (podeu fer una ullada, és clar, és molt senzill), i només cal que comenceu per trucar.

Punt de control: 599f809

Actualment, la representació no realitza cap acció. Per tant, quan l’inicieu hauria d’estar en execució, però obtindreu una pantalla negra. Dolç! Per tant, tenim una aplicació funcional sense widgets, etc., i un llenç en blanc per començar a dibuixar la nostra aplicació.

Representar formes

I com es dibuixa? Dibuixem un senzill rectangle per veure'l en acció. Afegiu el següent al vostre mètode de representació:

Com podeu veure, aquí estem definint un rectangle basat en les posicions de la pantalla. La imatge següent mostra com s’alineen les regles. Bàsicament l’origen es troba a la cantonada superior esquerra i l’eix puja cap a la dreta i cap avall.

Tingueu en compte també que la majoria dels mètodes de dibuix utilitzen Paint. Un color no és només un sol color, sinó que pot ser un degradat o alguna altra textura. Normalment, voleu un color sòlid o anar directament a un sprite. Per tant, acabem d’establir el color del color en una instància de Color.

Color significa un sol color ARGB. El creeu amb un enter que podeu escriure en un script hexadecimal per facilitar la lectura. Té el format A (alfa, transparència, normalment 0xFF) i, a continuació, dos dígits per a R, G i B en aquest ordre.

També hi ha una col·lecció de colors anomenats; no obstant això, es troba al paquet de materials. Només assegureu-vos d'importar només el mòdul Colors per no utilitzar accidentalment una altra cosa del paquet de material.

Així que, genial, ara tenim una plaça!

Punt de control: 4eff3bf

També sabem com funcionen els governants, però no sabem les dimensions de la pantalla. Com dibuixem alguna cosa als altres tres racons sense aquesta informació? No tinguis por, perquè Flame té un mètode per obtenir la mida real de la pantalla (això és perquè hi ha un problema documentat).

Bàsicament el mètode asíncron

Tingueu en compte que, com a JavaScript, la paraula clau await només es pot utilitzar en una funció asíncrona. Així que assegureu-vos de fer la vostra sincronització principal (el flutter no importa).

El següent punt de control obté les dimensions un cop al mètode principal i les emmagatzema a la nostra classe de joc, ja que les necessitarem repetidament.

Punt de control: a1f9df3

Render sprites

Finalment, podem dibuixar qualsevol forma que vulguem a la pantalla. Però volem sprites! El següent punt de control afegeix alguns dels recursos que volem utilitzar a la carpeta d’actius adequada:

Punt de control: 92ebfd9

I el següent fa una cosa crucial per no oblidar: afegir-ho tot al fitxer pubsepc.yaml. Durant la creació del vostre codi, Dart només agrupa els recursos que s’hi especifiquen.

Punt de control cf5975f

Per fi podem dibuixar el nostre sprite. La forma bàsica que ofereix Flame és proporcionar un mètode Flame.images.load ('ruta des de la carpeta d'imatges') que retorna una promesa de la imatge carregada que es pot dibuixar mitjançant el mètode canvas.drawImage. .

No obstant això, quan es dibuixa un quadre, això és molt més fàcil, ja que podem utilitzar la classe SpriteComponent així:

La classe abstracta Component és una interfície de dos mètodes: renderització i actualització, igual que el nostre joc. La idea és que el joc pugui estar format per components els mètodes de representació i actualització dels quals es coneixen dins dels mètodes del joc. SpriteComponent és una implementació que representa un sprite en funció del seu nom i mida (quadrat o rectangular), posició (x, y) i angle de rotació. La imatge es reduirà o ampliarà adequadament per ajustar-se a la mida desitjada.

En aquest cas, carreguem el fitxer "crate.png", que ha d'estar a la carpeta "assets / images", i té una classe de caixa que dibuixa marcs de 128 x 128 píxels i un angle de rotació de 0.

A continuació, afegim una propietat Crate al joc, la instanciem a la part superior de la pantalla, centrada horitzontalment i la representem al nostre bucle de joc:

Això farà que la nostra caixa! Brillant! El codi és bastant concís i fàcil de llegir també.

Punt de control 7603ca4

Actualitza l'estat al bucle del joc

La nostra caixa pràcticament es va aturar a l’aire. Ho volem moure! Cada caixa caurà a un ritme constant. Ho hem de fer amb el nostre mètode d'actualització; Només cal canviar la posició Y de cada caixa que tenim:

Aquest mètode pren el temps (en segons) que ha passat des de la darrera actualització. Normalment serà molt petit (de l’ordre de 10 ms). Per tant, la VELOCITAT és una constant en aquestes unitats. en el nostre cas SPEED = 100 píxels / segon.

Punt de control: 452dc40

Manipulació d’entrades

Hurra! Les caixes cauen i desapareixen, però no hi podeu interactuar. Afegim un mètode per destruir les caixes que toquem. Per a això utilitzarem un esdeveniment de finestra. El giny està disponible a tot el món en tots els projectes de Flutter i té algunes propietats útils. Registrarem un esdeveniment onPointerDataPacket per al mètode principal, és a dir, quan l'usuari toqui la pantalla:

Simplement extraiem la coordenada (x, y) del clic i la passem directament al nostre joc. D’aquesta manera, el joc pot gestionar el clic sense preocupar-se dels detalls dels esdeveniments.

A més, per fer les coses més interessants, hauríem de redissenyar la classe de joc per tenir una llista de caixes en lloc d’una sola. Al cap i a la fi, això és el que volem. Substituïm els mètodes de renderització i actualització per forEach sobre les caixes, i el nou mètode d’entrada és:

Punt de control: 364a6c2

Representar múltiples sprites

Hi ha un punt important a fer aquí, que està relacionat amb el mètode de renderització. Quan representem una caixa, l'estat del llenç es canvia i gira arbitràriament per permetre el dibuix. Com que volem dibuixar diverses caixes, hem de restablir el llenç entre les caixes individuals. Això es fa mitjançant els mètodes de desar, que desen l’estat actual, i restauren, que restauren l’estat desat anteriorment i l’eliminen.

Aquesta és una nota important, ja que és la causa de molts errors estranys. Potser hauríem de fer-ho automàticament cada vegada que representem? No sé què estàs pensant

Ara volem més caixes! Com funciona? Bé, el mètode d'actualització pot ser el nostre temporitzador. Per tant, volem que s’afegeixi un quadre nou a la llista cada segon. Per tant, vam crear una altra variable a la classe Game per acumular els temps delta (t) de cada trucada d'actualització. Si és superior a 1, es restablirà i crearà un quadre nou:

No oblideu conservar l’actualització anterior perquè les caixes no parin de caure. També estem canviant la velocitat a 250 píxels / segon per fer les coses una mica més interessants.

Punt de control: 3932372

Renderitza animacions

Hauria de ser un GIF, oi? Estem treballant en una configuració per obtenir millors captures de pantalla i GIF per a aquest tutorial.

Ara coneixem els conceptes bàsics sobre la manipulació i la representació de sprites. Passem al següent pas: explosions! Quin joc és bo sense ells? L’explosió és un altre tipus de bèstia perquè conté animació. Les animacions a Flame es creen simplement representant diferents elements quan es representen segons la marca de verificació actual. Igual que hem afegit un temporitzador fet a mà a les caixes de generació, afegirem una propietat LifeTime per a cada explosió. A més, Explosion no heretarà de SpriteComponent, ja que aquest últim només pot tenir un Sprite. Ampliarem la Superclass PositionComponent i implementarem la representació amb Flame.image.load.

Com que cada explosió conté molts fotogrames i aquests s'han de dibuixar ràpidament, precarregem cada fotograma una vegada i els guardem en una variable estàtica de la classe d'explosió. Com és això:

Fixeu-vos que carreguem cadascun dels nostres 7 fotogrames d’animació al seu torn. A continuació, creem una lògica senzilla al mètode de representació per decidir quin marc dibuixar:

Tingueu en compte que dibuixem "a mà" amb drawImageRect, com ja s'ha explicat. Aquest codi és similar al que SpriteComponent fa sota el capó. Tingueu en compte també que si la imatge no es troba a la matriu, no es dibuixarà res. Després de TIME segons (l'establim a 0,75 o 750 ms) no es renderitza res.

Tot està bé, però no volem continuar contaminant la nostra matriu d'explosions amb explosions explotades. Per això, també afegim un mètode destroy () que, basat en el mètode lifeTime, retorna si hem de destruir l'objecte explosiu.

Finalment, actualitzarem el nostre joc, afegirem una llista d’explosió, el representarem amb el mètode de renderització i, a continuació, actualitzarem amb el mètode d’actualització. S’han d’actualitzar per augmentar la seva vida útil. També ens dediquem el temps a refactoritzar el que anteriorment s’incloïa al mètode Game.update, és a dir, H. Les caixes cauen en el mètode Crate.update, ja que és responsabilitat de la caixa. Ara, l’actualització del joc només es delega a altres persones. Finalment, a l’actualització, hem d’eliminar de la llista el que s’ha destruït. La llista ofereix un mètode molt útil per a això, removeWhere:

Ja ho hem utilitzat per al mètode d'entrada per eliminar els camps que ha tocat la matriu. Allà també causarem una explosió.

Mireu el punt de control per obtenir més informació.

Punt de control: d8c30ad

Reprodueix àudio

Per fi reproduirem àudio a la propera publicació. Per fer-ho, heu d'afegir el fitxer a la carpeta Actius a assets / audio /. Ha de ser un fitxer MP3 o OGG. Després, en qualsevol lloc del vostre codi, feu el següent:

On filename.mp3 és el nom del fitxer que conté. En el nostre cas, reproduïm el so explosion.mp3 quan fem clic en un quadre.

Comencem per la puntuació. Afegim una variable de punts per emmagatzemar el nombre actual de punts. Comença amb zero; Tenim 10 punts per caixa feta clic i en perdem 20 si la caixa toca terra.

Ara tenim l’obligació de fer front a les caixes fugitives. Basant-nos en el que vam fer amb la classe Explosion, afegim un mètode de destrucció per a la caixa que tornarà si no es mostra a la pantalla. Això comença a convertir-se en un patró. Si es destrueix, l’eliminem de la matriu i ajustem els punts.

La puntuació funciona actualment, però no es mostra enlloc. que arribarà aviat.

L’àudio no funcionarà en aquest proper punt de control, ja que vaig oblidar afegir els fitxers i posar-los al fitxer pubspec.yaml. Això passa en el següent commit.

Punt de control: 43a7570

Ara volem més tons! Els reproductors d'àudio (tingueu en compte les llibreries) que utilitza Flame us permeten reproduir diversos sons alhora, com és possible que hagueu notat si feu clic a boig, però ara aprofitem-ho afegint fem un miss quan la caixa toca el terra (mètode de destrucció de la caixa) i la música de fons.

Per reproduir la música de fons en bucle, utilitzeu el mètode de bucle, que funciona de la següent manera:

En aquesta confirmació també estem corregint la condició de destrucció de les caixes que ens vam perdre a la confirmació anterior (ja que no hi havia manera de saber-ho, ara hi ha so).

Punt de control: f575150

Renderitza el text

Ara que tenim totes les dades d’àudio que volem (fons, música, efectes de so, MP3, OGG, bucle sense fi al mateix temps), podem començar a reproduir el text. Hem de veure aquesta puntuació. Això es fa "a mà" mitjançant la creació d'un objecte de paràgraf i l'ús de drawParagraph de l'àrea de dibuix. Requereix molta configuració i és una API bastant confusa, però es pot fer així:

Punt de control: e09221e

Es dibuixa amb el tipus de lletra per defecte i podeu utilitzar la propietat fontFamily per especificar un tipus de lletra general del sistema diferent. Tot i això, és probable que vulgueu afegir-ne un de personalitzat al vostre joc.

Així que vaig anar a 1001fonts.com i vaig obtenir aquest tipus de lletra Halo comercial i gratuït com a TTF. També aquí el fitxer només s’ha d’emmagatzemar a Actius / Tipus de lletra. Ara s’ha d’importar de manera diferent al fitxer pubspec.yaml. En lloc d’afegir un altre recurs, hi ha una etiqueta de tipus de lletra dedicada que s’anota per defecte amb instruccions completes sobre com afegir els tipus de lletra. Per tant, doneu-li un nom i feu alguna cosa així:

Aquesta capa addicional d'abstracció prové del mateix Flutter i us permet afegir diversos fitxers al mateix tipus de lletra (per definir mides en negreta, més grans, etc.). Ara, tornant al nostre paràgraf, anem a afegir la propietat fontFamily: 'Halo' al constructor TextStyle.

Corre i veuràs la bonica font Halo.

Punt de control: 3155bda

Aquest mètode descrit us proporciona més control si, per exemple, voleu tenir diversos estils al mateix paràgraf. Tanmateix, si, com en aquest cas, voleu un paràgraf senzill amb estil, podeu utilitzar l’ajudant Flame.util.text per crear-lo:

Aquesta línia única substitueix les 4 anteriors i mostra les funcions més importants. El text (primer argument) és obligatori i tots els altres són opcionals, amb valors predeterminats raonables.

Per al color, tornem a utilitzar l’ajudant Colors.white, però també podem utilitzar un color nou (0xFFFFFFFF) si voleu un color específic.

Punt de control: 952a9df

I aquí ho teniu! Un joc complet amb representació de sprite, renderització de text, àudio, bucle de joc, gestió d'esdeveniments i estat.

publicació

El vostre joc està llest per al llançament?

Simplement seguiu aquests senzills passos del tutorial de Flutter.

Tots són bastant senzills com es pot veure en aquest punt de control final, excepte la part d’icones que pot ser una mica de mal de cap. Us recomano fer una versió gran de la vostra icona (512 o 1024 píxels) i fer servir el lloc web Make App Icon per crear un fitxer zip amb tot el que necessiteu (iOS i Android).

Punt de control: 2974f29

Què més?

T'ha agradat Flame? Si teniu suggeriments, errors, preguntes, sol·licituds de funcions o similars, no dubteu a posar-vos en contacte amb mi.

Voleu millorar el vostre joc i obtenir més informació? Què tal un servidor amb Firebase i Google Inicia la sessió? Què tal si col·loqueu un anunci? Què tal si configureu un menú principal i diverses pantalles?

Per descomptat, hi ha moltes coses que es poden millorar: aquest és només un exemple de joc. Tanmateix, hi hauria d’haver una idea bàsica dels conceptes bàsics del desenvolupament del joc Flutter (amb o sense flama).

Espero que a tothom els hagi agradat!

Publicat originalment a GitHub.