Lekce 10 - Hra Tetris v MonoGame: Bodování a dokončení levelu
V minulé lekci, Hra Tetris v MonoGame: Zprovoznění hry, jsme zprovoznili základy hry a učinili tak level hratelný.
Dnes level dokončíme.
Příští kostka
Jak je u Tetrisu zvykem, hráč vidí příští kostku, aby se podle toho
mohl u současné kostky zařídit. Naše hra nebude výjimkou. Do
KomponentaLevel
si přidáme nový atribut s instancí příští
kostky:
private Kostka pristiKostka;
Před volání DalsiKostka();
v Initialize()
vložíme vytvoření instance příští kostky:
pristiKostka = generatorKostek.Generuj(7);
DalsiKostka();
Přesuneme se do metody DalsiKostka()
, která nyní do
kostka
uloží příští kostku a zároveň vygeneruje novou.
Její kód bude nově vypadat takto:
public void DalsiKostka() { kostka = pristiKostka; kostka.pozice = hraciPlocha.PosadKostku(kostka); pristiKostka = generatorKostek.Generuj(7); }
Nakonec příští kostku vykreslíme. Na pozadí je k tomu připravena
napřažená ruka robota, která vyzařuje modré pole. Na to kostku umístíme,
do Draw()
tedy za vykreslení kostky přidáme:
pristiKostka.Vykresli(new Vector2(930, 200), hra.spriteBatch, sprityPolicek);
Výsledek:
Skóre
Hra by měla hráče samozřejmě nějak vyzývat, v Tetrisu je k tomuto účelu skóre (body) a dosažený level. V souvislosti s hráčem nás bude zajímat ještě počet zaplněných řad a jeho přezdívka. V dalších lekcích si totiž ukážeme, jak skóre nahrát na internet tak, aby byl hráč pod přezdívkou vidět v tabulce nejlepších výsledků.
K projektu Robotris
připojíme novou třídu Hrac
,
do které vložíme zmíněné atributy. Ty budou pro zjednodušení veřejné a
třída bude sloužit jen k držení dat o hráči. Nezapomeňte dát třídě
modifikátor public
.
public class Hrac { public long body; public int rady; public int level; public string prezdivka; public Hrac() { body = 0; rady = 0; level = 1; } }
Na třídě není jinak nic zajímavého, asi jen to, že body
jsou typu long
, protože šikovný hráč by se nemusel vejít do
rozsahu typu int
Konstruktor atributy inicializuje.
Instanci hráče bude spravovat samotná třída Hra
, jelikož
ji bude v dalších lekcích potřeba sdílet mezi dalšími komponentami
(skóre tabulkou). Přesuňme se tedy do Hra.cs
a přidejme atribut
hrac
:
public Hrac hrac;
Tím jsme v Hra.cs
skončili, přejděme opět do
KomponentaLevel
, kde v Initialize()
vytvoříme
instanci hráče:
hra.hrac = new Hrac();
Vykreslení HUDu
Nyní vykreslíme tzv. HUD (informační panel s hodnotami hráče), který
bude zobrazovat skóre a level. Přejděme do metody Draw()
a
jednoduše přidejme výpis těchto hodnot z instance hráče. Budeme k tomu
potřebovat menší font, který máme připravený. Font se bude používat jen
v této komponentě, proto ho načteme zde. Jako další možnost bychom ho
mohli umístit do Hra.cs
jako veřejný. Vytvoříme privátní
atribut font
:
private SpriteFont font;
A v LoadContent()
do něj načteme font z obsahu:
font = hra.Content.Load<SpriteFont>("Fonty/font_blox_maly");
Nyní pouze v Draw()
vykreslíme hodnoty na příslušnou
pozici:
hra.spriteBatch.TextSeStinem(font, "skore\n " + hra.hrac.body.ToString(), new Vector2(30, 390), Color.Red); hra.spriteBatch.TextSeStinem(font, "level\n " + hra.hrac.level.ToString(), new Vector2(215, 390), Color.Red);
Výsledek:
Bodování
Pojďme hru bodovat, samozřejmě půjde o bodování řad. Budeme vycházet z originálního Tetrisu verze A. Body se připisují podle počtu zaplněných řad jednou kostkou:
Počet zaplněných řad | Vzorec |
---|---|
1 | level * 40 + 40 |
2 | level * 100 + 100 |
3 | level * 300 + 300 |
4 | level * 1200 + 1200 |
Zaplnění čtyř řad najednou se říká tetris a hráč toho docílí pouze kostkou tvaru I.
Pokud nějaká řada zmizí, ještě přehrajeme zvuk. Ten si nejdříve připravíme. Přidáme atribut třídy:
private SoundEffect zvukRada;
A načteme v LoadContent()
:
zvukRada = hra.Content.Load<SoundEffect>(@"Zvuky\zvuk_rada");
Jde se bodovat, přesuňme se do Update()
, do podmínky, kde
kontrolujeme kolizi kostky, připojujeme ji do hrací plochy a tak podobně.
Zmíněný blok kódu nyní vypadá takto:
if (hraciPlocha.Kolize(kostka, kostka.pozice))
{
kostka.pozice.Y--;
hraciPlocha.Pripoj(kostka);
hraciPlocha.VymazRady();
DalsiKostka();
}
Návratovou hodnotu metody VymazRady()
uložíme do proměnné
rady
(je to počet řad, které byly vymazány). Kód tedy
upravíme:
int rady = hraciPlocha.VymazRady();
Při vymazání alespoň jedné řady přehrajeme připravený zvuk:
if (rady > 0) zvukRada.Play();
Řady hráči připočteme:
hra.hrac.rady += rady;
A také mu za to přidělíme body podle příslušného vzorce:
switch (rady) { case 1: hra.hrac.body += hra.hrac.level * 40 + 40; break; case 2: hra.hrac.body += hra.hrac.level * 100 + 100; break; case 3: hra.hrac.body += hra.hrac.level * 300 + 300; break; case 4: hra.hrac.body += hra.hrac.level * 1200 + 1200; break; }
Level se bude zvyšovat každých 10 řad a při 0 řadách bude 1. Vypočteme ho takto:
int level = hra.hrac.rady / 10 + 1;
Pokud má hráč jiný level, než tento nový, vypočtený, aktualizujeme ho
a zároveň zvýšíme rychlost hry o 5/6
:
if (hra.hrac.level != level) { hra.hrac.level = level; rychlost = rychlost * 5 / 6; }
Můžete vyzkoušet.
Prohra
Zůstaňme ještě v bloku kolize kostky s hrací plochou. Budeme reagovat na
prohru. Hráč prohraje ve chvíli, kdy dovrší kostky až na horní okraj
hrací plochy. Jinými slovy pokud není kam položit nově padající kostku,
hra skončila. Proto se hned po volání DalsiKostka()
zeptáme,
zda koliduje. Pokud nová kostka ihned po položení koliduje, hru ukončíme.
To zatím uděláme tak, že ukončíme celou aplikaci. V dalších lekcích
zobrazíme obrazovku pro odeslání skóre a vrátíme se do menu.
Přidejme tedy za DalsiKostka()
jednoduchou podmínku:
if (hraciPlocha.Kolize(kostka, kostka.pozice))
hra.Exit();
Metodou Exit()
na instanci hry jsme hru ukončili. Level je
nyní již dobře hratelný.
Pauza
Určitě je dobré hráči umožnit hru zapauzovat. Existuje mnoho způsobů,
jak toho dosáhnout, mohli bychom si pro pauzu vytvořit další komponentu a
poté KomponentuLevel
zastavit. Uchýlíme se však k
jednoduššímu řešení, komponentě KomponentaLevel
přidělíme
stavy. Ty budou dva: Hra
a Pauza
. Ve třídě si
deklarujeme výčtový typ StavHry
a atribut tohoto typu:
public enum eStavHry { Hra, Pauza, } public eStavHry stavHry;
V Initialize()
stav nastavíme na Hra
:
stavHry = eStavHry.Hra;
Při pauze nebude hra aktivní a přes obrazovku bude vykreslen zašedlý sprite s hláškou, že je hra zapauzovaná. Sprite si stáhněte:
a přidejte do RobotrisContent
, do složky
Sprity/
.
Jako vždy si pro sprite vytvoříme atribut:
private Texture2D sprPauza;
A načteme ho v LoadContent()
:
sprPauza = hra.Content.Load<Texture2D>(@"Sprity\spr_pauza");
Podle stavu hry se bude chovat metoda Update()
. Rozdělíme ji
na 2 větve podle stavů. Celý současný obsah metody vložíme do podmínky,
zda je stav hry Hra
. Přidáme do něj ještě reakci na klávesu
Escape, která způsobí přepnutí stavu na pauzu a pozastavení
přehrávání hudby.
// Hra if (stavHry == eStavHry.Hra) { . . . if (hra.klavesy.IsKeyDown(Keys.Escape)) { MediaPlayer.Pause(); stavHry = eStavHry.Pauza; } }
Za větev s hrou přidáme další větev s pauzou. Ve stavu
Pauza
bude reagovat na klávesy A (Ano, ukončení hry)
a N (Ne, návrat do hry).
if (stavHry == eStavHry.Pauza) { if (hra.klavesy.IsKeyDown(Keys.A)) hra.Exit(); // Ukončení if (hra.klavesy.IsKeyDown(Keys.N)) { stavHry = eStavHry.Hra; // Pokračování MediaPlayer.Resume(); } }
Zbývá, aby na stav pauzy reagovala i vykreslovací metoda. Na konec
vykreslování v metodě Draw()
přidáme vykreslení spritu s
pauzou a textu:
if (stavHry == eStavHry.Pauza) { hra.spriteBatch.Draw(sprPauza, new Rectangle(0, 0, hra.sirkaOkna, hra.vyskaOkna), Color.White); hra.spriteBatch.TextSeStinem(font, "hra pozastavena", new Vector2(480, 260), Color.Red); hra.spriteBatch.TextSeStinem(hra.fontCourierNew, "Opravdu si přejete hru ukončit?\n\n\'A\' - ukončení hry \n\n\'N\' - pokračovat ve hře", new Vector2(440, 340), Color.Red); }
A vyzkoušíme:
Příště, v lekci Hra tetris v MonoGame: Vychytávky v levelu, do hry přidáme ještě další vychytávky.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 287x (12.09 MB)
Aplikace je včetně zdrojových kódů v jazyce C#