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 11 - Hra tetris v MonoGame: Vychytávky v levelu

V minulé lekci, Hra Tetris v MonoGame: Bodování a dokončení levelu, jsme dokončili level Tetrisu a zprovoznili bodování a pauzu.

Dnešní tutoriál je poslední, kdy se budeme levelu věnovat. Vylepšíme rotaci kostky a přidáme do hry prvek zaměřovač.

Vylepšená rotace kostky

Jak se kostka rotuje určitě není nejpřirozenější. V pravém Tetrisu jsou kostky ručně nastavené v několika polohách, některé mají třeba jen 2 (kostka I), některé jen jednu (kostka O). Tento způsob ručního zadávání je ale velmi neprogramátorský a nic bychom se tím nenaučili. Náš způsob zatím rotuje každou kostku jako čtvercovou matici o hraně 4. Problém je v tom, že většina kostek má hranu jen 3, některé dokonce 2. Metodu rotace tedy upravíme tak, aby rotovala dle rozměrů kostky, přesněji dle její hrany.

Nejprve si upravme naše kostky v textovém souboru tak, aby byly vždy umístěny v levém horním rohu. Samozřejmě musíme brát kostku jako čtverec a podle delší hrany určit její polohu (to se týká zejména kostky I). Soubor netfx.dll tedy přepíšeme na následující 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

Otevřeme si třídu Kostka a přidejme ji veřejný atribut hrana:

public int hrana;

Třídě přidejme privátní metodu ZjistiHranu(), ve které budeme nejprve předpokládat, že má kostka hranu 1, tedy nejnižší možnou, prázdnou kostku ve hře mít jistě nebudeme.

private void ZjistiHranu()
{
    hrana = 1;
}

Hranu zjistíme tak, že v metodě projedeme všechna políčka. Pokud narazíme na políčko s nenulovou hodnotou, zeptáme se, jestli je od levého horního rohu dále, než je současná známá hrana. Pokud ano, do hrany vždy uložíme tu větší souřadnici 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;
        }
    }

Metodu zavoláme v konstruktoru po generování spritů:

ZjistiHranu();

Hranu máme uloženou, nyní je hračka upravit metodu pro rotaci (budeme pracovat pouze s částí pole velikosti 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 velikost kostky bude reagovat i metoda PosadKostku() v třídě HraciPlocha:

public Vector2 PosadKostku(Kostka kostka)
{
    return new Vector2((sirka / 2) - (kostka.hrana / 2), 0);
}

Hru nyní vyzkoušíme, rotace je mnohem preciznější.

Zaměřovač

Některé verze Tetrisu ukazují přímo místo, kam kostka spadne, pokud stiskneme šipku dolů. Tento prvek jsem nazval zaměřovačem, ten do hry přidáme.

Získání pozice

Přejděme do HraciPlocha.cs, kam přidáme metodu SpocitejSouradniceZamerovace(). Jako parametr bude brát kostku a vracet bude pozici jako Vector2. To budou souřadnice místa, kam by kostka spadla, když bychom stiskli šipku dolů. Zjištění těchto souřadnic již tedy umíme, stačí jednoduše posouvat pozici kostky dolů tak dlouho, dokud nebude kolidovat. Pozici o 1 výše poté vrátíme.

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);
}

Protože zjištění této pozice je ve hře nyní 2x, což není hezký programátorský návyk, nahradíme logiku v obsluze šipky dolů v metodě Update() v KomponentaLevel voláním této funkce. Kus kódu s obsluhou bude vypadat takto:

if (hra.NovaKlavesa(Keys.Down))
{
    kostka.pozice = hraciPlocha.SpocitejSouradniceZamerovace(kostka);
    casOdPoslednihoPadu += casMeziPady;
}

Zůstaňme v KomponentaLevel a založme si privátní proměnnou s pozicí zaměřovače:

private Vector2 poziceZamerovace;

Nastavení této proměnné se bude provádět v Update(), ve větvi se stavem Hra:

poziceZamerovace = hraciPlocha.SpocitejSouradniceZamerovace(kostka);

Vykreslení

Pozici bychom měli, nyní musíme zaměřovač na toto místo vykreslit. Zaměřovač bude vypadat stejně jako kostka. Nebudeme pro něj tvořit další objekt (i když by to jistě také šlo), ale jen naučíme kostku vykreslovat se na jiné souřadnice a průhledně.

Přejdeme do Kostka.cs a upravíme metodu Vykresli(). Označíme ji jako privátní a před její název vložíme _ (podtržítko). Přidáme ji další 2 parametry navíc. Parametr pozice typu float bude vykreslovací pozice kostky. Takto můžeme kostku vykreslovat jinde, než opravdu je. Něco podobného jsme již dělali u metody Kolize(). Parametr alfa bude alfaprůhlednost kostky. V kódu jen barvu vynásobíme alfa, C# sám pochopí, že myslíme pozici z parametru metody a ne atribut třídy.

Kód metody vypadá nyní 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);
}

Přidat metody k vykreslení kostky a zaměřovače bude nyní hračka. Metoda Vykresli() jednoduše zavolá _Vykresli(), kde bude jako pozice atribut pozice a alfa bude 1:

public void Vykresli(Vector2 levyOkraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity)
{
    _Vykresli(levyOkraj, spriteBatch, sprity, pozice, 1);
}

Metoda VykresliZamerovac() využije všechny parametry metody _Vykresli():

public void VykresliZamerovac(Vector2 levyOkraj, LepsiSpriteBatch spriteBatch, Texture2D[] sprity, Vector2 pozice, float alfa)
{
    _Vykresli(levyOkraj, spriteBatch, sprity, pozice, alfa);
}

Pokud vás nyní napadá, že by nám stačily metody jen 2 a to tak, že by Vykresli() volala VykresliZamerovac(), máte pravdu. Z hlediska návrhu mi to však nepřijde logické.

Přesuneme se do KomponentaLevel, do metody Draw(). Zde za vykreslení kostky a příští kostky vložíme vykreslení zaměřovače:

kostka.VykresliZamerovac(poziceHraciPlochy, hra.spriteBatch, sprityPolicek, poziceZamerovace, 0.3f);

A spustíme:

Ukázkový Tetris v XNA Game Studio - Od nuly k tetrisu v MonoGame

Hra se hraje mnohem lépe.

Pulzování

Pojďme si na konec ukázat ještě jeden efekt, necháme alfaprůhlednost zaměřovače pulzovat, jako jsme to již dělali s barvou mraků. Opět budeme ukládat směr pulzování do proměnné typu int jako hodnoty 1 a -1. Samotnou hodnotu průhlednosti poté do proměnné typu float. Přidáme k třídě tyto 2 atributy:

private int zamerovacSmer;
private float zamerovacAlfa;

V Initialize() nastavíme výchozí hodnoty:

zamerovacAlfa = 0.0f;
zamerovacSmer = 1;

Do Update() do větve pro stav Hra vložíme obsluhu pulzování:

zamerovacAlfa += 0.02f * zamerovacSmer;
if (zamerovacAlfa > 0.5)
    zamerovacSmer = -1;
if (zamerovacAlfa < 0.2)
    zamerovacSmer = 1;

Hodnota zamerovacAlfa nyní pulzuje mezi 0.2 a 0.5.

Přesuňte se do Draw() a pozměňte vykreslení zaměřovače a příští kostky takto:

pristiKostka.Vykresli(new Vector2(930, 200 + (40 * zamerovacAlfa)), hra.spriteBatch, sprityPolicek);
kostka.VykresliZamerovac(poziceHraciPlochy, hra.spriteBatch, sprityPolicek, poziceZamerovace, zamerovacAlfa);

Spusťte a vychutnejte si hru, protože je hotová :) Pulzování jsme využili i pro efekt levitace příští kostky.

Příště, v lekci MonoGame: Správa herních obrazovek, se podíváme na správu herních obrazovek a připravíme se na tvorbu herního menu.


 

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

 

Předchozí článek
Hra Tetris v MonoGame: Bodování a dokončení levelu
Všechny články v sekci
Od nuly k tetrisu v MonoGame
Přeskočit článek
(nedoporučujeme)
MonoGame: Správa herních obrazovek
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
2 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