Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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:

Přidání nového souboru do projektu - Od nuly k tetrisu v MonoGame

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:

Soubory ve Visual Studio ve složce s aplikací - Od nuly k tetrisu v MonoGame

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:

Ukázková hra Tetris v XNA - Od nuly k tetrisu v MonoGame

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 304x (11.89 MB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

Předchozí článek
Hra tetris v MonoGame: Kostka
Všechny články v sekci
Od nuly k tetrisu v MonoGame
Přeskočit článek
(nedoporučujeme)
Hra tetris v MonoGame: Hrací plocha
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
9 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity