NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

Lekce 1 - Struktura MonoGame hry

V úvodním dílu o MonoGame jsme si udělali úvod do MonoGame a framework jsme si nainstalovali. Dnes se podíváme na strukturu MonoGame hry a vyzkoušíme si úplné základy práce s frameworkem.

Ještě jednou upozorňuji, že tento kurz se věnuje výuce frameworku MonoGame, nikoli výuce jazyka C#. Pokud C# neovládáte, přečtěte si nejprve alespoň první 2 sekce C# tutoriálů a potom práci se soubory.

MonoGame jsme si již nainstalovali minule, spusťme si tedy Visual Studio a založme nový projekt MonoGame (MonoGame Cross Platform Desktop Project), který pojmenujeme Robotris:

Založení nového projektu - Od nuly k tetrisu v MonoGame

Pokud vám název připomněl Tetris, máte pravdu, celý kurz se bude točit okolo této hry. Postupně si vytvoříme hru, herní menu, on-line skóre tabulku a obrazovku s autory hry. Naučíte se základy práce s frameworkem a budete poté schopni vytvořit jakoukoli vlastní hru :)

Struktura projektu

MonoGame hra má svou specifickou strukturu. V novém solution nalezneme složky x64/ a x86/, které spolu s se soubory *.dylib zajišťují chod Cross-Platform, pro náš tutoriál je důležitý soubor Game1.cs, který je samotná MonoGame hra. A složka Content/ je tzv. obsah. Tak MonoGame (Původně Microsoft v XNA) nazval obrázky, zvuky a hudbu. Právě do této složky přes MonoGame Pipeline Tool je budeme přidávat.

Zaměřme se nejprve na třídu Game1.cs, kterou nám Visual Studio vygenerovalo a popišme si její kód, který vypadá nějak takto:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Robotris
{
    /// <summary>
    /// This is the main type for your game.
    /// </summary>
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// game-specific content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}

Jmenné prostory

Nejprve máme deklaraci několika jmenných prostorů. Zde si můžeme všimnout, že žádný MonoGame namespace vlastně není a že vše je z jmenného prostoru Microsoft.Xna.Framework. To díky postavení MonoGame na XNA frameworku a vývojáři se rozhodli zachovat původní jmenný prostor. Máme vysvětleno a teď přejděme ke třídě Game1.

Třída Game

Třídu přejmenujeme na Hra a to tak, že přemístíme kurzor na Game1 a stiskneme F2, následující dialog též potvrdíme. Vidíme, že třída dědí z Microsoft.Xna.Framework.Game.

Máme založené 2 proměnné a sice graphics a spriteBatch.

  • graphics je typu GraphicsDeviceManager a poskytuje metody např. pro změnu velikosti herního okna, přepnutí fullscreen (celoobrazovkového režimu) a podobně.
  • spriteBatch je instance třídy SpriteBatch, která poskytuje funkcionalitu pro práci se sprity. Máme zde nový termín - Sprite. Sprite (sprajt, sprit) je vlastně obrázek. Ten je klíčovým prvkem 2D her, přes sprity se řeší v podstatě vše od pozadí, přes postavy ve hře až po písmo, kde je každé písmeno jeden sprite. Sprity mohou být i animované.

Konstruktor a metoda Initialize()

Přejděme ke konstruktoru. Zde se inicializuje proměnná graphics a také se nastaví kořenová složka pro Content. Již víme, že je to složka, ve které budou naše herní data.

Kromě konstruktoru zde máme i metodu Initialize(). To může být matoucí a také, že je :) MonoGame vlastně nijak neříká, kdy použít k inicializaci konstruktor a kdy metodu Initialize().

Rozdíl je v tom, že konstruktor se volá hned při vytvoření hry (nebo herní komponenty, o těch až později) a měl by nastavit hru nebo komponentu tak, aby byla funkční. To je u hry vždy a proto sem nic již psát nebudeme. Další význam konstruktoru je k předání závislostí, to poznáme u komponent, ze kterých se hra potom může skládat.

Metoda Initialize() potom slouží k načtení dat, které nejsou obsah (Content, tedy nejsou sprity, zvuky nebo hudba), mohou to být např. nějaké soubory map. Také zde provedeme veškerou inicializace hry, vytvoření potřebných objektů, nastavení proměnných na výchozí hodnoty a podobně.

V Initialize() máme vložený řádek kódu:

base.Initialize();

Ten se stará o spuštění metody Initialize() na všech komponentách hry. O komponentách ale až později. Podobný řádek nalezneme i v dalších metodách třídy.

LoadContent()

Dále máme ve třídě metodu LoadContent(), do té patří načítání obsahu, tedy spritů, zvuků a hudby. Vidíme, že se zde vytváří i spriteBatch. Na konec metody můžeme psát logiku, kterou potřebujeme spustit až po načtení obsahu. Např. nemůžeme v Initialize() zjistit výšku fontu, který ještě není načtený, proto by kód patřil do LoadContent().

UnloadContent() se volá po skončení hry, protože my budeme vždy používat Content (což je vlastnost hry, kde je instance ContentManager), která si uvolnění zdrojů řeší sama, není pro nás metoda důležitá.

Následují 2 nejdůležitější metody.

Update()

Update() obsahuje real-time logiku, tedy vše, co se zpracovává v reálném čase. Většinou je to klávesnice, případně myš nebo jiné ovladače, potom kolize a pohyb objektů ve hře. Obecně sem patří reakce na nějaké události a posun objektů, případně jejich animace. Metoda se vykonává 60x za vteřinu. Pokud posuneme postavu v této metodě o 1, bude chodit stejně rychle i na jinak rychlém počítači. Jiný interval můžeme nastavit jako desetinné číslo menší než 1 pomocí vlastnosti hry TargetElapsedTime, ale to nebudeme potřebovat. Důležité je vědět, že MonoGame za nás bude samo dělat optimalizace i když počítač nebude hru stíhat a to tak, že bude vynechávat vykreslování.

V parametru metody dostaneme instaci GameTime. Zde je uložen herní čas, nalezneme na něm 2 užitečné vlastnosti:

  • ElapsedGameTime - TimeSpan s časem uběhnutým od posledního update.
  • TotalGameTime - TimeSpan s celkovým časem běhu hry.

Díky těmto hodnotám můžeme v Update pracovat s reálným časem, udělat něco třeba každou sekundu, minutu a podobně. Vše si ukážeme během seriálu.

Další vlastnost na gameTime je IsRunningSlowly, ta je true, pokud se hra "seká" a MonoGame vynechává vykreslování. Můžeme tak na tuto situaci reagovat, ale my takto náročné hry zatím vytvářet nebudeme :)

V metodě je již doplněná jedna reakce na tlačítko Zpět na gamepadu, které ukončí hru. To je důležitý kód proto, aby šla hra ukončit na XBoxu, kdybychom ji tam chtěli nahrát. Mimo jiné vidíme, že ukončení hry se provede pomocí metody Exit().

Draw()

Draw() se stará o vykreslení hry (jak již název napovídá). Vykreslování probíhá voláním metod na instanci SpriteBatch. Grafické zařízení musíme před každým snímkem (frame) vymazat, aby na něm nezůstaly vykreslené objekty z minula. Toho je docíleno metodou Clear() a modrou barvou (na barvě vlastně nazáleží, pokud bude mít hra pozadí). Setkáváme se zde se strukturou Color, ta v MonoGame slouží k ukládání barev. Má na sobě několik statických metod, které navrátí její instanci nastavenou na určitou barvu.

Herní smyčka

Je důležité vědět, jak uvnitř hra pracuje. Herní metody se volají v tzv. herní smyčce a to v tomto pořadí:

  1. Initialize()
  2. LoadContent()
  3. Update()
  4. Draw()
  5. UnloadContent()

Update() a Draw() se stále opakují, dokud není hra ukončena. Cyklus znázorněný pomocí vývojového diagramu by vypadal takto:

Herní smyčka v MonoGame - Od nuly k tetrisu v MonoGame

Příště, v lekci Vložení obsahu MonoGame hry, si do projektu vložíme sprity, fonty, zvuky a hudbu, půjde tedy o vložení obsahu :)


 

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

 

Všechny články v sekci
Od nuly k tetrisu v MonoGame
Přeskočit článek
(nedoporučujeme)
Vložení obsahu MonoGame hry
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
28 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