6. díl - Hra JellyBox v MonoGame - Sugar a Menu

C# .NET XNA game studio Hra JellyBox Hra JellyBox v MonoGame - Sugar a Menu

V minulém tutoriálu o MonoGame jsme si do hry dodělali velice pěkné Toxic želé a začali jsme pracovat na Sugar želé, které bude naším posledním ve hře, ale také asi s nejlepším chováním. Dnes si sugar dokončíme a začneme pracovat na herním menu, aby hra dostala určitý řád.

Textury

Dnes budeme potřebovat 2 textury, a to textury pro 2 tlačítka, která budou obsažena v menu (New game, Exit). Textury si nahrajeme do složky Content.

New Game
Exit

Snad mě za tato tlačítka nikdo nepopraví, protože jsem je musel dělat sám :-) Ale pro názornost menu nám postačí.

Sugar Box Finále

Nyní si dokončíme náš sugar box, což bude velice jednoduché, protože to bude pouze o tom vytvořit si pro něj "spawn", přidělit mu texturu a v určitý čas ho generovat. Prní úpravu budeme dělat ve třídě Logic, tak se do ní přepneme a můžeme začít.

Vytvoříme si novou Texture2D, kterou nazveme sugarBox a zde budeme uchovávat texturu pro něj.

private Texture2D sugarBox;

Teď si vytvoříme novou metodu SpawnSugarBox, která nám opět bude generování daného boxu řídit.

public void SpawnSugarBox()
{
        Box box = new SugarBox(sugarBox, Vector2.Zero);
        int x = random.Next(0,
                (int)game.GetScreenSize().X - (int)box.GetSize().X - (((SugarBox)box).Scale * 2));
        ((SugarBox)box).SetPosition((int)x, -100);

        boxes.Add(box);
}

Nastavíme všechny potřebné parametry jako texturu, pozici, Scale a pak přidáme box do našeho listu.

Pozn. pro ty, kteří zapomněli, tak Scale zde při generování pozice X znamená maximální výchilku při kmitání. Jelikož kmitáme jako do + tak do -, tak musíme použít Scale * 2. Tímto jsme zamezili, aby náš objekt vyjížděl z hrací plochy.

Nyní si zde přidáme novou podmínku, která nám bude zajišťovat, aby se při generování nevygenerovali 2 boxy přes sebe, ale také jakmile narazí např. toxic box do normálního boxu, tak ho zničí. Varuji, že je to trochu brutálnější podmínka, ale když se na ni podíváte trochu více, tak ji určitě hned pochopíte.

for (int e = 0; e < boxes.Count; e++)
{
        for (int i = 0; i < boxes.Count; i++)
        {
                if (e != i)
                {
                        if (boxes[e].GetRectangle().Intersects(boxes[i].GetRectangle()))
                        {
                                if (boxes[e].GetType() != typeof(SugarBox) &&
                                        boxes[i].GetType() != typeof(SugarBox))
                                {
                                        if (boxes[e].GetType() == typeof(ToxicBox) &&
                                                boxes[i].GetType() != typeof(ToxicBox))
                                                boxes[i].Delete = true;
                                        else if (boxes[i].GetType() != typeof(ToxicBox))
                                                boxes[i].Delete = true;
                                }
                        }
                }
        }
}

Jakmile v podmínce zjistíme, že do sebe 2 boxy "naráží", tak pak už je to jen o určení jednotlivých typů boxů a jejich následném chování. Takže jakmile se s něčím střetne toxic box, tak ten druhý vymaže, ale u sugar boxu je kolize ignorována a nice se neděje.

Tímto máme třídu Logic hotovou a je třeba doupravit třídu Game1. Jako první si ve třídě Game1 přidáme novou proměnnou typu float sugarBoxSpawnTime, která nám bude zaznamenávat čas pro spawn (vygenerování) sugar boxu.

priva float sugarBoxSpawnTime;

V metodě Initialize ji nastavíme na 0.

sugarBoxSpawnTime = 0;

Teď se musíme mrknout do metody LoadContent a upravit vytváření instance třídy Logic a přidat tam texturu pro náš sugar box.

logic = new Logic(this, boxes, bullets,
Content.Load<Texture2D>("GreenJelly"),
Content.Load<Texture2D>("BrownJelly"),
Content.Load<Texture2D>("Bullet"),
Content.Load<Texture2D>("ToxicJelly"),
Content.Load<Texture2D>("Sugar"));

