Sledujte nás na YouTube

Vyvíjíme pro WP v XNA: Tvorba vlastní hry (6. díl)

V šestém díle našeho pravidelného seriálu o programování pro Windows Phone v XNA začneme tvořit vlastní hru. Nažhavte Visual Studio a jdeme na to!

V minulých dvou dílech jsme si začali tvořit naši hru. Zatím se nám podařilo vykreslit obrázek na pozadí a nastavit zobrazení na celou obrazovku. Připomínám, jak teď například může vypadat náš konstruktor Game1() po úpravách v minulém díle (předpokládáme, že naše hra poběží na šířku a bude nám stačit maximální rychlost 30 snímků za sekundu):

public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    graphics.IsFullScreen = true;
    graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft;
    Content.RootDirectory = "Content";

    TargetElapsedTime = TimeSpan.FromTicks(333333);
    InactiveSleepTime = TimeSpan.FromSeconds(1);
}

Konečně si můžeme prozradit, co vlastně budeme vytvářet za hru. Bude to jednoduchá 2D střílečka. Na levé straně budeme mít raketku, která se bude moci pohybovat nahoru a dolů. Její pohyb budeme ovládat naklápěním telefonu. Proti ní budou zprava létat nějaké objekty, například asteroidy. Raketka se jim bude muset vyhýbat. Naštěstí ale bude neustále vystřelovat malé náboje, čímž je bude moci zničit. Z pravé strany občas budou přilétávat i bonusy, ty budeme moci sebrat kliknutím na displej. Raketka tak získá další palebnou sílu, rychlým flick gestem (tažením zleva doprava) budeme moci vyslat laser, roztažením dvou prstů od sebe vyvoláme tlakovou vlnu, která zničí všechny asteroidy v okolí. Dále budeme mít na displeji ukázané aktuální skóre a budeme také přehrávat zvuky.

-

Kromě toho už ale naše hra bude naprosto jednoduchá. Nebudeme například řešit animace výbuchů, ani nějaké vykreslování hlavního menu, obrazovky nastavení nebo tabulek rekordů. Určitě zde bude necháno mnoho prostoru pro vlastní kreativitu – budu rád, když nám na konci každému vznikne trochu jiná hra. Určitě se budu těšit, až se pochlubíte s výsledkem!

Poznámka: Obrázky pozadí, raketky a dalších objektů si budete moci stáhnout například z webů cgtextures.com nebo TurboSquid.com, já zatím také budu používat nějaké pomocné obrázky. Finální grafickou podobu hry si vyladíme každý po svém v průběhu vývoje. Předpokládané přibližné rozměry obrázků: raketka 120×120 pix, asteroid max. 150×150 pix, ikonka bonusu 64×64 pix, střela 30×10 pix, laser 800×15 pix. Budete moci použít formát jpg nebo png, i s průhledností.

-

Obrázek na pozadí už máme tedy vykreslený z minulých dílů. Další, do čeho se pustíme, bude vykreslení raketky na levé straně displeje. To už uděláme trochu sofistikovaněji. Kdybychom si veškerý kód pořád připisovali do hlavní třídy Game1, brzy bychom se v ní začali ztrácet. Přidáme si tedy do projektu ještě jednu novou třídu – nazveme ji Sprite, v té si budeme udržovat ke každému obrázku jeho pozici, texturu a velikost. Časem si do ní přidáme i některé další metody, například pro ověřování kolizí mezi ostatními herními prvky.

 -

V paletce Solution Explorer si do našeho projektu přidáme složku GameObjects. Na tu klikneme pravým tlačítkem a zvolíme Add –> Class. Tuto třídu si nazveme Sprite. Do předpřipraveného kódu jí doplníme před “class Sprite” modifikátor public, abychom se na ni mohli odvolávat i z hlavní třídy Game1. Dále si do ní přidáme tři základní datové položky, ve kterých si budeme udržovat pozici, velikost na herním plánu a texturu:

