speciál
Vývoj her v XNA #10: 2D hra s raketkou #1

Vývoj her v XNA #10: 2D hra s raketkou #1
Vývoj her v XNA #10: 2D hra s raketkou #1
10:00, 09.02.2011

V několika dílech našeho seriálu jsme se podívali, jak se v XNA vykresluje model, jak se s ním pohybuje a jak se na něj mapuje textura, a pochopili jsme spousty skvělých věcí.

Na začátku kompletního seriálu jsme si vykreslili míček na obrazovku. V těchto kapitolách se ke 2D grafice vrátíme. Dnešním cílem bude vykreslení raketky a její pohyb po obrazovce. Zní to jednoduše a vcelku jednoduché to i je – takže hurá do práce.

Připravil jsem vám texturu rakety:



Klik pro zvětšení (Vývoj her v XNA #10: 2D hra s raketkou #1)

Vypadá možná jednoduše, ale strávil jsem nad ní ve Photoshopu asi 30 minut. Vzdejte hold proto všem herním grafikům. Vězte, že udělat raketu tak realisticky, aby vypadala opravdu jako raketa, není až taková legrace.

Založil jsem si nový projekt s názvem RocketGame a do složky Content jsem si nahrál soubor raketky. Tu jsem potom vykreslil přesně tak, jako jsme se to učili s balónkem již dříve.

Klik pro zvětšení (Vývoj her v XNA #10: 2D hra s raketkou #1)

K pohybu rakety budeme používat klávesnici. Odchytávat akce si budeme v metodě Update(), která je k tomu určena.

Při držení kláves budeme přepisovat hodnoty proměnných uložených někde v paměti. Na hodnotách těchto proměnných budeme raketku vykreslovat. Výsledek tedy bude ten, že se nám bude pohybovat přesně po stisku kláves.

Založíme si nové proměnné, které si umístíme nahoru do deklarací:

int osaX = 0;

int osaY = 0;

Vykreslovací metodu Draw() si upravíme tak, aby se raketka vykreslovala právě na této pozici (slovo raketa je zde objekt typu Texture2D s načtenou texturou, stejně, jako jsme to dělali ve 4. díle s balonkem):

protected override void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();

spriteBatch.Draw(raketa, new Vector2(osaX, osaY), Color.White);

spriteBatch.End();

base.Draw(gameTime);
}


Další postup je celkem jednoduchý. Na začátku metody Update() si hodnoty proměnných osaX a osaY budeme přepisovat podle stisků jednotlivých kláves.

if (Keyboard.GetState().IsKeyDown(Keys.Right))

{
osaX++;
}


Tento kousek kódu je jednoduchý. Měli byste jej chápat sami, ale pro jistotu ho vysvětlím. Metoda GetState() třídy Keyboard nám umožnuje dotazovat se na stavy jednotlivých kláves vstupního zařízení. Metoda IsKeyDown() je přesně takovým dotazem na stav, jestli byla zmáčknuta, a jako argument jí předávám určitou klávesu. V tomto případě šipku vpravo.

Jakmile je stisknuta, tak se inkrementuje proměnná osaX. Mimochodem, zkrácený zápis osaX++ znamená totéž, jako bychom napsali osaX = osaX + 1;

Obdobně si můžeme tyto metody udělat pro všechny možné posuny. Tedy pro posun dopředu, dozadu a doleva (doprava už máme hotový). Stav klávesnice si také můžeme zjistit jen jednou na začátku metody, pak už se jen dotazovat, jaká klávesa byla stisknuta. Pokud máme “tělo podmínky” jen jednořádkové, můžeme také vynechat otevírací a zavírací složené závorky. Výsledek tedy může vypadat nějak takto:

KeyboardState keyState = Keyboard.GetState();

if (keyState.IsKeyDown(Keys.Right))

osaX++;

if (keyState.IsKeyDown(Keys.Left))

osaX--;

if (keyState.IsKeyDown(Keys.Up))

osaY--;

if (keyState.IsKeyDown(Keys.Down))

osaY++;


Když si projekt spustíme, raketa se nám bude hýbat podle stisku jednotlivých šipek. Ještě by to chtělo, aby se pohybovala o něco rychleji. To je vcelku jednoduché. Musíme jen jednotlivé hodnoty zvyšovat o více než o jeden bod. Budeme je zvyšovat rovnou o pět. Výsledek upravených podmínek tedy vypadá takto:

if (keyState.IsKeyDown(Keys.Right))

osaX = osaX + 5;

if (keyState.IsKeyDown(Keys.Left))

osaX = osaX - 5;

if (keyState.IsKeyDown(Keys.Up))

osaY = osaY - 5;

if (keyState.IsKeyDown(Keys.Down))

osaY = osaY + 5;


A hurá! Máme raketku která se může pohybovat po hrací ploše. Ještě by to chtělo její pohyb nějak limitovat. Například nechceme, aby raketka mohla vylétávat mimo hrací plochu. Omezíme tedy její pohyb v ose X mezi 0 až šířku celé hrací plochy, mínus šířku rakety (pamatujme, že raketku pozicujeme podle jejího levého horního rohu, hodnoty na ose X nám stoupají zleva doprava a na ose Y shora dolů. Úplně nalevo nahoře je tedy bod X = 0, Y = 0.

Upravený kód nám nyní vypadá nějak takto. Pokud chceme, aby zároveň platily dvě podmínky najednou, používáme symbol dvojitého ampersandu (&&, na české klávesnici se napíše jako pravý Alt+C). Pokud bychom chtěli napsat logické “nebo”, neboli, že by měla platit alespoň jedna podmínka, to se značí dvojitým svislítkem (||, na české klávesnici jako pravý Alt+W).

if (keyState.IsKeyDown(Keys.Right) && (osaX < (GraphicsDevice.Viewport.Width - 100)))

osaX = osaX + 5;

if (keyState.IsKeyDown(Keys.Left) && osaX > 0)

osaX = osaX - 5;

if (keyState.IsKeyDown(Keys.Up) && osaY > 0)

osaY = osaY - 5;

if (keyState.IsKeyDown(Keys.Down) && (osaY < (GraphicsDevice.Viewport.Height - 116))

osaY = osaY + 5;


Jsou to tedy jen kombinované podmínky.  Na vysvětlenou si vezmu hned tu první na paškál a vysvětlím ji. První část

keyState.IsKeyDown(Keys.Right)

je celkem jasná. Musí být stisknuta klávesa šipka vpravo. Druhá část je zajímavější:

osaX < (GraphicsDevice.Viewport.Width - 100)

Hodnota proměnné osaX musí být menší, než je šířka celé hrací plochy, mínus šířka rakety. To je proto, aby nám ten obrázek nevyjel mimo plochu. Podobný ale upravený princip je použit i u osyY.

A co jsme tím dokázali? Dokázali jsme tím celou naši raketu “udržet” v hrací ploše. Mimochodem, díky tomu, že rozměry plochy si zjišťujeme pomocí třídy GraphicsDevice a její vlastnosti Viewport, máme toto omezení i dynamické. Bude vždycky takové, jaké je rozlišení hracího okna.

Můžeme si to ověřit přidáním řádku

Window.AllowUserResizing = true;
do konstruktoru třídy Game1. Naše okno pak půjde různě měnit, roztahovat, zmenšovat… no prostě jako jakékoliv okno normální okno ve Windows.

Bylo by dobré se dnes pustit ještě do něčeho. V minulých dílech jsme si do projektu přidávali soubory, kde v každém byla naimplementovaná nějaká třída. Dneska si takovou jednoduchou vlastní třídu zkusíme vytvořit. Bude ve stejném Namespace, jako je naše hra, a bude sloužit k uchovávání informací o každém objektu ve scéně. Tedy i naší rakety. Hodně nám zjednoduší práci a zrychlí vývoj. Takže hurá do toho!

Klik pro zvětšení (Vývoj her v XNA #10: 2D hra s raketkou #1)

V panelu Solution Explorer si klikneme pravým tlačítkem na název projektu a zvolíme Add –> Class. Vytvoříme si novou třídu, kterou si pojmenujeme GameObject. Do ní si napíšeme konstruktor. Konstruktor je speciální metoda třídy, která se volá vždy při vytvoření projektu. Každá třída má už automaticky bezparametrický konstruktor. Můžeme si ale místo něj vytvořit i konstruktor přebírající nějaké parametry, jako zde například pozici X a Y. Naše třída bude sloužit k uchovávání pozice objektu na obrazovce. V budoucnu si ji budeme moci rozšířit i o další vlastnosti, například jeho pootočení, rozměr obrázku, odkaz na texturu a podobně.

public GameObject(int posX, int posY)

{

}

Do této třídy si nyní vložíme několik vlastností (properties), které nám budou pozice uchovávat. Napsáním prop a dvojitým stiskem tabulátoru se nám automaticky vytvoří pro zápis property jednoduchá šablona. Ve výchozím stavu je tam datový typ property integer, což se nám hodí, takže int necháme a stiskem tabulátoru se přesuneme na editaci jména. Změníme jméno na PosX. Vytvoříme stejným stylem ještě jednu property PosY. Upravíme nyní konstruktor tak, že přijaté hodnoty při tvorbě třídy (posX a posY) nastaví jako hodnoty těmto vlastnostem třídy.

Jestli dovolíte, udělal bych malou odbočku ke konvencím a značení v C#. Veřejně dostupné položky třídy (označené klíčovým slovem public) se značí prvním písmenem velkým. Privátní položky obvykle začínají malým písmenem (v C# se většinou nepoužívá podtržítková konvence, jako například v C++). Ve třídě poté většinou píšeme pod sebe nejdřív veřejné property (vlastnosti), potom privátní. Následují konstruktory třídy, její veřejné metody a implementace interface, dále privátní metody a nakonec se většinou píše případný destruktor, implementace metod Dispose() apod. Pevná pravidla tu ale samozřejmě nejsou, vždy je to o nějaké dohodě v týmu apod. Jednotlivé úseky kódu se dají také oddělovat a následně skrývat pomocí klíčových slov #region a #endregion.

Celá naše třída teď tedy vypadá nějak takto:

class GameObject

{

public int PosX { get; set; }

public int PosY { get; set; }

public GameObject(int posX, int posY)

{
this.PosX = posX;
this.PosY = posY;
}
}


Je hrozně jednoduchá. Má jen konstruktor a dvě property (vlastnosti). Při její inicializaci se hodnoty předané v konstruktoru přiřadí jejím vlastnostem. Toť vše :) Klíčové slovo this v konstruktoru znamená, že se odvoláváme na položky této třídy.

Předěláme si nyní hru tak, aby k vykreslování naší rakety využívala tuto námi napsanou třídu. Do třídy Game si přímo na začátek vložíme deklaraci a rovnou i inicializaci třídy GameObject, kterou si pojmenujeme player:

GameObject player = new GameObject(0, 0);

Dosadili jsme jí rovnou hodnoty X = 0, Y = 0. Nyní budeme chtít, aby si hráčova raketa uchovávala v objektu player svoji pozici. Upravíme řádek v metodě Draw() tak, aby si pozice získával z objektu player a ne z proměnných osaX a osaY. Výsledek tedy bude vypadat takto:

spriteBatch.Draw(raketa, new Vector2(player.posX, player.posY), Color.White);

Nyní musíme také upravit příkazy v metodě Update(). Výsledek:

if (keyState.IsKeyDown(Keys.Right) && (player.posX < GraphicsDevice.Viewport.Width - 100))

player.posX = player.posX + 5;

if (keyState.IsKeyDown(Keys.Left) && player.posX > 0)

player.posX = player.posX - 5;

if (keyState.IsKeyDown(Keys.Up) && (player.posY > 0))

player.posY = player.posY - 5;

if (keyState.IsKeyDown(Keys.Down) && (player.posY < GraphicsDevice.Viewport.Height - 116))

player.posY = player.posY + 5;


Nyní už nám stačí vymazat deklarace proměnných osaX a osaY – nikde je nepoužíváme a k ničemu nám již nejsou. Dokonce nás na ně Visual Studio při kompilaci upozorní, že tam jen tak sedí a nic nedělají, takže se jich můžeme s radostí zbavit.

A to je vše. Máme raketu, která se vykresluje na pozice obrazovky podle objektu player a můžeme si s ní jednoduše hýbat.


CGwillWin