Lekce 7 - Hra tetris v MonoGame: Generátor kostek
V minulé lekci, Hra tetris v MonoGame: Kostka, jsme si naprogramovali kostku do hry Tetris.
Dnes si vytvoříme generátor náhodných kostek, přesněji vzorů kostek.
Soubor se vzory
Vzory kostek budeme ukládat do textového souboru. Je to mnohem pohodlnější a čistší řešení, než bušit je do zdrojového kódu, kam patří opravdu jen herní logika. Všechna takováto data, mapy, delší texty a podobně by měla být uložena externě. Soubor by měl být v reálu opatřen nějakým hashem nebo kompilován do exe souboru hry, aby ho hráč nemohl editovat a dát si takové kostky, se kterými by nahrál vysoké skóre. My si pro jednoduchost soubor pojmenujeme tak, aby nikoho nenapadlo v něm kostky hledat
K projektu Robotris
připojíme nový textový soubor
(přidáním nového itemu, jak to již známe, viz obrázek) se jménem
netfx.dll
:
Do souboru vložíme vždy na 4 řádky kostku a 5. řádek necháme prázdný. Počítači je to sice jedno, ale my se v souboru lépe vyznáme. Do souboru vložte následující data:
1000 1110 0000 0000 0010 1110 0000 0000 0000 0110 0110 0000 0000 1111 0000 0000 0100 1110 0000 0000 0000 1100 0110 0000 0000 0110 1100 0000 0000 0100 0000 0000 1010 0100 1010 0000 0100 1110 0100 0000 1110 0100 0100 0000 0000 0110 0000 0000
Prvních 7 kostek je z klasického tetrisu, přesněji tetrisu verze A, který tu vyrábíme. Další jsou nové kostky, které v souboru necháme, ale v tomto kurzu je nebudeme používat. Vy si hru potom můžete vylepšit a dát je třeba do dalšího módu nebo je nechat padat od určitého levelu.
Poslední, co je třeba udělat, je zaškrtnout soubor netfx.dll
v SolutionExploreru a poté v okně Properties nastavit Copy to Output
Directory na Copy always:
Tím docílíme toho, že Visual Studio bude tento soubor přidávat do složky se zkompilovanou hrou a my ho jednoduše budeme moci načíst. Pro generátor je tedy vše připraveno.
Generátor kostek
Generátor kostek si načte vzory kostek z textového souboru do připravené kolekce vzorů. Ten poté vždy jeden náhodně vybere a na jeho základě vygeneruje novou kostku.
Přidáme novou třídu GeneratorKostek
, modifikátor přístupu
opět upravíme na public
:
public class GeneratorKostek
Přidáme několik atributů. Bude to kolekce vzorů kostek
vzory
, což budou nám známe pole intů 4x4. Atribut
soubor
udává cestu k souboru se vzory. Posledním atributem je
instance generátoru náhodných čísel.
private List<int[,]> vzory; private string soubor; private Random nahodnyGenerator;
Atributy následně inicializujeme v konstruktoru, který bude brát parametr s cestou k souboru se vzory:
public GeneratorKostek(string soubor) { vzory = new List<int[,]>(); nahodnyGenerator = new Random(); this.soubor = soubor; }
Parsování souboru
Přejděme k parsování souboru. Připravme si metodu
NactiKostky()
. V ní si otevřeme textový soubor ke čtení:
public void NactiKostky() { using (StreamReader sr = new StreamReader(soubor)) { } }
Nezapomeňte do using
(teď myslím příkazy using
na úplném začátku zdrojáku) přidat:
using System.IO;
V bloku using
si před čtením připravíme několik
proměnných:
string radka; int[,] vzor = new int[4, 4]; int j = 0; int cisloRadky = 1;
Proměnná radka
je poslední načtená řádka textového
souboru. Proměnná vzor
je nám známé pole, do kterého budeme
jednotlivé vzory ze souboru ukládat. j
je svislá souřadnice ve
vzoru, dále bude i i
. cisloRadky
udává na které
řádce souboru se nacházíme.
Započněme načítání a za inicializaci proměnných vložme následující kód:
while ((radka = sr.ReadLine()) != null) { // každou pátou řádku vynecháme, jinak pokračujeme if ((cisloRadky % 5) != 0) { } cisloRadky++; }
Cyklus while
načítá novou řádku tak dlouho, dokud není na
konci souboru. Další kód se provede jen tehdy, pokud není číslo řádky
dělitelné pěti, protože každá 5. řádka je prázdná.
Pokračujeme v bloku podmínky a načteme daný řádek do příslušného řádku ve vzoru:
for (int i = 0; i < 4; i++) vzor[i, j] = int.Parse(radka[i].ToString());
Všechny 4 znaky na řádku převedeme na string
a následně
naparsujeme jako int
. Získanou hodnotu uložíme na aktuální
souřadnice ve vzoru.
Nyní nám zbývá jen obsluha proměnné j
, která nás
posouvá ve vzoru o řádek dolů. Jakmile však přesáhne hodnotu 3 (tedy 4.,
poslední řádek), musíme ji vynulovat, vzor přidat do seznamu vzorů a
vymazat:
if (j > 3) { vzory.Add(vzor); j = 0; vzor = new int[4, 4]; }
Protože to bylo trochu náročnější, zde je kompletní kód metody:
public void NactiKostky() { using (StreamReader sr = new StreamReader(soubor)) { // příprava proměnných string radka; int[,] vzor = new int[4, 4]; int j = 0; int cisloRadky = 1; // přečtení všech řádek textového souboru while ((radka = sr.ReadLine()) != null) { // každou pátou řádku vynecháme, jinak pokračujeme if ((cisloRadky % 5) != 0) { // načtení čísel z řádky do vzoru for (int i = 0; i < 4; i++) vzor[i, j] = int.Parse(radka[i].ToString()); j++; // načetli jsme poslední řádku? if (j > 3) { // vložíme vzor vzory.Add(vzor); // a resetujeme proměnné j = 0; vzor = new int[4, 4]; } } cisloRadky++; } } }
Generování náhodné kostky
Generování náhodné kostky je již primitivní, pouze si vybereme nějaký
vzor a vrátíme kostku, vytvořenou na jeho základu. Přidejme metodu
Generuj()
, která bere jako parametr počet vzorů, ze kterých se
má generovat. Takto můžeme s parametrem 7 generovat jednoduché kostky, s
parametrem 12 i ty složité a dále si můžete přidat třeba ještě nějaké
bonusové.
public Kostka Generuj(int pocet) { int index = nahodnyGenerator.Next(0, pocet); return new Kostka(vzory[index]); }
Přesuňme se do KomponentaLevel
. Třídě přidáme atribut s
instancí našeho generátoru kostek:
private GeneratorKostek generatorKostek;
Přesuneme se do Initialize()
, kde odstraníme vytvoření
testovacího vzoru i vytvoření kostky. Dále zde vytvoříme generátor kostek
a rovnou i vzory načteme:
generatorKostek = new GeneratorKostek("netfx.dll"); generatorKostek.NactiKostky();
Za načtení kostek přidáme vytvoření kostky pomocí generátoru:
kostka = generatorKostek.Generuj(7);
Hru několikrát spustíme, kostka bude vždy náhodná. Opět je vidět problém s centrováním kostek, ale to zatím nebudeme řešit.
Náhodné sprity políček
Všechna políčka kostky jsou stejná a to pro ně máme 15 různých
spritů. Budeme chtít, aby si kostka při vytvoření náhodně vygenerovala
sprity pro svá políčka, tedy aby v jednotlivých políčkách nebyly jen
hodnoty 0
-1
, ale 0
-15
. Vzor
kostky, který jí posíláme do konstruktoru, bude vždy obsahovat pouze
hodnoty 0
-1
. Kostka si ale pak sama vygeneruje tyto
indexy pro sprity.
Přesuneme se zpět do třídy Kostka
. Přidáme atribut s
instancí generátoru náhodných čísel:
private Random nahodnyGenerator;
V konstruktoru ji inicializujeme:
nahodnyGenerator = new Random();
Přidáme novou metodu GenerujSprity()
, která najde jedničky v
políčkách a zamění je za náhodné číslo mezi 1
-
15
(matoucí může být, že metodě Next()
zadáváme parametry 1
a 16
, ale horní mez již do
rozmezí nepatří):
private void GenerujSprity() { // výběr políček pro kostku for (int j = 0; j < 4; j++) for (int i = 0; i < 4; i++) if (Policka[i, j] > 0) Policka[i, j] = nahodnyGenerator.Next(1, 16); }
Metodu zavoláme v konstruktoru po zkopírování políček a vytvoření náhodného generátoru:
GenerujSprity();
Nyní upravíme metodu Vykresli()
tak, aby místo jednoho spritu
brala celé pole textur a poté je vykreslovala podle indexu v políčku:
public void Vykresli(Vector2 okraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity) { 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); }
Kostka je pro dnešek hotová. Ještě musíme změnit její obsluhu, vraťme
se do KomponentaLevel
.
Deklaraci atributu spritePolicka
změníme na deklaraci pole,
atribut též přejmenujeme:
private Texture2D[] sprityPolicek;
Přesuneme se do LoadContent()
a místo původního načtení
jednoho spritů jich načteme celé pole pomocí for
cyklu. Pole je
nejprve nutné inicilizovat.
// Načtení spritů kostek sprityPolicek = new Texture2D[15]; for (int i = 0; i < 15; i++) sprityPolicek[i] = hra.Content.Load<Texture2D>("Sprity\\Policka\\" + (i + 1).ToString());
Poslední úprava spočívá v přejmenování proměnné u metody
Draw()
:
kostka.Vykresli(poziceHraciPlochy, hra.spriteBatch, sprityPolicek);
Výsledek:
V příští lekci, Hra tetris v MonoGame: Hrací plocha, na nás čeká hrací plocha.
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 306x (11.89 MB)
Aplikace je včetně zdrojových kódů v jazyce C#