public Vector2 Position;
public Vector2 Size;
public Texture2D Texture;

Visual Studio nám bude tato klíčová slova ještě podtrhávat, doplníme si tedy do této třídy potřebné usingy Microsoft.Xna.Framework a Microsoft.Xna.Framework.Graphics. To zařídíme jednoduše tak, že klikneme na jedno z podtržených klíčových slov a v nabídce vybereme “using…”.

-

Do této třídy si dále budeme chtít určitě přidat i nějaké konstruktory. Neboli speciální metody, pomocí kterých budou moci být objekty typu Sprite inicializovány. Budou se nám nejspíš hodit všechny tři varianty – buď objektu při jeho vytváření určíme pozici, nebo mu nastavíme pozici i velikost. Případně mu rovnou předáme odkaz na načtenou texturu (v tomto případě mu bude také automaticky nastavena jeho velikost podle rozměrů této textury).

public Sprite(Vector2 position)
{
    Position = position;
}

public Sprite(Vector2 position, Vector2 size)
{
    Position = position;
    Size = size;
}

public Sprite(Vector2 position, Texture2D texture)
{
    Position = position;
    Texture = texture;
    Size = new Vector2(Texture.Width, Texture.Height);
}

Dále si sem přidáme i dvě metody – LoadContent() a Draw(). Ty budou odpovídat stejně pojmenovaným metodám ve třídě Game1. Budou nám sloužit k načtení textury do našeho objektu Sprite a k jejímu vykreslení. Vyplníme si do nich prakticky stejný kód, jako jsme už psali, když jsme si zkoušeli vykreslit obrázek na pozadí.

To, že tento kód nyní budeme mít zabalený ve třídě Sprite a ne v Game1, nám přinese několik výhod. Ve třídě Game1 se nám zjednoduší zápis volání této funkcionality. Pokud si budeme chtít následně upravit způsob načítání nebo vykreslování textury, bude nám stačit upravit kód ve třídě Sprite, hlavní třídu Game1 už nebudeme muset měnit. Když si také budeme chtít vytvořit odvozené objekty od Sprite (poděděné), budeme si moct jednoduše “přetížit” tyto metody (nahradit jejich funkcionalitu jinou). Pokud také někdy v budoucnu začneme ve svých projektech využívat komponenty typu GameComponent nebo DrawableGameComponent, objevíme tam také velice podobnou strukturu metod. Za naše konstruktory si tedy vložíme ještě tyto dvě metody:

public void LoadContent(string assetName, ContentManager content)
{
    Texture = content.Load<Texture2D>(assetName);

    if (Size == Vector2.Zero)
        Size = new Vector2(Texture.Width, Texture.Height);
}

public void Draw(SpriteBatch spriteBatch)
{
    spriteBatch.Draw(Texture, Rect, Color.White);
}

Do metody LoadContent() si budeme předávat název textury, kterou bychom si zde rádi načetli, a odkaz na potřebný Content Manager. Tuto texturu si zde potom jednoduše nahrajeme do paměti. Content Manager se v XNA chová poměrně rozumně, pokud se ho zeptáme víckrát na stejné Asset Name, z paměti si texturu načte jen napoprvé, pak už nám vrátí jen odkaz na načtený objekt (což je přesně to, co bychom od něj očekávali, nemusíme si tedy psát žádnou svoji správu textur nebo modelů).

V naší hře můžeme mít i více Content Managerů. Doporučovaným způsobem je udržovat si jeden Content Manager pro pomocné herní objekty jako pozadí, menu apod., další pro načítání objektů na herním plánu. Při načítání dalšího levelu si na tento gamePlanContentMan zavoláme .UnLoad() a postupně si do něj znovu načteme nové objekty. U naší jednoduché hry tohle ale zatím řešit nebudeme, zůstaneme jen u jednoho Content Manageru a jednoho herního levelu.

