Java týden Body zdarma
Využij podzimních slev a získej od nás až 40 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 5 - Rozdělení MonoGame hry do komponent

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

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.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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ě

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

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.


 

Stáhnout

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

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
4 hlasů
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 sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Předchozí článek
Zvuky, hudba, klávesnice a myš v MonoGame
Všechny články v sekci
Od nuly k tetrisu v MonoGame
Miniatura
Následující článek
Hra tetris v MonoGame: Kostka
Aktivity (8)

 

 

Komentáře
Zobrazit starší komentáře (25)

Avatar
matesax
Redaktor
Avatar
matesax:8.12.2012 19:02

Místo hra má být hlavně game... (To je hlavní třída - ty z ní dědíš.) Ne - to s tím nesouvisí - nesnáším Češtinu v kódu - a mám na mysli to, že komponentka má vlastnost Game - od ní se dostaneš k nadřazené třídě Game - takže v komponentce načítáš Game.xxx a v hlavní třídě Game rovnou - popř. s this, tak nechápu, kde bereš tu proměnnou hra... Ale jak jsem psal - LoadContent je volán jednou - z konstruktoru - viz. object browser - takže tam načítáš všechny obrázky - najednou - ne uprostřed hry...

Editováno 8.12.2012 19:03
 
Odpovědět 8.12.2012 19:02
Avatar
Mini
Člen
Avatar
Mini:8.12.2012 19:12

Takže mam všechny textury načíct hned v té mé tříde Game (kde volam komponenty) a potom už jen volat načtenou texturu? Chapu to dobře?

Odpovědět 8.12.2012 19:12
Jste dobří jen v tom, co vás baví.
Avatar
matesax
Redaktor
Avatar
Odpovídá na Mini
matesax:8.12.2012 19:26

Je jedno jestli to načteš z komponentky, či přímo z Game - tak jako tak se to provádí v Game.Load. A ano - vše načítáš v load - pak to jen voláš. Proto když chceš udělat pás obrázků, mezi kterýma chceš měnit, udělej pole - viz. má ukázka...

 
Odpovědět 8.12.2012 19:26
Avatar
United121CZ
Člen
Avatar
United121CZ:26.8.2013 22:48

Nedaří se mi jakýmkoliv způsobem zavolat "spritebatch"(v Draw) v komponentě zkoušel jsem snad vše ( game. , Game. , Hra. , hra. )nic z toho nemá v nabídce spriteBatch a to jsem dělal vše podle toho co tady je napsaný ... nevíte kde může být problém ?

 
Odpovědět 26.8.2013 22:48
Avatar
abushrek
Člen
Avatar
abushrek:11.6.2015 21:08

Zdarec chtěl bych se zeptat, kde patří:
Components.Ad­d(mraky);
Components.Ad­d(level);
Zkoušel jsem to dat do do všech metod, ale padá mi to na vyjímku ArgumentException.

Edit: Tak už mi to jde moje blbá chyba...(chybama se člověk učí) :)

Editováno 11.6.2015 21:09
 
Odpovědět 11.6.2015 21:08
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
CallMany Vyhlídal:18.8.2015 22:43

Ahoj potřebuju poradit s chybou od Visual studia
**Inconsistent accessibility: field type 'Robotris.Lep­siSpriteBatch' is less accessible than field 'Robotris.Hra­.spriteBatch'
**
jde o to , že všechny instance třídy Hra jsou v komponentách hry nastaveny na public ale stejně to nejde

 
Odpovědět 18.8.2015 22:43
Avatar
David Hanina
Člen
Avatar
Odpovídá na CallMany Vyhlídal
David Hanina:19.8.2015 10:09

Nastavil si třídu LepsiSpriteBatch na public ?

 
Odpovědět 19.8.2015 10:09
Avatar
Odpovídá na David Hanina
CallMany Vyhlídal:19.8.2015 10:23

Jo děkuju neměl jsem :)

 
Odpovědět 19.8.2015 10:23
Avatar
Print-Hamlet
Člen
Avatar
Print-Hamlet:12. ledna 19:39

Ahoj, mám problém s vytvořením metody za pomocí public override. Kód:
public Game game;
public KomponentaMra­ky(Game game)
: base(game)
{
this.game = game;
}

 
Odpovědět 12. ledna 19:39
Avatar
Print-Hamlet
Člen
Avatar
Odpovídá na Print-Hamlet
Print-Hamlet:12. ledna 22:53

problém vyřešen :)....

 
Odpovědět 12. ledna 22:53
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 35. Zobrazit vše