A finálně se podíváme do metody Update a přidáme si podmínku pro spawn sugar boxu.

sugarBoxSpawnTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (sugarBoxSpawnTime > 22)
{
        sugarBoxSpawnTime = 0;
        logic.SpawnSugarBox();
}

Do proměnné sugarBoxSpawnTime nastavujeme uběhlý čas hry. Pokud dosáhne 22s, tak ho vynulujeme a vygenerujeme nový sugar box.

Málem bych zapoměl, že než to bude kompletní, tak musíme upravit hráčovu kolizi ve třídě Player a zohlednit tam kolizi se sugar boxem a nadefinovat co se má stát. Takže ve třídě Player, v metodě Collision přidáme následujicí podmínku, pod podmínku kdy se obj = ToxicBox.

if (obj is SugarBox)
{
        obj.Delete = true;
        Lives++;
        game.score.ScoreMultiply++;
}

Nyní jsem nadefinovali, že při kolizi se sugar boxem hráč dostane jeden život a zvýší se mu ScoreMultiply.

Menu

V projektu si vytvořme novou složku s názvem Menu. Než se pustíme do menu jako takového, tak by bylo dobré si vytvořit button, který v něm budeme používat. Vytvořme novou třídu jménem Button (ve složce Menu). Třída bude obsahovat následujicí proměnné.

private Texture2D texture;
private Vector2 position;
private bool isActive;

Samozřejmě texturu, pozici, ale také bool isActive, který nám bude říkat, zda je button aktivní.

Vytvoříme si konstruktor třídy Button a nastavíme v něm isActive na false;

public Button(Texture2D texture, Vector2 position)
{
        this.texture = texture;
        this.position = position;
        isActive = false;
}

Nyní si vytvoříme 2 metody (Activate, Deactivate), které budeme používat k aktivování, či deaktivování tlačítka.

public void Activate()
{
        isActive = true;
}

public void Deactivate()
{
        isActive = false;
}

Metody jsou opravdu primitivní, prakticky zatím jde jen o nastavení boolean hodnoty, ale pokud by někdo dělal sofistikovanější menu, tak se mu zde může kód zvětšit.

A jako poslední opět metoda Draw díky které budeme Button vykreslovat.

public void Draw(SpriteBatch spriteBatch)
{
        if (isActive)
                spriteBatch.Draw(texture, position, Color.White);
        else
                spriteBatch.Draw(texture, position, Color.White * 0.5f);
}

V metodě Draw rozlišujeme, zda je button aktivní a pokud není, tak mu dáme 50% průhlednost.

Tímto máme button připravený a můžeme se pustit do tvorby menu. Ale než tak učiníme, tak se přesuneme do třídy Game1 a učiníme malou změnu, aby bylo vše připraveno.

Vytvoříme si zde nový veřejný enum eStavHry, který nám bude říkat v jakém je hra stavu.

public enum eStavHry
{
        Hra,
        Pauza,
        GameOver,
        Menu,
}

Jak vidíte, zvolil jsem zde stavy (Hra, Pauza, GameOver, Menu), toto jsou všechny stavy, kterých může naše hra nabýt.

Nyní si vytvoříme novou proměnnou typu eStavHry, takže to bude enum a nezveme ji stavHry.

public eStavHry stavHry;

V metodě Initialize ji přiřadíme hodnotu a nastaváme ji na Menu. Je zde velice výhodné, že enum vám ukáže všechny jeho možné hodnoty a vy jednu z nich vyberete, takže se nikdy nestane, že by jste např. v podmínce špatně určili název stavu.

stavHry = eStavHry.Menu;

Teď je vše připraveno a vytvoříme si novou třídu jménem MenuScreen ve složce Menu.

Budeme zde potřebovat 3 textury (pro pozadí, tlačítko nová hra a tlačítko konec), dále si vytvoříme List buttonů a rovnou si vytvoříme jeho instanci. Budeme také potřbovat předat skrze konstruktor instanci třídy Game1 Bool hodnoty, které budou hlídat, aby došlo ke zmáčknutí (nahoru, dolů) pouze jednou (press). Pak int index, který nám bude určovat index buttonu, který je práve aktivní. Jako poslední bool NewGame, který nám bude určovat, zde bylo spuštěna nová hra.

private Texture2D background, newGame, exit;
private List<Button> buttons = new List<Button>();
private Game1 game;
private bool upPressed, downPressed;
private int index;
public bool NewGame { get; set; }