Při psaní metody Draw() si všimneme ještě jednoho zádrhelu. Tak jak jsme si to zapsali, bychom si chtěli vykreslit texturu na zadaný Rectangle. Rectangle je struktura se čtyřmi celočíselnými položkami, pozicí X, Y, šířkou a výškou. My máme ale naše data reprezentovaná jako pozici a velikost ve formátu Vector2. Vector2 obsahuje dvě čísla X a Y, která jsou ale desetinná (typu float). Používání desetinných čísel se nám určitě hodí například při přesném pozicování objektů v animacích, zde bychom z nich ale rádi dostali celočíselné hodnoty. Do třídy Sprite si tedy ještě přidáme nahoru k ostatním datovým položkám tento getter:

public Rectangle Rect
{
    get {
        return new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y);
    }
}

Rect nám bude sloužit jako veřejně přístupná datová položka, s přístupem jen pro čtení. Při zeptání se na jeho hodnotu se nám vrátí “obdélník” s celočíselnými hodnotami podle naší skutečné pozice a velikosti, přesně tak, jak jsme chtěli.

Ve třídě Sprite to bude prozatím vše. Připravili jsme si ji jako pomocný objekt pro jednodušší vykreslování, nyní si ukážeme, jak ji využít. Přepneme se do třídy Game1. Na začátek si nadeklarujeme objekt typu Sprite pro naši raketku a rovnou mu nastavíme pozici (zatím na x=0,y=0).

Sprite jet = new Sprite(Vector2.Zero);

Nejspíš si do třídy Game1 také ještě budeme muset přidat jeden using (na NázevNašehoProjektu.GameObjects), ideálně opět tak, že klikneme na podtržené slovo Sprite a vybereme si tuto položku z nabídky.

V metodě LoadContent() si potom do našeho objektu raketky načteme potřebnou texturu, podle nastaveného Asset Name:

jet.LoadContent("jet", Content);

A v metodě Draw() si ji jednoduše vykreslíme (kód si vložíme před volání spriteBatch.End()):

jet.Draw(spriteBatch);

Zatím bude naše raketka zobrazena v levém horním rohu. Později si ji budeme ovládat pohybovým senzorem. Pro dnešek bychom si ale ještě mohli zkusit raketku jednoduše rozpohybovat. K tomu nám bude sloužit metoda Update(). Zatím si to uděláme jen jednoduše, v každém snímku si raketku posuneme o malý přírustek (půl pixelu) směrem dolů:

jet.Position.Y += 0.5f;

Vidíme, že nám stačí měnit jen její pozici v metodě Update(), do vykreslování v metodě Draw() už nijak nezasahujeme, to nám zůstává stejné. Když si naši hru spustíme, raketka se nám rozjede. Její rychlost bude zatím závislá na rychlosti vykreslování (počtu snímků za sekundu), v příštím díle si ukážeme, jak se tohoto neduhu zbavit. Také se pustíme do vykreslování a animace létajících bonusů a asteroidů a zkusíme si ošetřit jednoduché kolize.

O autorovi článku

Studuji Matematicko-fyzikální fakultu UK v Praze, věnuji se platformě .NET a programování v C#. Tvořil jsem pro Windows Mobile, nyní se věnuji především platformě Windows Phone 7. Zajímám se o návrh her a další multimediální tvorbu, dokončuji vývoj herního 3D engine postaveného nad XNA. Ve volném čase si najdu rád chvilku na in-line brusle, nebo na hraní na klávesy. Na škole působím jako Microsoft Student Partner.

Tomáš Slavíček

2 komentáře

  1. Richie (neregistrovaný)

    Ahoj, mám takový problém.

    Během psaní kódu není žádný problém, ani když ho dopíšu. Žádné podtržení nebo něco podobného.

    Jakmile ale dám spustit, tak to vyhodí chybu, jejíž popis je se vejde na celý monitor od shora dolů. Nevíš, co by to mohlo být a co by to mohlo spravit? Díky

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *