7. díl - Hra tetris v XNA: Generátor kostek

C# .NET XNA game studio Robotris Hra tetris v XNA: Generátor kostek

V minulém dílu seriálu XNA tutoriálů 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:

Připojení nového souboru k projektu

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 seriálu 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:

Soubory ve Visual Studio ve složce s aplikací

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.cs, 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 usingy 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;

Radka je poslední načtená řádka textového souboru. 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++;
}

While cyklus 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:

Ukázková hra Tetris v XNA

 

Stáhnout

Staženo 277x (5.78 MB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (3 hlasů) :
55555


 


Miniatura
Předchozí článek
Hra tetris v XNA: Kostka
Miniatura
Všechny články v sekci
Od nuly k tetrisu v XNA game studio
Miniatura
Následující článek
Hra tetris v XNA: Hrací plocha

 

 

Komentáře

Avatar
matesax
Redaktor
Avatar
matesax:

Nechci moc rýpat (protože to je dobrý článek), jen mi přijde nahodnyGenerator jako zavádějící pojem - přesnější by bylo - generatorNahody. :)

Jo a prázdná řdka by se dala zvládnout bez pomocné proměnné:

String.IsNullO­rEmpty

Editováno 3.10.2012 6:44
 
Odpovědět 3.10.2012 6:41
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na matesax
David Čápka:

Tak jsem to dříve měl, ale padalo to když byla na té řádce třeba mezera nebo tab, tak jsem to raději udělal takhle :)

Jo, není to nejpřesnější pojmenování, ale random jsem tam psát nechtěl, generátor náhodných čísel je moc dlouhé, generátor náhody by šlo, ale už to asi měnit nebudu.

Odpovědět 3.10.2012 7:32
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Kit
Redaktor
Avatar
Odpovídá na David Čápka
Kit:

Jestli to má generovat číslo 1-6, nazval bych to Házení kostkou.

Odpovědět 3.10.2012 9:45
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Kit
David Čápka:

Nene, generuje to jednu z několika kostek Tetrisu, v základní verzi jich je 7, rozšířená jich má 12. Hrací kostku mám také, ale v jiném tutoriálu :)

Odpovědět 3.10.2012 10:10
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
matesax
Redaktor
Avatar
Odpovídá na David Čápka
matesax:

String.IsNullOr­WhiteSpace

 
Odpovědět 3.10.2012 11:35
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovědět 3.10.2012 11:42
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Kit
Redaktor
Avatar
Odpovídá na David Čápka
Kit:

No jo, to bych si ten článek nejdřív musel přečíst. Když je to o C#, tak to jen přelétnu pohledem.

Odpovědět 3.10.2012 11:47
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Kit
David Čápka:

C# jsme budovali společně s Javou, zatím co o něj je velký zájem, o Javu moc ne. Díval jsem se i na další servery, např. na počty příspěvků v diskuzích a jsou na tom podobně. Chceme se samozřejmě věnovat tomu, o co je zájem, když to má nějakou úroveň a je to zajímavé, což .NET určitě je. Přečíst si to můžeš stejně, i když to na Linuxu nespustíš, kód je srozumitelný a určitě bych stál o tvé názory na postupy, které zde používám.

Vlastně jsem se tě chtěl zeptat na něco k vektorům ve hrách, již jsi zde o tom mluvil, můžeš upřesnit, co se díky tomu dá všechno dělat?

Odpovědět 3.10.2012 11:54
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Kit
Redaktor
Avatar
Odpovídá na David Čápka
Kit:

Hlavní výhodu vektorů vidím v možnosti přetížení operátorů. Programování je pak pohodlnější a přehlednější. Pokud máš vektor pozice a vektor směru, pro získání další pozice stačí součet. Otočení kolem osy uděláš vynásobením jednotkovým vektorem. Když ještě zavedeš vektor zrychlení, sčítáním uděláš změnu rychlosti. Vzdálenost určíš jako absolutní hodnotu rozdílu vektorů.

Je dobré, pokud je práce s vektory nebo alespoň s komplexními čísly implementována přímo na úrovni jazyka. Umí to např. Fortran a Octave.

Něco z C# spustím, pokud to zvládne Mono. Ale vyhýbám se tomu.

Odpovědět 3.10.2012 12:38
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Kit
David Čápka:

Díky, určitě se k tomuto postu vrátím.

David Jančík [sczdavos] Mono zkoušel a byl velmi zklamaný. Počítám, že v tom Microsoft nejede a bastlí to nějaká komunita, s přenosem kódu tam jsou prý velké problémy.

Odpovědět 3.10.2012 12:52
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 10.