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:

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ší.

Dospěli jsme do fáze, kdy nám nic nebrání začít tetrisovat .
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 484x (11.85 MB)
Aplikace je včetně zdrojových kódů v jazyce C#