6. diel - Hra tetris v MonoGame: Kostka
V minulej lekcii, Rozdelenie MonoGame hry do komponentov , sme si ukázali rozdelenie hry do komponentov. Dnes konečne začneme s Tetris, presnejšie s padajúce kockou.
Instance kocky budú v hre vždy len 2. Jedna aktuálne padajúce, druhá tá, čo padne nabudúce. Pretože ich teda bude viac a jedná sa o objekt v hre, nepoužijeme k reprezentácii kocky komponentu, ale iba jednoduchú triedu. Komponent zvyčajne spravuje väčší celok logiky. Po dopade sa kocka spojí s hracou plochou, ktorá teda nebude kolekcií kociek, ale jednoducho dvojrozmerných poľom políčok. Určite by sa hra dala riešiť aj inak, ale tento spôsob mi prišiel najjednoduchšie.
Vytvoríme si triedu reprezentujúci kocku Tetrisu. K projektu
Robotris
pridáme novú triedu Kostka
, jej
modifikátor prístupu nastavíme na public
.
public class Kostka
Kocke pridáme verejnú vlastnosť Policka
, ktorá bude
dvojrozmerný poľom typu int
. Tam budú uložené jednotlivé
políčka, z ktorých sa kocka skladá. Pole má rozmer 4x4 (kvôli tej
najdlhšej kocke tvaru I), mohli by sme si ho s kockou tvare S predstaviť asi
takto:
0110 1100 0000 0000
1
označuje plné políčko, 0
prázdne. Setter
vlastnosti označíme ako privátne, ale nie je to nutné a podobného výsledku
by sme dosiahli aj obyčajným atribútom. V hrách sa na vlastnosti nedbá
toľko, ako sme zvyknutí, je to mimojiné aj preto, že sú pomalšie a u hry
väčšinou požadujeme maximálny výkon.
public int[,] Policka { get; private set; }
Ďalej pridáme pozíciu kocky na hracej ploche, bude typu
Vector2
a bude sa jednať o bežnú, verejnú premennú. Keďže je
Vector2
štruktúra (teda hodnotový typ), mali by sme pri
použití vlastností problém jednoducho modifikovať jej zložky. Určite sa
nebojte používať úplne obyčajné verejné premenné, v hre sa na to toľko
nehrá. Pridáme potrebný using
:
using Microsoft.Xna.Framework;
a atribút:
public Vector2 pozice;
Keďže vieme, že polia sú v C# riešené referencií, nemôžeme
jednoducho dosadiť políčka jednej kocky do políčok druhej. Výsledkom by
bolo, že by obe kocky používali tá istá políčka a keď by sa jedna treba
orotovala, orotovaly by sa obe. Pretože políčka budeme kopírovať často,
vytvoríme si k tomu metódu. Tá políčka znovu založia a dosadí do nich
hodnoty z tých, ktorá kopírujeme. Okopírované pole metóda vráti. Celého
efekt dosiahneme jednoducho pomocou 2 for
cyklov.
private int[,] ZkopirujPolicka(int[,] policka) { int [,] nova = new int [4, 4]; for (int j = 0; j < 4; j++) for (int i = 0; i < 4; i++) nova[i, j] = policka[i, j]; return nova; }
Implementujte teraz konštruktor triedy, ten bude brať v parametri políčka, z ktorých kocku vytvorí. Ďalej tu resetujeme pozíciu kocky:
public Kostka(int[,] policka) { Policka = ZkopirujPolicka(policka); pozice = new Vector2(0, 0); }
Pád
Pridajme si jednoduchú metódu Spadni()
, ktorá vyvolá pád
kocky o 1 úroveň:
public void Spadni() { pozice.Y++; }
Rotácie
To bolo také odhlehčující : D Teraz zas niečo ťažšieho, napíšeme si metódu, ktorá naše polia orotujeme. Rotovať budeme v smere hodinových ručičiek. Rotácia dosiahneme tak, že pole dokopírujeme az tejto kópie budeme políčka dosadzovať do políčok pôvodných. Trik je v tom, že prehodíme X a Y súradnice políčok a 2. súradnicu odpočítame od pozície 3. Pre lepšie pochopenie prikladám ilustráciu:
Ak stále tápate, skúste si to namaľovať a papier otáčať. Ak ani to nepomohlo, budete mi proste musieť veriť Metóda vyzerá takto:
public void Orotuj() { // pomocné pole int[,] a = ZkopirujPolicka(Policka); // rotace pole jako matice prohozením souřadnic for (int y = 0; y < 4; y++) for (int x = 0; x < 4; x++) Policka[x, y] = a[y, 3 - x]; }
Rotácia má problém, ktorý v tutoriálu z dôvodu jednoduchosti zabudneme. Niektoré kocky sú rozmere 2x2 (v základnej verzii len kocka O), niektoré 3x3 a niektoré (v základnej verzii len kocka I) rozmeru 4x4. Metóda vyššie rotuje kocku vždy ako pole 4x4 a menšie kocky teda nie sú centrované. Toho by sa dalo docieliť nejakou ďalšou kontrolou a ak budete naliehať, môžem ju po dokončení kurzu doplniť.
Zostáva kocku nejako vykresliť. Už sme si ukázali, ako sa vykresľujú
komponenty. U kocky to bude podobné, tiež ju pridáme metódu
Vykresli()
. SpriteBatch
a príslušnú textúru
políčka do nej dostaneme cez parameter. Je to okrem komponentov, kde sa
odovzdávala inštancie hry v konstruktoru, druhý spôsob, ako sa vysporiadať
so závislosťami. Opäť opakujem, že tento spôsob je lepší pri
jednotlivých herných objektov (napr. Jednotlivé kocky), komponenty sú skôr
pre celky (napr. Level obsahujúce kocky, hraciu plochu ...). Keďže súradnice
kocky zodpovedajú súradniciam v hracej ploche (teda napr. [6; 10]) a nie
súradniciam na obrazovke, pridáme ešte parameter okraj, ktorý nám umožní
kocku posúvať. Metóda prejde jednotlivé políčka a tie s hodnotou väčšou
ako 0
vykreslí. V cykloch budeme pracovať s rozmermi jedného
políčka, aby sme ich mohli skladať vedľa seba, rozmer zistíme vlastnosťami
Width
a Height
na inštanciu textúry.
public void Vykresli(Vector2 okraj, LepsiSpriteBatch spriteBatch, Texture2D sprite) { for (int j = 0; j < 4; j++) for (int i = 0; i < 4; i++) if (Policka[i, j] > 0) spriteBatch.Draw(sprite, new Vector2(okraj.X + (i + pozice.X) * sprite.Width, okraj.Y + (j + pozice.Y) * sprite.Height), Color.White); }
Kvôli použitie Texture2D
pridáme using
:
using Microsoft.Xna.Framework.Graphics;
Triedu zatiaľ necháme a kocku si vyskúšame. Stiahnite si sprity políčok
z archívu pod článkom. Jedná sa o sadu 15tich spritov pre políčka. Zložku
Policka/
rozbaľte a celú ju pretiahnite do zložky
Sprity/
v MonoGame Pipeline Tool. Výsledok by mal vyzerať
takto:
Presunieme sa do komponenty KomponentaLevel
. Tu pridáme 3
privátne atribúty:
private Kostka kostka; private Vector2 poziceHraciPlochy; private Texture2D spritePolicka;
Prvá je inštancia práve padajúce kocky, druhý je pozícia hracej plochy na pozadí leveli, tretí je sprite pre políčko.
V Initialize()
do kocky dosadíme novú inštanciu triedy
Kostka
. Keďže ešte nemáme generátor kociek a kocka potrebuje v
konstruktoru vzor, z ktorého sa má vytvoriť, vytvoríme si na skúšku jedno
pole sami. Keďže sa v .NET môžeme spoľahnúť, že nové celočíselné
pole má v prvkoch samé nuly, stačí nám nastaviť 4 hodnoty, treba tej
kocky, ako je vyššie pri obrázku k otáčania:
int[,] vzor = new int[4, 4]; vzor[1, 0] = 1; vzor[2, 0] = 1; vzor[0, 1] = 1; vzor[1, 1] = 1; kostka = new Kostka(vzor);
V rovnakej metóde ešte nastavíme premennú
poziceHraciPlochy
:
poziceHraciPlochy = new Vector2(366, 50);
V LoadContent()
načítame sprite nejakého políčka do nami
pripravenej premennej:
spritePolicka = hra.Content.Load<Texture2D>(@"Sprity\Policka\5");
Teraz sa presunieme do Draw()
a za vykreslenie pozadia pridáme
vykreslenie kocky:
kostka.Vykresli(poziceHraciPlochy, hra.spriteBatch, spritePolicka);
Tu vidíte, ako kocka kód čistí, stará sa o svoje vykresľovanie sama, aj keď ho musíme volať. Tak by to malo byť u všetkých herných objektov.
vyskúšame:
Pridáme obsluhu rotácie. Do Update()
doplníme reakciu na
Enter alebo šípku nahor:
if (hra.NovaKlavesa(Keys.Enter) || hra.NovaKlavesa(Keys.Up))
kostka.Orotuj();
Opäť môžete vyskúšať. Až na nešvár vzniknutý rotáciou kocky 3x3 ako pole 4x4 to funguje celkom dobre. Nabudúce, v lekcii Hra tetris v MonoGame: Generátor kociek , si naprogramujeme generátor vzorov kociek a naučíme kocku náhodne generovať sprity políčok.
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é 432x (11.9 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#