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 5 - Rozdělení MonoGame hry do komponent

V minulé lekci, Zvuky, hudba, klávesnice a myš v MonoGame, jsme si ukázali přehrávání zvuků a práci s klávesnicí a myší.

Dnes si řekneme něco o architektuře hry, ukážeme si, jak ji rozdělit do několika samostatných komponent. Po dnešním dílu budeme mít již vše připraveno k tomu, abychom mohli začít programovat samotnou logiku hry Tetris.

Asi nám je jasné, že hra bude obsahovat několik obrazovek. Těmi bude menu, samotný level, pak třeba obrazovka s rolujícím textem o autorech hry, on-line skóre tabulka a tak podobně. Každá tato obrazovka bude poté složena z dalších součástí, např. padající kostka, hrací plocha nebo ty plující mraky, co jsme si na začátku seriálu naprogramovali.

Kdybychom celou hru nabušili do jedné třídy, bylo by to velmi nepřehledné. K dekompozici hry do více tříd se nám nabízí několik způsobů, všechny si během seriálu ukážeme, abychom viděli jejich výhody i nevýhody. Během kurzu si vytvoříme jednoduchou komponentově-objektovou architekturu.

Herní komponenty

Jedním ze způsobu, jak hru dekomponovat na několik části jsou tzv. herní komponenty. Ty pro nás jsou nachystané přímo jako součásti frameworku MonoGame. Většina tutoriálů o nich úplně mlčí, i když se jedná o poměrně dobrý způsob, jak si ulehčit práci.

Komponenta je co nejvíce samostatná část hry s co nejmenším počtem závislostí. Svou vnitřní strukturou připomíná samotnou hru, tedy třídu Game. Má metody jako LoadContent(), Initialize(), Update() a podobně. Můžeme si ji představit jako hru ve hře. Komponenta by měla být co nejvíce nezávislá, tento předpoklad nám dokonale splňují naše plující mraky. Přesuneme je tedy do komponenty.

Ze hry vymažeme vše nepotřebné z minulých pokusů, tedy hlavně myš a nějaké zbytečné vykreslování. Konkrétně odstraníme proměnné mys, obdelnikRobota a vše s nimi spojené, dále vykreslování písma a reakci na Enter. Naše 2 metody pro práci s klávesnicí a hudbou ponecháme, stejně tak jedoucí mraky a další 2 pozadí. Celý obsah hry ponecháme (teď mám na mysli Content).

K projektu Robotris přidáme novou komponentu, kterou pojmenujeme KomponentaMraky (pravým na projekt Robotris v Solution Exploreru -> Add -> New Item -> Class):

Visual Studio nám vygenerovalo třídu, kterou podědíme od Microsoft.Xna.Framework.GameComponent. V konstruktoru se předává instance naší hry, přesněji jejího předka Game.

Komponenta bude vypadat takto:

public class KomponentaMraky : Microsoft.Xna.Framework.GameComponent
{
    public KomponentaMraky(Game game)
        : base(game)
    {

    }
}

Metody LoadContent(), Initialize() a Update() jsou poděděné a můžeme je přepsat.

Template na vytvoření komponenty v základním balíčku MonoGame bohužel není a tedy musíme komponentu vytvořit takto ručně.

Vykreslovatelná komponenta

GameComponent nedisponuje vykreslováním. I když se nám ve hře i takovéto komponenty mohou hodit, vykreslování u mraků potřebovat budeme. Naštěstí MonoGame nabízí ještě jeden typ komponenty a to DrawableGameComponent, na tohoto předka změníme současné GameComponent v hlavičce třídy:

public class KomponentaMraky : Microsoft.Xna.Framework.DrawableGameComponent

Budeme potřebovat komponentě nějakým způsobem předat společná data hry, to uděláme modifikací konstruktoru tak, aby jeho parametrem byla konkrétní třída naší hry, zaměníme tedy Game na Hra. Dále vytvoříme v komponentě privátní proměnnou hra typu Hra a instanci předanou konstruktorem do ní uložíme. Modifikovaný konstruktor komponenty vypadá takto:

public KomponentaMraky(Hra hra)
    : base(hra)
{
    this.hra = hra;
}

Nyní komponentě přidáme metody LoadContent(), Initialize(), Update() a Draw(). Stačí začít psát public override... a VS nám již nabízí seznam metod, které můžeme třídě dodat pouhým odenterováním:

Přidání metod XNA komponentě - Od nuly k tetrisu v MonoGame

Kostra komponenty je připravena. Protože se jistě bude hodit i do budoucna (k tvorbě dalších komponent), vložím sem její zdrojový kód:

public class KomponentaMraky : Microsoft.Xna.Framework.DrawableGameComponent
{

    private Hra hra;

    public KomponentaMraky(Hra hra)
        : base(hra)
    {
        this.hra = hra;
    }

    public override void Initialize()
    {
        base.Initialize();
    }

    protected override void LoadContent()
    {
        base.LoadContent();
    }

    public override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
    }

    public override void Draw(GameTime gameTime)
    {
        base.Draw(gameTime);
    }
}

Až budete s tímto kódem tvořit další komponentu (což bude za moment), nezapomeňte se jménem třídy změnit i jméno konstruktoru.

Komentáře jsem opět vynechal, jsou na vás. Nyní do komponenty přesuneme ze hry vše, co se týká mraků. Mělo by to být jednoduché, pouze musíme dodat, že k Content v LoadContent() přistoupíme jako k hra.Content, podobně tomu uděláme v Draw() s hra.spriteBatch. Ten jsme neměli veřejný, proto to ve hře napravíme:

public LepsiSpriteBatch spriteBatch;

V Draw() nezapomeneme připsat Begin() a End(), musí být jak v Draw() ve hře, tak v Draw() v komponentě!

Dnes to máme takové hodně zdrojákové, měli byste dospět k něčemu podobnému:

public class KomponentaMraky : Microsoft.Xna.Framework.DrawableGameComponent
{

    private int zmena;
    private int smer;
    private Texture2D mraky;
    private Vector2 pozice;
    private Hra hra;

    public KomponentaMraky(Hra hra)
        : base(hra)
    {
        this.hra = hra;
    }

    public override void Initialize()
    {
        pozice = new Vector2(0, 0);
        zmena = 0;
        smer = 1;

        base.Initialize();
    }

    protected override void LoadContent()
    {
        mraky = hra.Content.Load<Texture2D>(@"Sprity\spr_mraky");

        base.LoadContent();
    }

    public override void Update(GameTime gameTime)
    {
        // posun mraků doleva
        pozice.X--;
        // návrat na startovní pozici po vyjetí z obrazovky
        if (pozice.X < -(mraky.Width))
            pozice.X = 0;

        // změna barvy mraků dle směru
        zmena += smer;
        if (zmena >= 96)
            smer = -1;
        if (zmena <= 0)
            smer = 1;

        base.Update(gameTime);
    }

    public override void Draw(GameTime gameTime)
    {
        hra.spriteBatch.Begin();
        Color barva = new Color(128 + zmena, 255 - zmena, 128 + zmena);
        for (int i = 0; i < 6; i++)
            hra.spriteBatch.Draw(mraky, new Vector2(pozice.X + i * mraky.Width, 0), barva * 0.8f);
        hra.spriteBatch.End();

        base.Draw(gameTime);
    }

}

Zprovoznění necháme na konec. Hře přidáme další vykreslovatelnou komponentu, pojmenujeme ji KomponentaLevel. V ní se bude odehrávat samotný gameplay. Ten my zatím nemáme, ale vykreslíme v ní alespoň pozadí levelu, to je to s robotem. Určitě to zvládnete, postup je obdobný, jako u mraků, přesunout vykreslení pozadí ze hry do této komponenty.

Výsledek bude vypadat asi takto:

public class KomponentaLevel : Microsoft.Xna.Framework.DrawableGameComponent
{

    private Hra hra;
    private Texture2D pozadi;

    public KomponentaLevel(Hra hra)
        : base(hra)
    {
        this.hra = hra;
    }

    public override void Initialize()
    {
        base.Initialize();
    }

    protected override void LoadContent()
    {
        pozadi = hra.Content.Load<Texture2D>(@"Sprity\pozadi_level");
        base.LoadContent();
    }

    public override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
    }

    public override void Draw(GameTime gameTime)
    {
        hra.spriteBatch.Begin();
        hra.spriteBatch.Draw(pozadi, new Vector2(0, 0), Color.White);
        hra.spriteBatch.End();

        base.Draw(gameTime);
    }

}

Připojení komponent ke hře

Ve hře nám zbylo v Draw() jen vykreslení pozadí kostek a v Update() obsluha stavu klávesnice. Přesuneme se do Initialize() a na začátku vytvoříme naše komponenty, kterým předáme instanci hry v konstruktoru:

KomponentaMraky mraky = new KomponentaMraky(this);
KomponentaLevel level = new KomponentaLevel(this);

Nyní přijde magie! Hra má na sobě vlastnost Components. To je kolekce (přesněji List) komponent, ze kterých se skládá. Do listu přidáme komponenty v tom pořadí, v jakém chceme, aby se obnovovaly a vykreslovaly:

Components.Add(mraky);
Components.Add(level);

Spustíme a vidíme, že hra funguje stejně dobře, jako předtím. Komponenty se totiž obsluhují úplně samy a my se o ně nemusíme starat. Kód hry je přehledný a velmi krátký. Komponenty obsahují jen to, co se jich týká. Vše je možné díky volání base.Initialize(), base.Update() atd. v metodách hry, což má v předkovi za následek volání metod všech aktivních komponent hry.

Ještě malé, kosmetické úpravy na závěr. Soubor Game1.cs přejmenujeme na Hra.cs. Protože komponent bude více, vytvoříme na ně složku (Pravým na Robotris -> Add -> New Folder), kterou pojmenujeme Komponenty/. Naše 2 komponenty do ní jednoduše přesuneme myší.

Solution explorer XNA hry Robotris - Od nuly k tetrisu v MonoGame

Dospěli jsme do fáze, kdy nám nic nebrání začít tetrisovat :P.

Hned příště, v lekci Hra tetris v MonoGame: Kostka, se na to podíváme.


 

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

 

Předchozí článek
Zvuky, hudba, klávesnice a myš v MonoGame
Všechny články v sekci
Od nuly k tetrisu v MonoGame
Přeskočit článek
(nedoporučujeme)
Hra tetris v MonoGame: Kostka
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
13 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