Zbytek necháme na příště a to dokončíme Menu, ale i GameOver :-) V tomto stavu jde hra bez problémů spustit, takže si nový typ boxu můžete ihned vyzkoušet. Těším se opět u dalšího tutoriálu o hře v MonoGame.


 

Stáhnout

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

 

  Aktivity (1)

Článek pro vás napsal Jakub Lásko[Saarix]
Avatar
Věnuji se programování v C#, MonoGame a Unity.

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


 


Miniatura
Všechny články v sekci
Hra JellyBox v MonoGame

 

 

Komentáře

Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Chlape, ta kontrola kolize je nějaká divná. Určitě nikdy nepoužívej for cykly místo foreache, když nepotřebuješ index. GetType lze v tomto případě nahradit operátorem is. Co mi není jasné je ten if.. else uvnitř. V else je ta samá podmínka jako v původní if, jen bez pravé části. Kód:

Vymazání se tedy provede vždy, pokud platí:

(boxes[i].GetType() != typeof(ToxicBox)

Trochu jsem to refaktoroval, ale protože nevím co to má přesně dělat, je dost možné, že jsem to udělal blbě, zkus na to ještě kouknout:

foreach (Box box in boxes)
{
        foreach (Box okolni in boxes)
        {
                if ((box != okolni) &&
                        (box.GetRectangle().Intersects(okolni.GetRectangle())) &&
                        (!(box is SugarBox || okolni is SugarBox)))
                {
                        okolni.Delete = (!okolni is ToxicBox);
                }
        }
}
Editováno 22.11.2013 13:48
Odpovědět 22.11.2013 11:39
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
Odpovídá na David Čápka
Jakub Lásko[Saarix]:

Dobrá, díky za radu, je pravda že na zrovna na tento kus zasahování želé do sebe jsem od doby vytvoření vůbec nekoukl, takže na to mrknu.

Odpovědět 22.11.2013 13:44
Časem je vše možné.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Jakub Lásko[Saarix]
David Čápka:

Ono by bylo nejlepší dát to do metody kolize pro každý objekt. Dávat tolik cyků do sebe a ifovat typy vždycky signalizuje, že v návrhu je něco špatně.

Tohle by vypadalo mnohem lépe:

foreach (Box box in boxes)
   box.kontrolaKolize();
Editováno 22.11.2013 13:52
Odpovědět 22.11.2013 13: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í.
Avatar
Odpovídá na David Čápka
Jakub Lásko[Saarix]:

Tak jsem to přepsal prakticky skoro přesně dle kódu co jsi tu poznamenal a funguje to, for jsem občas používal místo foreache, protože jsem u vícekrát zažil, že když jsem přes to dělal kolizi a mazal, tak mě jiný foreach vyhodil error, že došlo ke změně kolekce.

Odpovědět 22.11.2013 13:53
Časem je vše možné.
Avatar
Odpovědět 22.11.2013 13:53
Časem je vše možné.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Jakub Lásko[Saarix]
David Čápka:

Kolekci nesmíš při iteraci nikdy modifikovat. Foreach si to hlídá, for tě to nechá udělat, ale zaděláváš si tím na problémy, jelikož se změní indexy. Další důvod ho nepoužívat.

Odpovědět 22.11.2013 13: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
Odpovídá na David Čápka
Jakub Lásko[Saarix]:

Kdy by tím pádem bylo ideální provádět reálné mazání, abych se vyhnul tomuto erroru?

Odpovědět 22.11.2013 14:23
Časem je vše možné.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Jakub Lásko[Saarix]
David Čápka:

Mazání máš ve své aplikaci udělané přes Vlastnost Delete, předpokládal jsem, že je to kvůli tomu. Trik je v tom smazat položky až po iteraci. To uděláš samozřejmě tak, že položku označíš jako ke smazání. Buď nějkou její vlastností enbo možná čistěji tak, že je dáš do nějakého listu keSmazani. Ten poté proiteruješ a z prvního listu vymažeš tyto položky.

Odpovědět 22.11.2013 14:27
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
Odpovídá na David Čápka
Jakub Lásko[Saarix]:

Aha mazané :-) Ano ta vlastnost Delete je tam přesně kvůli tomu, jen jsem takto přesně nechápal ten postup s iterací.

Odpovědět 22.11.2013 14:35
Časem je vše možné.
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 9 zpráv z 9.