11. diel - Hra tetris v MonoGame: Vychytávky v leveli
V minulej lekcii, Hra Tetris v MonoGame: Bodovanie a dokončenie levelu , sme dokončili level Tetrisu a sprevádzkovali bodovania a pauzu. Dnešná tutoriál je posledný, kedy sa budeme levelu venovať. Vylepšíme rotáciu kocky a pridáme do hry prvok zameriavač.
Vylepšená rotácie kocky
Ako sa kocka rotuje určite nie je najprirodzenejší. V pravom Tetrisu sú kocky ručne nastavené v niekoľkých polohách, niektoré majú napríklad len 2 (kocka I), niektoré len jednu (kocka O). Tento spôsob ručného zadávania je ale veľmi neprogramátorský a nič by sme sa tým nenaučili. Náš spôsob zatiaľ rotuje každú kocku ako štvorcovú maticu o hrane 4. Problém je v tom, že väčšina kociek má hranu len 3, niektoré dokonca 2. Metódu rotácie teda upravíme tak, aby rotovala podľa rozmerov kocky, presnejšie podľa jej hrany.
Najprv si upravme naše kocky v textovom súbore tak, aby boli vždy
umiestnené v ľavom hornom rohu. Samozrejme musíme brať kocku ako štvorec a
podľa dlhšej hrany určiť jej polohu (to sa týka najmä kocky I). Súbor
netfx.dll
teda prepíšeme na nasledujúci 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
Otvoríme si triedu Kostka
a pridajme ju verejný atribút
hrana
:
public int hrana;
Triede pridajme privátne metódu ZjistiHranu()
, v ktorej budeme
najprv predpokladať, že má kocka hranu 1
, teda najnižšiu
možnú, prázdnu kocku v hre mať určite nebudeme.
private void ZjistiHranu() { hrana = 1; }
Hranu zistíme tak, že v metóde prejdeme všetky políčka. Ak narazíme na políčko s nenulovú hodnotou, spýtame sa, či je od ľavého horného rohu ďalej, než je súčasná známa hrana. Ak áno, do hrany vždy uložíme tu väčšia súradnici 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; } }
Metódu zavoláme v konstruktoru po generovanie spritov:
ZjistiHranu();
Hranu máme uloženú, teraz je hračka upraviť metódu pre rotáciu (budeme pracovať iba s časťou poľa veľkosti 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 veľkosť kocky bude reagovať aj metóda PosadKostku()
v
triede HraciPlocha
:
public Vector2 PosadKostku(Kostka kostka) { return new Vector2((sirka / 2) - (kostka.hrana / 2), 0); }
Hru teraz vyskúšame, rotácia je oveľa precíznejšie.
Zameriavač
Niektoré verzie Tetrisu ukazujú priamo miesto, kam kocka spadne, ak stlačíme šípku nadol. Tento prvok som nazval zameriavačom, ten do hry pridáme.
Získanie pozície
Prejdime do HraciPlocha.cs
, kam pridáme metódu
SpocitejSouradniceZamerovace()
. Ako parameter bude brať kocku a
vracať bude pozíciu ako Vector2
. To budú súradnice miesta, kam
by kocka spadla, keď by sme stlačili šípku nadol. Zistenie týchto
súradníc už teda vieme, stačí jednoducho posúvať pozíciu kocky dole tak
dlho, dokiaľ nebude kolidovať. Pozíciu o 1 vyššie po tom vrátime.
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); }
Pretože zistenie tejto pozície je v hre teraz 2x, čo nie je pekný
programátorský návyk, nahradíme logiku v obsluhe šípky nadol v metóde
Update()
v KomponentaLevel
volaním tejto funkcie. Kus
kódu s obsluhou bude vyzerať takto:
if (hra.NovaKlavesa(Keys.Down))
{
kostka.pozice = hraciPlocha.SpocitejSouradniceZamerovace(kostka);
casOdPoslednihoPadu += casMeziPady;
}
Zostaňme v KomponentaLevel
a založme si privátne premennú s
pozíciou zameriavača:
private Vector2 poziceZamerovace;
Nastavenie tejto premennej sa bude vykonávať v Update()
, vo
vetve so stavom Hra
:
poziceZamerovace = hraciPlocha.SpocitejSouradniceZamerovace(kostka);
Vykreslenie
Pozíciu by sme mali, teraz musíme zameriavač na toto miesto vykresliť. Zameriavač bude vyzerať rovnako ako kocka. Nebudeme pre neho tvoriť ďalší objekt (aj keď by to určite tiež šlo), ale len naučíme kocku vykresľovať sa na iné súradnice a transparentne.
Prejdeme do Kostka.cs
a upravíme metódu
Vykresli()
. Označíme ju ako privátne a pred jej názov vložíme
_
(znak podčiarknutia). Pridáme ju ďalšie 2 parametre navyše.
Parameter pozice
typu float
bude vykresľovací
pozície kocky. Takto môžeme kocku vykresľovať inde, než naozaj je. Niečo
podobné sme už robili u metódy Kolize()
. Parameter
alfa
bude alfaprůhlednost kocky. V kóde len farbu vynásobíme
alfa
, C# sám pochopí, že myslíme pozíciu z parametra metódy a
nie atribút triedy:
Kód metódy vyzerá teraz 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); }
Pridať metódy k vykreslenie kocky a zameriavača bude teraz hračka.
Metóda Vykresli()
jednoducho zavolá _Vykresli()
, kde
bude ako pozícia atribút pozice
a alfa bude 1
:
public void Vykresli(Vector2 levyOkraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity) { _Vykresli(levyOkraj, spriteBatch, sprity, pozice, 1); }
Metóda VykresliZamerovac()
využije všetky parametre metódy
_Vykresli()
:
public void VykresliZamerovac(Vector2 levyOkraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity, Vector2 pozice, float alfa) { _Vykresli(levyOkraj, spriteBatch, sprity, pozice, alfa); }
Ak vás teraz napadá, že by nám stačili metódy len 2 a to tak, že by
Vykresli()
volala VykresliZamerovac()
, máte pravdu. Z
hľadiska návrhu mi to však nepríde logické.
Presunieme sa do KomponentaLevel
, do metódy
Draw()
. Tu za vykreslenie kocky a budúci kocky vložíme
vykreslenie zameriavača:
kostka.VykresliZamerovac(poziceHraciPlochy, hra.spriteBatch, sprityPolicek, poziceZamerovace, 0.3f);
A spustíme:
Hra sa hrá oveľa lepšie.
Pulzovanie
Poďme si na koniec ukázať ešte jeden efekt, necháme alfaprůhlednost
zameriavača pulzovať, ako sme to už robili s farbou mrakov. Opäť budeme
ukladať smer pulzovanie do premennej typu int
ako hodnoty
1
a -1
. Samotnú hodnotu priehľadnosti potom do
premennej typu float
. Pridáme k triede tieto 2 atribúty:
private int zamerovacSmer; private float zamerovacAlfa;
V Initialize()
nastavíme predvolené hodnoty:
zamerovacAlfa = 0.0f; zamerovacSmer = 1;
Do Update()
do vetvy pre stav Hra
vložíme obsluhu
pulzovanie:
zamerovacAlfa += 0.02f * zamerovacSmer; if (zamerovacAlfa > 0.5) zamerovacSmer = -1; if (zamerovacAlfa < 0.2) zamerovacSmer = 1;
Hodnota zamerovacAlfa
teraz pulzuje medzi 0.2
a
0.5
.
Presuňte sa do Draw()
a pozmeňte vykreslenie zameriavača a
budúci kocky takto:
pristiKostka.Vykresli(new Vector2(930, 200 + (40 * zamerovacAlfa)), hra.spriteBatch, sprityPolicek); kostka.VykresliZamerovac(poziceHraciPlochy, hra.spriteBatch, sprityPolicek, poziceZamerovace, zamerovacAlfa);
Spustite a vychutnajte si hru, pretože je hotová Pulzovanie sme využili aj pre efekt levitácie budúci kocky. Nabudúce, v lekcii MonoGame: Správa herných obrazoviek , sa pozrieme na správu herných obrazoviek a pripravíme sa na tvorbu herného menu.
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é 245x (12.09 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#