Lekce 11 - Hra tetris v MonoGame: Vychytávky v levelu
V minulé lekci, Hra Tetris v MonoGame: Bodování a dokončení levelu, jsme dokončili level Tetrisu a zprovoznili bodování a pauzu.
Dnešní tutoriál je poslední, kdy se budeme levelu věnovat. Vylepšíme rotaci kostky a přidáme do hry prvek zaměřovač.
Vylepšená rotace kostky
Jak se kostka rotuje určitě není nejpřirozenější. V pravém Tetrisu jsou kostky ručně nastavené v několika polohách, některé mají třeba jen 2 (kostka I), některé jen jednu (kostka O). Tento způsob ručního zadávání je ale velmi neprogramátorský a nic bychom se tím nenaučili. Náš způsob zatím rotuje každou kostku jako čtvercovou matici o hraně 4. Problém je v tom, že většina kostek má hranu jen 3, některé dokonce 2. Metodu rotace tedy upravíme tak, aby rotovala dle rozměrů kostky, přesněji dle její hrany.
Nejprve si upravme naše kostky v textovém souboru tak, aby byly vždy
umístěny v levém horním rohu. Samozřejmě musíme brát kostku jako
čtverec a podle delší hrany určit její polohu (to se týká zejména kostky
I). Soubor netfx.dll
tedy přepíšeme na následující obsah:
1000 1110 0000 0000 0010 1110 0000 0000 1100 1100 0000 0000 0000 1111 0000 0000 0100 1110 0000 0000 1100 0110 0000 0000 0110 1100 0000 0000 1000 0000 0000 0000 1010 0100 1010 0000 0100 1110 0100 0000 1110 0100 0100 0000 1100 0000 0000 0000
Otevřeme si třídu Kostka
a přidejme ji veřejný atribut
hrana
:
public int hrana;
Třídě přidejme privátní metodu ZjistiHranu()
, ve které
budeme nejprve předpokládat, že má kostka hranu 1
, tedy
nejnižší možnou, prázdnou kostku ve hře mít jistě nebudeme.
private void ZjistiHranu() { hrana = 1; }
Hranu zjistíme tak, že v metodě projedeme všechna políčka. Pokud narazíme na políčko s nenulovou hodnotou, zeptáme se, jestli je od levého horního rohu dále, než je současná známá hrana. Pokud ano, do hrany vždy uložíme tu větší souřadnici plného políčka.
for (int j = 0; j < 4; j++) for (int i = 0; i < 4; i++) { // nalezeno políčko dále od levého horního rohu, než je současná hrana if ((Policka[i, j] > 0) && (((i + 1) > hrana) || ((j + 1) > hrana))) { if (i > j) hrana = i + 1; else hrana = j + 1; } }
Metodu zavoláme v konstruktoru po generování spritů:
ZjistiHranu();
Hranu máme uloženou, nyní je hračka upravit metodu pro rotaci (budeme pracovat pouze s částí pole velikosti hrany):
public void Orotuj() { // pomocné pole int[,] a = ZkopirujPolicka(Policka); // rotace pole jako matice prohozením souřadnic for (int y = 0; y < hrana; y++) for (int x = 0; x < hrana; x++) Policka[x, y] = a[y, (hrana - 1) - x]; }
Na velikost kostky bude reagovat i metoda PosadKostku()
v
třídě HraciPlocha
:
public Vector2 PosadKostku(Kostka kostka) { return new Vector2((sirka / 2) - (kostka.hrana / 2), 0); }
Hru nyní vyzkoušíme, rotace je mnohem preciznější.
Zaměřovač
Některé verze Tetrisu ukazují přímo místo, kam kostka spadne, pokud stiskneme šipku dolů. Tento prvek jsem nazval zaměřovačem, ten do hry přidáme.
Získání pozice
Přejděme do HraciPlocha.cs
, kam přidáme metodu
SpocitejSouradniceZamerovace()
. Jako parametr bude brát kostku a
vracet bude pozici jako Vector2
. To budou souřadnice místa, kam
by kostka spadla, když bychom stiskli šipku dolů. Zjištění těchto
souřadnic již tedy umíme, stačí jednoduše posouvat pozici kostky dolů tak
dlouho, dokud nebude kolidovat. Pozici o 1 výše poté vrátíme.
public Vector2 SpocitejSouradniceZamerovace(Kostka kostka) { Vector2 pozice = kostka.pozice; // zkusíme posouvat kostku dolů tak dlouho, dokud nebude kolidovat while (!Kolize(kostka, pozice)) pozice.Y++; // vrátíme souřadnici o 1 nahoru, kdy ještě nekolidovala return new Vector2(kostka.pozice.X, pozice.Y - 1); }
Protože zjištění této pozice je ve hře nyní 2x, což není hezký
programátorský návyk, nahradíme logiku v obsluze šipky dolů v metodě
Update()
v KomponentaLevel
voláním této funkce. Kus
kódu s obsluhou bude vypadat takto:
if (hra.NovaKlavesa(Keys.Down))
{
kostka.pozice = hraciPlocha.SpocitejSouradniceZamerovace(kostka);
casOdPoslednihoPadu += casMeziPady;
}
Zůstaňme v KomponentaLevel
a založme si privátní proměnnou
s pozicí zaměřovače:
private Vector2 poziceZamerovace;
Nastavení této proměnné se bude provádět v Update()
, ve
větvi se stavem Hra
:
poziceZamerovace = hraciPlocha.SpocitejSouradniceZamerovace(kostka);
Vykreslení
Pozici bychom měli, nyní musíme zaměřovač na toto místo vykreslit. Zaměřovač bude vypadat stejně jako kostka. Nebudeme pro něj tvořit další objekt (i když by to jistě také šlo), ale jen naučíme kostku vykreslovat se na jiné souřadnice a průhledně.
Přejdeme do Kostka.cs
a upravíme metodu
Vykresli()
. Označíme ji jako privátní a před její název
vložíme _
(podtržítko). Přidáme ji další 2 parametry
navíc. Parametr pozice
typu float
bude vykreslovací
pozice kostky. Takto můžeme kostku vykreslovat jinde, než opravdu je. Něco
podobného jsme již dělali u metody Kolize()
. Parametr
alfa
bude alfaprůhlednost kostky. V kódu jen barvu vynásobíme
alfa
, C# sám pochopí, že myslíme pozici z parametru metody a ne
atribut třídy.
Kód metody vypadá nyní takto:
private void _Vykresli(Vector2 okraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity, Vector2 pozice, float alfa) { for (int j = 0; j < 4; j++) for (int i = 0; i < 4; i++) if (Policka[i, j] > 0) spriteBatch.Draw(sprity[Policka[i, j] - 1], new Vector2(okraj.X + (i + pozice.X) * sprity[0].Width, okraj.Y + (j + pozice.Y) * sprity[0].Height), Color.White * alfa); }
Přidat metody k vykreslení kostky a zaměřovače bude nyní hračka.
Metoda Vykresli()
jednoduše zavolá _Vykresli()
, kde
bude jako pozice atribut pozice
a alfa bude 1
:
public void Vykresli(Vector2 levyOkraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity) { _Vykresli(levyOkraj, spriteBatch, sprity, pozice, 1); }
Metoda VykresliZamerovac()
využije všechny parametry metody
_Vykresli()
:
public void VykresliZamerovac(Vector2 levyOkraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity, Vector2 pozice, float alfa) { _Vykresli(levyOkraj, spriteBatch, sprity, pozice, alfa); }
Pokud vás nyní napadá, že by nám stačily metody jen 2 a to tak, že by
Vykresli()
volala VykresliZamerovac()
, máte pravdu. Z
hlediska návrhu mi to však nepřijde logické.
Přesuneme se do KomponentaLevel
, do metody Draw()
.
Zde za vykreslení kostky a příští kostky vložíme vykreslení
zaměřovače:
kostka.VykresliZamerovac(poziceHraciPlochy, hra.spriteBatch, sprityPolicek, poziceZamerovace, 0.3f);
A spustíme:

Hra se hraje mnohem lépe.
Pulzování
Pojďme si na konec ukázat ještě jeden efekt, necháme alfaprůhlednost
zaměřovače pulzovat, jako jsme to již dělali s barvou mraků. Opět budeme
ukládat směr pulzování do proměnné typu int
jako hodnoty
1
a -1
. Samotnou hodnotu průhlednosti poté do
proměnné typu float
. Přidáme k třídě tyto 2 atributy:
private int zamerovacSmer; private float zamerovacAlfa;
V Initialize()
nastavíme výchozí hodnoty:
zamerovacAlfa = 0.0f; zamerovacSmer = 1;
Do Update()
do větve pro stav Hra
vložíme
obsluhu pulzování:
zamerovacAlfa += 0.02f * zamerovacSmer; if (zamerovacAlfa > 0.5) zamerovacSmer = -1; if (zamerovacAlfa < 0.2) zamerovacSmer = 1;
Hodnota zamerovacAlfa
nyní pulzuje mezi 0.2
a
0.5
.
Přesuňte se do Draw()
a pozměňte vykreslení zaměřovače a
příští kostky takto:
pristiKostka.Vykresli(new Vector2(930, 200 + (40 * zamerovacAlfa)), hra.spriteBatch, sprityPolicek); kostka.VykresliZamerovac(poziceHraciPlochy, hra.spriteBatch, sprityPolicek, poziceZamerovace, zamerovacAlfa);
Spusťte a vychutnejte si hru, protože je hotová Pulzování jsme využili i pro
efekt levitace příští kostky.
Příště, v lekci MonoGame: Správa herních obrazovek, se podíváme na správu herních obrazovek a připravíme se na tvorbu herního menu.
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 245x (12.09 MB)
Aplikace je včetně zdrojových kódů v jazyce C#