Lekce 12 - MonoGame: Správa herních obrazovek
V minulé lekci, Hra tetris v MonoGame: Vychytávky v levelu, jsme do levelu s Tetrisem přidali vylepšenou rotaci a zaměřovač. Tím pro nás Tetrisování končí a budeme hru považovat za hotovou. Vy si tam samozřejmě můžete dodat další power-upy, herní módy a podobně.
Dnes se zaměříme na herní obrazovky, díky kterým budeme poté schopni vytvořit např. herní menu.
Herní obrazovka
Víme, že MonoGame není engine, ale framework. Proto MonoGame samotná kromě komponent neposkytuje žádný způsob, jak se vypořádat s přepínáním herních obrazovek. Herní obrazovkou myslím nějakou samostatnou část hry, třeba level, menu, skóre tabulku, obrazovku autoři a podobně. Hlavní menu bude mít určitě jinou logiku, než level s Tetrisem. Navíc je třeba docílit toho, abychom herní obrazovky mohli přepínat a ukládat jejich stav.
Možností je samozřejmě mnoho. Úplně ta nejhloupější je napsat celou hru do jednoho souboru a udělat mnoho stavů (stav pro menu, stav pro hru a tak). Výsledný soubor by ale asi nevypadal úplně hezky a když umíme používat herní komponenty, určitě je využijeme. Občas jsem viděl způsob, kdy je co obrazovka, to komponenta. Komponenty se poté mezi sebou přepínají (např. z komponenty Menu do komponenty Level). Pro malé hry to může být dostačující, ale pro větší projekty již nikoli. Problém je v tom, že jsme omezeni na 1 komponentu pro každou obrazovku, nemohli bychom mít např. v tetrisu oddělené Mraky a Level, případně ještě nějaké další součásti, které by se ve složitější hře našly.
Řešení, které si zde ukážeme, definuje herní obrazovku jako soubor komponent. Do hry jsou vloženy všechny komponenty a při přepnutí obrazovky se zakáží a povolí se jen ty, které používá konkrétní obrazovka.
Přidejme si k projektu třídu HerniObrazovka
. Její instance
budou reprezentovat jednotlivé herní obrazovky. Třídu si nastavte jako
public
. Bude mít 2 privátní atributy, jeden bude kolekce
komponent, které obrazovka obsahuje. Druhý instance Hry
:
private List<GameComponent> komponenty; private Hra hra;
Kvůli použití typu GameComponent
dodáme nad třídu
using
:
using Microsoft.Xna.Framework;
Přidáme veřejnou metodu PridejKomponentu()
, která komponentu
předanou v parametru vloží do interní kolekce komponenty
a
zároveň i do kolekce Components
hry. Do Components
se musí komponenta vložit pouze tehdy, když tam již není. Některé
obrazovky totiž využívají stejné komponenty. Metoda vypadá takto:
public void PridejKomponentu(GameComponent komponenta) { komponenty.Add(komponenta); if (!hra.Components.Contains(komponenta)) hra.Components.Add(komponenta); }
Díky tomu, že je list privátní, všechna přidání proběhnou přes tuto
metodu a můžeme si být jistí, že budou dané komponenty i v
Components
hry.
V konstruktoru třídy si předáme v parametru instanci hry tak, jak jsme
zvyklí z komponent a herních objektů. Dále pomocí klíčového slova
params
umožníme vložit několik parametrů typu
GameComponent
. Ty proiterujeme a přidáme pomocí naší
metody.
public HerniObrazovka(Hra hra, params GameComponent[] komponenty) { this.hra = hra; this.komponenty = new List<GameComponent>(); foreach (GameComponent komponenta in komponenty) { PridejKomponentu(komponenta); } }
Jako poslední přidáme metodu VratKomponenty()
, která vrátí
komponenty v obrazovce jako pole:
public GameComponent[] VratKomponenty() { return komponenty.ToArray(); }
Pro úplnost si můžete napsat metodu pro vymazání komponenty, v nějaké složitější hře by se mohly za běhu přidávat a odebírat, ale pro naše účely to není nutné.
Menu
Abychom měli na čem testovat, přidáme si ke hře komponentu
KomponentaMenu
(do složky Komponenty/
, ale
namespace ponechte pouze na Robotris
). Bude se
opět jednat o DrawableComponent
, jak ji přidat jsme si ukázali v
tutoriálu Rozdělení
MonoGame hry do komponent. Pro jistotu si ukážeme i kód třídy:
public class KomponentaMenu : Microsoft.Xna.Framework.DrawableGameComponent { private Hra hra; public KomponentaMenu(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); } }
Komponentu zatím ponecháme prázdnou.
Obsluha herních obrazovek
Samotnou obsluhu herních obrazovek vložíme do třídy Hra
.
Docela mi to dává smysl a je jednoduše viditelná ze všech komponent.
Další možnost by byla vytvořit nějaký manažer obrazovek.
Přesuneme se do Hra.cs
, kde do atributů třídy přidáme 2
veřejné herní obrazovky, budou to obrazovky pro menu a pro level:
public HerniObrazovka obrazovkaMenu, obrazovkaLevel;
Do Initialize()
přidáme k vytvoření komponent i vytvoření
komponenty menu:
KomponentaMenu menu = new KomponentaMenu(this);
Připojení komponent ke hře do kolekce Components
úplně
odstraníme, protože to za nás dělá herní obrazovka. Namísto toho si
vytvoříme herní obrazovky:
obrazovkaMenu = new HerniObrazovka(this, mraky, menu); obrazovkaLevel = new HerniObrazovka(this, mraky, level);
Vidíme, že můžeme použít tu samou komponentu Mraky ve více obrazovkách.
Přidáme privátní metodu k přepnutí stavu obrazovky. Jako parametr bude
brát komponentu a stav (zapnuto/vypnuto). GameComponent
v MonoGame
má vlastnost Enabled
, která umožňuje zapínat/vypínat
vykonávání metody Update()
. Pokud ho vypneme, komponenta se
zastaví. Pokud je komponenta ještě typu DrawableGameComponent
(což téměř vždy bude), nastavíme i vlastnost Visible
. Ta
udává, jestli se má vykonávat metoda Draw()
a tedy jestli se
má komponta vykreslovat.
private void PrepniKomponentu(GameComponent komponenta, bool zapnout) { komponenta.Enabled = zapnout; if (komponenta is DrawableGameComponent) ((DrawableGameComponent)komponenta).Visible = zapnout; }
Když takto komponentu vypneme, nebude se vykreslovat ani obsluhovat. Stále však existuje a když ji poté zapneme, bude přesně ve stavu, v jakém jsme ji nechali. To se nám někdy může hodit (např. přechod mezi různými lokacemi, minihrami, z menu do hry atd.).
Vrátíme se do Initialize()
, po vytvoření obrazovek
proiterujeme všechny komponenty hry a vypneme je:
foreach (GameComponent komponenta in Components) { PrepniKomponentu(komponenta, false); }
Nakonec přidáme samotnou metodu k přepnutí obrazovky. Bude veřejná a jako parametr bude brát obrazovku, do které se chceme přepnout.
public void PrepniObrazovku(HerniObrazovka obrazovka) { }
Nejprve si z obrazovky vyžádáme komponenty, které obsahuje:
GameComponent[] povolene = obrazovka.VratKomponenty();
Proiterujeme komponenty hry a zjistíme, jestli je daná komponenta povolená. To uděláme tak, že se ji pokusíme vyhledat v poli povolených komponent. Nakonec komponentu nastavíme na požadovaný stav.
foreach (GameComponent komponenta in Components) { bool povolena = povolene.Contains(komponenta); PrepniKomponentu(komponenta, povolena); }
V metodě ještě updatujeme minulý stav kláves, protože přepnutí komponent má za následek jeho vynechání:
klavesyMinule = klavesy;
Na konec metody LoadContent()
přidáme přepnutí obrazovky
(nastane až ve chvíli, kdy bude vše načteno):
PrepniObrazovku(obrazovkaMenu);
Když hru nyní spustíme, měli bychom vidět menu, které je představováno zatím jen mraky.
Přesuneme se do KomponentaMenu
a přidáme do
Update()
reakci na Enter, která nás přesune do
hry:
if (hra.NovaKlavesa(Keys.Enter))
hra.PrepniObrazovku(hra.obrazovkaLevel);
Vyzkoušíme a vidíme, že jsme naprogramovali přepínání obrazovek.
Příště, v lekci MonoGame: Herní menu, se zaměříme na menu
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 277x (12.1 MB)
Aplikace je včetně zdrojových kódů v jazyce C#