15. diel - Hra tetris v MonoGame: Skóre
V minulom dieli seriálu MonoGame tutoriálov, MonoGame: Skrolující text (autori hry) , sme si vytvorili posúvajúce text s autormi. Dnes hru obohatíme o tabuľku skóre, ktorej sa, ako máme sľúbené, internetová. Tak smelo do toho.
Komponenta skóre tabuľky
Tabuľka skóre bude komponent. A ako mnohí tušia, tak do zložky
Komponenty/
vytvoríme triedu s názvom
KomponentaSkoreTabulka
a podědíme ju z
DrawableGameComponent
. Ďalej pridáme privátne premenné pre
Hra
a Texture2D
s názvom pozadi
. V
LoadContent()
textúru načítame. Doteraz všetko známe:
public class KomponentaSkoreTabulka : DrawableGameComponent { private Hra hra; private Texture2D pozadi; public KomponentaSkoreTabulka(Hra hra) : base(hra) { this.hra = hra; } protected override void LoadContent() { pozadi = hra.Content.Load<Texture2D>(@"Sprity\pozadi_menu"); base.LoadContent(); } }
Zmyslom tejto komponenty bude ukladanie a zobrazovanie dosiahnutých
výsledkov v našej hre. Po skončení hry kolízií novovytvorenej kocky sa hra
prepne na obrazovku skóre, kde zobrazí uložené výsledky a vyzve aktuálneho
hráča na uloženie výsledku pod prezývku. K tomu budeme potrebovať ešte
pár premenných: reťazec pre prezývku a kolekciu uložených výsledkov
(rekordov). K tomu máme triedu Hrac
. A pretože výsledkov môže
byť na viac stránok, tak si budeme ešte držať aktuálne stánku. Tieto
premenné do triedy KomponentaSkoreTabulka
pridáme:
private int strana; private List<Hrac> hraci; private string prezdivka;
Skóre klient
Výsledky budeme ukladať na server a následne sťahovať do súboru. Preto
vytvoríme špeciálnu triedu SkoreKlient
, ktorá bude
zabezpečovať ukladanie a načítanie dát skóre tabuľky. V komponente budeme
len volať jej metódy, menovite Nacti()
a Uloz()
,
prípadne si tu získame text chybových hlášok. Túto triedu si zatiaľ len
pripravme, doprogramujeme ju nabudúce:
public class SkoreKlient { public string ChybaNacitani => $"Skóre se nepodařilo načíst.\n Soubor '{soubor}' pravděpodobně neexistuje"; public string ChybaUkladani => $"Skóre se nepodařilo uložit.\n Vyskytla se chyba při ukládání souboru."; private readonly string soubor = "skore.xml"; public void Uloz(Hrac hrac) { } public List<Hrac> Nacti() { } }
Rovno sme si pripravili aj názov súboru, do ktorého nabudúce skóre z internetu stiahneme.
Vrátime sa späť do triedy KomponentaSkoreTabulka
a inštanciu
SkoreKlient
pridáme medzi jej privátne atribúty:
private SkoreKlient klient;
A v neposlednom rade nezabudneme, všetko čo sme doteraz deklarovali, inicializovať:
public override void Initialize() { hraci = new List<Hrac>(); strana = 0; prezdivka = ""; klient = new SkoreKlient(); base.Initialize(); }
Stav skóre tabuľky
Odchytávanie chýb pri načítaní alebo ukladaní skóre celkom ošemetná
záležitosť zvlášť potom v aplikáciách využívajúce update-render
slučku. My to vyriešime fikan pomocou enum
stavov tabuľky a
konštrukcie switch
.
Najprv si zadefinujeme enum eStav
a vytvorené enum
použijeme ako typ pre premenou stav
:
private enum eStav { ChybaNacteni, ChybaUlozeni, Zapis, Vypis, Otazka, } private eStav stav;
Skóre tabuľka môže teraz buď ukazovať chybu načítanie, ukazovať
chybu uloženie, zapisovať nový rekord alebo vypisovať tabuľku rekordov. A
čo hodnota Otazka
? Tú použijeme v prípade, že sa aplikácia
používateľa pýta, či si želá uložiť svoje dosiahnuté skóre.
Uloženie a načítanie skóre tabuľky
Stav budú meniť najmä metódy Uloz()
a Nacti()
.
Využijeme v nich try
- catch
blok pri volaní metód
klienta, ktorého sme si pripravili vyššie. Stav zmeníme ak všetko prejde
alebo aj keď bola vyhodená výnimka.
public void Uloz() { try { klient.Uloz(hra.hrac); Nacti(); stav = eStav.Vypis; } catch { stav = eStav.ChybaUlozeni; } } public void Nacti() { stav = eStav.Vypis; try { klient.Stahni(); hraci = klient.Nacti(); } catch { stav = eStav.ChybaNacteni; } }
Draw ()
Teraz ešte napísať metódy Update()
a Draw()
.
Začneme metódou Draw()
:
public override void Draw(GameTime gameTime) { hra.spriteBatch.Begin(); hra.spriteBatch.Draw(pozadi, new Vector2(0, 0), Color.White); // výběr vykreslování dle aktuálního stavu switch (stav) { // vykreslení dotazu na nahrání skóre case eStav.Otazka: hra.spriteBatch.TextSeStinem(hra.fontCourierNew, "Přejete si uložit skóre ?\n\n" + "[A/N]", new Vector2(510, 240), Color.Red); break; // vykreslení zadávání přezdívky case eStav.Zapis: hra.spriteBatch.TextSeStinem(hra.fontCourierNew, "Zadejte jméno:", new Vector2(510, 240), Color.Red); hra.spriteBatch.DrawString(hra.fontBlox, prezdivka, new Vector2(510, 280), Color.Yellow); break; // výpis hráčů v tabulce case eStav.Vypis: // index posledního hráče v tabulce int posledni = (strana * 12) + 11; if (posledni > hraci.Count - 1) posledni = hraci.Count - 1; // index prvního hráče v tabulce int prvni = strana * 12; // výpis aktuální strany tabulky for (int i = prvni; i <= posledni; i++) { string text = (i + 1).ToString().PadLeft(3) + ". " + hraci[i].prezdivka + ":" + hraci[i].body; hra.spriteBatch.TextSeStinem(hra.fontCourierNew, text, new Vector2(510, 240 + (i - prvni) * 28), Color.Red); } hra.spriteBatch.TextSeStinem(hra.fontCourierNew, "Strana " + (strana + 1), new Vector2(930, 550), Color.Red); break; // vykreslení chyby načtení case eStav.ChybaNacteni: hra.spriteBatch.TextSeStinem(hra.fontCourierNew, $"{klient.ChybaNacitani} \n Zkusit znovu? [A/N]", new Vector2(510, 240), Color.Red); break; // vykreslení chyby uložení case eStav.ChybaUlozeni: hra.spriteBatch.TextSeStinem(hra.fontCourierNew, $"{klient.ChybaUkladani} \n Zkusit znovu? [A/N]", new Vector2(510, 240), Color.Red); break; } hra.spriteBatch.End(); base.Draw(gameTime); }
Najkomplikovanejšie je vykreslenie stavu výpisu rekordov. Ostatné stavy
len vypisujú nejaký text. Na aké sme strane udáva atribút
strana
, predvolená hodnota je 0
. Vypočítame index
prvého a posledného rekordu na tejto strane, teda aké inštancie
Hrac
z výsledkov hráčov chceme vypísať. Na jednu stranu
vypíšeme vždy 12 záznamov. Potom prejdeme všetky čísla medzi prvým a
posledným az poľa vypíšeme hráčov a ich výsledky na obrazovku. Nakoniec
nezabudneme vypísať na akej stránke sa nachádzame. Je to síce maličkosť,
ale v takýchto maličkostiach je spokojnosť používateľa (hráča) .
Update ()
V metóde Update()
to bude ďaleko zaujímavejšie. Tu budeme
strážiť klávesnicu a odchytávať užívateľský vstup, presnejšie
klávesy A a N a dokonca potom všetky písmená pre
zápis prezývky hráčov. Ukážme si najprv kompletnú kód metódy:
public override void Update(GameTime gameTime) { // návrat o do menu if (hra.klavesy.IsKeyDown(Keys.Escape) == true) hra.PrepniObrazovku(hra.obrazovkaMenu); // výběr chování dle aktuálního stavu switch (stav) { // dotaz při chybě načtení case eStav.ChybaNacteni: if (hra.NovaKlavesa(Keys.A)) // ano Nacti(); if (hra.NovaKlavesa(Keys.N)) // ne hra.PrepniObrazovku(hra.obrazovkaMenu); break; // dotaz při chybě uložení case eStav.ChybaUlozeni: if (hra.NovaKlavesa(Keys.A)) Uloz(); if (hra.NovaKlavesa(Keys.N)) hra.PrepniObrazovku(hra.obrazovkaMenu); break; // dotaz na nahrání skóre na internet case eStav.Otazka: if (hra.NovaKlavesa(Keys.A)) stav = eStav.Zapis; if (hra.NovaKlavesa(Keys.N)) hra.PrepniObrazovku(hra.obrazovkaMenu); break; // zadávání přezdívky case eStav.Zapis: // získání všech stisknutých kláves Keys[] stisknuteKlavesy = hra.klavesy.GetPressedKeys(); foreach (var klavesa in stisknuteKlavesy) { // klávesa je nově stisknuta if (hra.klavesyMinule.IsKeyUp(klavesa)) { // mazání backspacem if ((klavesa == Keys.Back) && (prezdivka.Length > 0)) prezdivka = prezdivka.Remove(prezdivka.Length - 1, 1); else // mezerník if (klavesa == Keys.Space) prezdivka = prezdivka.Insert(prezdivka.Length, " "); else // potvrzení enterem if (klavesa == Keys.Enter) { hra.hrac.prezdivka = prezdivka; Uloz(); // vynulujeme uložené body hra.hrac.body = 0; } else // ostatní klávesy - vložení písmen { string pismeno = klavesa.ToString(); if (pismeno.Length == 2) pismeno = pismeno.Replace("D", ""); pismeno = pismeno.Replace("NumPad", ""); // přidání písmene do přezdívky if ((pismeno.Length == 1) && (prezdivka.Length < 9)) prezdivka += pismeno; } } } break; // přepínání stran ve výpisu tabulky case eStav.Vypis: if (hra.klavesy.IsKeyDown(Keys.Up) && hra.klavesyMinule.IsKeyUp(Keys.Up)) if (strana > 0) strana--; if (hra.klavesy.IsKeyDown(Keys.Down) && hra.klavesyMinule.IsKeyUp(Keys.Down)) if (strana < (int)Math.Ceiling((double)hraci.Count / 12) - 1) strana++; break; } base.Update(gameTime); }
Najzložitejšia je v tomto prípade zadania prezývky hráčov, zvyšné stavy sú len triviálne reakcie na klávesy A a N.
U zadanie prezývky dáme hráči možnosť zapísať akúkoľvek prezývku
bude chcieť. Očakávania je možnosť písať akýkoľvek znak a možnosť
napísaný znak zmazať. Obmedzíme iba dĺžku prezývky. Budeme čítať
stlačené klávesy a prevádzať ich priamo na reťazec. Ale má to háčik,
čísla na NumPad majú prefix "NumPad"
a čísla v hornej časti
klávesnice majú prefix "D"
. Odstrániť "NumPad"
je
ľahké, použijeme metódu Replace()
priamo na reťazci au
písmená "D"
skontrolujeme dĺžku zaznamenaného znaku, pretože
chceme odstrániť len "D"
pred znakom a nie skutočné písmeno
"D"
OnEnabledChanged ()
Ešte vás zoznámim s peknou preťažiteľný metódou
OnEnabledChanged()
, ktorá príde veľmi vhod, ak chceme prepnúť
komponent do aktívneho stavu a súčasne jej uviesť do východiskového stavu.
Napríklad budeme chcieť, aby sa hra nevypla, ak prehráme. Keby sme sa
vrátili do menu prepnutím obrazovky a potom späť do hry, tak zistíme, že
hra je v stave v akom sme jej skončili a nie je možné ju hrať. A práve
preto príde na scénu metóda OnEnabledChanged()
. Upravíme ešte
komponent KomponentaLevel
na znovu spustenie po prehre. Úpravy
budú vyzerať takto:
using System; // přidat using pro EventArgs, pokud není protected override void OnEnabledChanged(object sender, EventArgs args) { if (Enabled) { stavHry = eStavHry.Hra; hra.PrepniHudbu(hra.hudba_zardax); hra.hrac = new Hrac(); hraciPlocha.Vyprazdni(); rychlost = 1; // vygenerování a dosazení kostek pristiKostka = generatorKostek.Generuj(7); DalsiKostka(); } base.OnEnabledChanged(sender, args); }
A v Update()
pri vytvorení novej kocky upravíme takto
if (hraciPlocha.Kolize(kostka, kostka.pozice))
hra.PrepniObrazovku(hra.obrazovkaMenu);
Po týchto úpravách budete môcť opakovane hrať bez nutnosti hru znova zapínať. Šikovné že?
Nabudúce, v lekcii Hra tetris v MonoGame: Dokončenie , dokončíme klienta na ukladanie výsledkov a tým našej série uzavrieme.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 17x (13.23 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#