5. díl - Rozdělení XNA hry do komponent

C# .NET XNA game studio Robotris Rozdělení XNA hry do komponent

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

V minulém dílu seriálu XNA tutoriálů 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, online 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 seriálu 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 nachystal přímo Microsoft a jsou součásti frameworku XNA. 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 RobotrisContent).

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

Nová XNA komponenta

Visual Studio nám vygenerovalo následující třídu:

/// <summary>
/// This is a game component that implements IUpdateable.
/// </summary>
public class KomponentaMraky : Microsoft.Xna.Framework.GameComponent
{
        public KomponentaMraky(Game game)
                : base(game)
        {
                // TODO: Construct any child components here
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
                // TODO: Add your initialization code here

                base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
                // TODO: Add your update code here

                base.Update(gameTime);
        }
}

Vidíme, že komponenta dědí z Microsoft.Xna­.Framework.Ga­meComponent. Komentář výše nám říká, že komponenta implementuje rozhraní IUpdatable, tedy jako hra má i metodu Update(). Vidíme též metodu Initialize(). V konstruktoru se předává instance naší hry, přesněji jejího předka Game.

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í XNA nabízí ještě jeden typ komponenty a to DrawableGameCom­ponent, na tohoto předka změníme současné GameComponent v hlavičce třídy:

public class KomponentaMraky : Microsoft.Xna.Framework.DrawableGameComponent

VS bohužel neumožňuje přidat rovnou DrawableGameCom­ponent, ale vytvořit ji z GameComponent je hračka.

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() 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ě se na to podíváme.


 

Stáhnout

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

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
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 se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

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


 


Miniatura
Všechny články v sekci
Od nuly k tetrisu v XNA game studio
Miniatura
Následující článek
Hra tetris v XNA: Kostka

 

 

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

Avatar
matesax
Redaktor
Avatar
matesax:

Jestli načítáš v komponentce používej Game - to nikam není třeba ukládat - to už je samotné vlastnost... (nechápu původ proměnné hra - jestli je to jen tak, či co...)

 
Odpovědět 8.12.2012 18:38
Avatar
Merry
Člen
Avatar
Merry:

Mam třídu Hra kde volam všechny komponenty.
Mylsis ze je problem v tom?

private Hra hra;
private Texture2D Zem;
public Zem(Hra hra)
: base(hra)
{

}
Misto té moji Hry mam dat Game jo?

Editováno 8.12.2012 18:49
Odpovědět 8.12.2012 18:46
Jste dobří jen v tom, co vás baví.
Avatar
matesax
Redaktor
Avatar
Odpovídá na Merry
matesax:

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
Merry
Člen
Avatar
Merry:

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 Merry
matesax:

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:

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:

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
Avatar
CallMany Vyhlídal:

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:

Nastavil si třídu LepsiSpriteBatch na public ?

 
Odpovědět 19.8.2015 10:09
Avatar
 
Odpovědět 19.8.2015 10:23
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 33. Zobrazit vše