Lekce 9 - Hra Tetris v MonoGame: Zprovoznění hry
V minulé lekci, Hra tetris v MonoGame: Hrací plocha, jsme si naprogramovali hrací plochu k tetrisu.
Dnes zprovozníme základy hry tak, aby se dala hrát.
Přesuneme se do KomponentaLevel
. Přidáme atribut
hraciPlocha
, ve kterém bude uložena instance třídy z
minula:
private HraciPlocha hraciPlocha;
V Initialize()
nastavíme hrací ploše rozměry a pozici:
hraciPlocha = new HraciPlocha(10, 20, poziceHraciPlochy);
Přidáme metodu, která nám podá další kostku. Bude velmi jednoduchá, vygeneruje novou kostku a nastaví ji pozici nahoru doprostřed:
public void DalsiKostka() { kostka = generatorKostek.Generuj(7); kostka.pozice = hraciPlocha.PosadKostku(kostka); }
Vidíme, že jsme použili k zarovnání kostky metodu hrací plochy. Metodu
zavoláme na konci Initialize()
, místo generování kostky:
DalsiKostka();
Hrací plochu budeme určitě chtít vykreslovat, za vykreslení kostky v
Draw()
tedy přidáme:
hraciPlocha.Vykresli(hra.spriteBatch, sprityPolicek);
Skvělé. Můžete zkusit spustit, výsledkem by zatím měla být náhodná kostka nahoře ve středu hrací plochy.
Real-time logika
Konečně se dostáváme k real-time logice celé hry. Začněme pádem kostky.
Pád kostky
Pád kostky nastane jednou za určitou dobu. Pro začátek bude tato doba 1
sekunda, později se však bude hra zrychlovat. Přidejme si atribut
rychlost
typu float
:
private float rychlost;
V Initialize()
ho nastavíme na hodnotu 1
:
rychlost = 1;
Budeme potřebovat měřit čas od posledního spadnutí kostky. Přidejme
podobně ještě jednu proměnnou jménem casOdPoslednihoPadu
:
private float casOdPoslednihoPadu;
V Initialize()
ji nastavíme na 0
:
casOdPoslednihoPadu = 0;
Přesuneme se do Update()
. Zde si vytvoříme proměnnou
sekundy
, do které uložíme uběhlé vteřiny od posledního
update (bude to samozřejmě desetinné číslo, tedy float
):
float sekundy = (float)gameTime.ElapsedGameTime.TotalSeconds;
Takto získaný počet sekund poté přičteme k
casOdPoslednihoPadu
:
casOdPoslednihoPadu += sekundy;
Nyní se stačí podívat, zda uběhlo již tolik, kolik říká rychlost
hry. Pokud ano, necháme kostku spadnout a vynulujeme
casOdPoslednihoPadu
:
if (casOdPoslednihoPadu >= rychlost) { kostka.Spadni(); casOdPoslednihoPadu = 0; }
Můžeme vyzkoušet.
Kostka po chvilce samozřejmě vyjede z hrací plochy. Musíme reagovat na kolizi a v tomto případě kostku připojit k hrací ploše. K obojímu máme připravené metody a neměl by to být žádný problém.
Kolize nastane ve chvíli, kdy je kostka již tam, kde být nemá. To
opravíme jednoduše tím, že ji snížíme Y
o jedna. Kostku
poté připojíme k hrací ploše a zavoláme vymazání případných plných
řad. Nakonec si řekneme o další kostku.
if (hraciPlocha.Kolize(kostka, kostka.pozice))
{
kostka.pozice.Y--;
hraciPlocha.Pripoj(kostka);
hraciPlocha.VymazRady();
DalsiKostka();
}
Celou tuto kontrolu vložíme za volání metody Spadni()
v
Update()
, protože logicky ke kolizi dojde poté, co kostka spadne
příliš nízko nebo na jinou kostku.
Hru spustíme a necháme ji chvíli běžet.
Vidíme, že kolize nastane jak při dosažení dna hrací plochy, tak při střetu s jinou kostkou. Stejně nám kolize nastane i v případě, že s kostkou vyjedeme z hrací plochy po stranách.
Pohyb kostkou
Aby se dala hra opravdu hrát, mělo by jít kostkou pohybovat. Posouvat
kostku by nemělo být nic těžkého, pouze musíme kontrolovat kolize, aby
nám nevyjela z hrací plochy nebo nenajela do jiné kostky. Zde se nám
přesně hodí parametr pozice v metodě Kolize()
, budeme se totiž
ptát vždy na to, jestli je budoucí souřadnice kostky volná a pokud ne,
pohyb vůbec neprovedeme.
if ((hra.klavesy.IsKeyDown(Keys.Right)) && (!hraciPlocha.Kolize(kostka, new Vector2(kostka.pozice.X + 1, kostka.pozice.Y)))) kostka.pozice.X++; // pohyb doprava if ((hra.klavesy.IsKeyDown(Keys.Left)) && (!hraciPlocha.Kolize(kostka, new Vector2(kostka.pozice.X - 1, kostka.pozice.Y)))) kostka.pozice.X--; // doleva
Kód vložíme za uložení sekund do Update()
a
vyzkoušíme.
Pohyb je velmi rychlý, protože se klávesa vyhodnocuje příliš často. S
tímto problémem jsme se již setkali v prvních lekcích našeho kurzu. Jedna
možnost by byla použít naši metodu NovaKlavesa()
a pohybovat
kostkou klikáním. To by však bylo nepohodlné. Vytvoříme si tedy časovač,
podobně jako pro pád kostky a pohyb budeme vykonávat jen tehdy, pokud uběhl
určitý čas.
Třídě přidáme 2 další atributy, casOdPoslednihoStisku
a
prodlevaKlavesy
:
private float casOdPoslednihoStisku; private float prodlevaKlavesy;
Proměnné inicializujeme v Initialize()
:
prodlevaKlavesy = 0.06f; casOdPoslednihoStisku = 0;
Nyní budeme postupovat úplně stejně jako u pádu, v Update()
budeme přičítat uplynulé sekundy k času od posledního stisku klávesy.
Obsluhu kláves poté vložíme do podmínky a případně vynulujeme čas od
posledního stisku.
casOdPoslednihoStisku += sekundy; if (casOdPoslednihoStisku > prodlevaKlavesy) { if ((hra.klavesy.IsKeyDown(Keys.Right)) && (!hraciPlocha.Kolize(kostka, new Vector2(kostka.pozice.X + 1, kostka.pozice.Y)))) kostka.pozice.X++; // pohyb doprava if ((hra.klavesy.IsKeyDown(Keys.Left)) && (!hraciPlocha.Kolize(kostka, new Vector2(kostka.pozice.X - 1, kostka.pozice.Y)))) kostka.pozice.X--; // doleva casOdPoslednihoStisku = 0; }
Vyzkoušejte si to, rychlost můžete případně regulovat změnou prodlevy
v Initialize()
.
Vraťme se ještě k rotaci. I když funguje, může nastat situace, že po rotaci bude kostka kolidovat (natočí se tak, že se zapasuje do jiné nebo vyleze z hrací plochy). V takovém případě nebudeme chtít, aby k rotaci došlo. Tohoto chování dosáhneme malým trikem, kostku orotujeme stejně, jako to děláme teď, ale pokud v tu chvíli začne kolidovat, orotujeme ji ještě 3x, tím pádem se vrátí do původní polohy a navenek se nestane vůbec nic
Obsluhu rotace upravíme takto:
if (hra.NovaKlavesa(Keys.Enter) || hra.NovaKlavesa(Keys.Up)) { kostka.Orotuj(); // Orotovaná kostka koliduje - vrátím rotaci zpět if (hraciPlocha.Kolize(kostka, kostka.pozice)) for (int i = 0; i < 3; i++) kostka.Orotuj(); }
Protože hráč bude jistě netrpělivý a kostky padají zpočátku pomalu,
umožníme mu zrychlit jejich pád pravým Ctrl. Rychlost již tedy
nebude vždy stejná a proměnná rychlost
nám přestává
stačit. Přidejme si do Update()
pod obsluhu kláves novou
proměnnou casMeziPady
, kterou nastavíme na rychlost
.
Pokud je držena klávesa pravé Ctrl, nastavíme ji na nějakou
nižší hodnotu, třeba 0.15f
. K podmínce držení klávesy
ještě musíme přidat, aby byla rychlost vyšší, než zde
0.15f
, jinak by si hráč mohl klávesou hru ve vysokých
obtížnostech naopak zpomalit.
float casMeziPady = rychlost; if (hra.klavesy.IsKeyDown(Keys.RightControl) && (rychlost > 0.15f)) casMeziPady = 0.15f;
Ještě upravíme podmínku pádu kostky, kde místo proměnné
rychlost
použijeme casMeziPady
:
if (casOdPoslednihoPadu >= casMeziPady)
Vyzkoušíme.
V Tetrisu bývá zvykem ještě tlačítko, které kostku uzemní okamžitě,
tím mám na mysli, že spadne dolů až tam, kde bude kolidovat. Toto chování
naprogramujeme na klávesu šipka dolů. Bude stačit volat metodu
Spadni()
v cyklu tak dlouho, dokud o pozici níže nedojde ke
kolizi. Takto bude kostka připravena těsně před kolizí. Ještě poté
přičteme do proměnné casOdPoslednihoPadu
, aby se vyvolala
obsluha pádu kostky níže. Následující kód umístíme za obsluhu
kláves:
if (hra.NovaKlavesa(Keys.Down)) { while (!hraciPlocha.Kolize(kostka, new Vector2(kostka.pozice.X, kostka.pozice.Y + 1))) kostka.Spadni(); casOdPoslednihoPadu += casMeziPady; }
Vyzkoušíme.
To je pro dnešek vše.
Příště, v lekci Hra Tetris v MonoGame: Bodování a dokončení levelu, level dokončíme .
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 330x (11.9 MB)
Aplikace je včetně zdrojových kódů v jazyce C#