Upozornění: Tento seriál je určen pokročilejším programátorům, které zajímá tvorba počítačových her. Pokud se teprve učíte programovat a nemáte moc zkušeností, nemá smysl, abyste se vrhli po hlavě přímo do vody, splácáním kousků kódu, které zkopírujete z tohoto seriálu, se nic nenaučíte a budete mít v hlavě zmatek. Můžete raději zkusit Seriál VB.NET pro úplné začátečníky.
Co je to XNA?
Pokud jste již zkoušeli napsat nějakou větší počítačovou hru, jejíž vývoj trvá déle než jedno či dvě odpoledne, pravděpodobně jste sáhli po technologii DirectX, případně OpenGL. Jestliže jste v těchto technologiích již něco naprogramovali, jistě mi dáte za pravdu, že občas musíme kvůli banálním věcem napsat poměrně dost kódu. Typickým příkladem je načtení obrázku ve formátu PNG tak, aby byla správně respektována průhlednost, nebo načtení 3D modelu, který nám poslal grafik.
Pokud například píšeme v DirectX, musíme hlídat i spoustu dalších potencionálních problémů - pokud nám uživatel během hry minimalizuje okno a po čase jej zase obnoví, když se vrací do hry, je třeba do paměti grafické karty načíst znovu všechny textury a data, která z ní mezitím vypadla. Na tuto a spoustu dalších maličkostí, které ale mají velký význam, musíme bohužel myslet, když hru programujeme.
Proto Microsoft vyvinul framework XNA. Velké komerční firmy, které tvoří gigantické hry typu Oblivion, asi "současné" XNA zajímat nebude (pro ně Microsoft plánuje speciální edici s pokročilými nástroji pro práci v týmu). XNA je totiž zatím určeno zejména pro studenty a menší herní vývojové týmy, tedy pro ty, které programování her baví, ale mají problém s komplexností DirectX, ve kterém si sice můžeme nastavit a přizpůsobit úplně vše, ale u jednodušších her tato rozsáhlost spíše vadí.
XNA framework nám dovoluje soustředit se pouze na naši herní logiku, tedy na to, co má hra dělat ve skutečnosti. Nemusí nás zajímat, na jakém hardware hra poběží, je nám jedno, jakou má hráč grafickou kartu a jestli tato karta umí to či ono, můžeme se plně soustředit na funkcionalitu hry. Hry napsané v XNA mohou běžet jak na PC s Windows XP a vyšším, tak i na herních konzolích XBox 360 (na to ale musíte mít zakoupené předplatné XNA Creators Club Subscription, které sice není drahé, ale nepodařilo se nám jej zakoupit z České republiky). Hry pro Windows můžete v XNA vyvíjet zcela zdarma, a to i komerčně.
Jinak ještě upozorňuji, že XNA je jednom nadstavba nad rozhraním DirectX, jeho výhodou je to, že spoustu funkcí, které bychom v DirectX museli složitě psát, už XNA obsahuje. Kromě podpory pro různé formáty obrázků a 3D modelů zde máme i spoustu funkcí pro počítání s maticemi a vektory, engine pro přehrávání zvuků a hudby, funkce pro komunikaci po síti atd.
Co budeme potřebovat?
Pro vývoj v XNA potřebujete stáhnout a nainstalovat několik produktů, všechny jsou k dispozici zdarma. XNA oficiálně podporuje pouze jazyk C#, ale to neznamená, že z jazyka Visual Basic .NET není možné XNA používat, i když to mnoho lidí tvrdí. Máme zde bohužel jistá omezení - knihovny XNA můžeme plně využívat z jakéhokoliv jazyka postaveného na .NET frameworku, ale pro pohodlný vývoj v XNA jsou k dispozici různé nástroje a doplňky do vývojového prostředí, které bohužel pro Visual Basic .NET dostupné nejsou. Musíme si tedy poradit jinak (je to trošku krkolomné a pokud toto řešení používáte, neobracejte se na oficiální technickou podporu, VB.NET oficiálně podporován není).
1) Jako první budete potřebovat vývojové prostředí Visual Basic 2005 Express Edition nebo libovolnou edici (placeného) Visual Studio 2005 s nainstalovanou podporou jazyka Visual Basic .NET. Pokud máte verzi 2008, budou v ní jisté odlišnosti, ale fungovat by měla také. Pokud nemáte nainstalované velké Visual Studio 2005, musíte si nainstalovat navíc ještě Visual C# 2005 Express (jinak vám XNA nepůjde nainstalovat; pokud máte C# verzi 2008, musíte nainstalovat i verzi 2005).
2) Stáhněte a nainstalujte všechny aktualizace, hlavně Service Pack 1 pro Express edice nebo pro velké Visual Studio.
3) Dále budete potřebovat XNA Game Studio Express 2.0, což je sada nástrojů pro vývoj v XNA spolu se samotnými knihovnami XNA Frameworku.
4) V neposlední řadě musíte stáhnout a nainstalovat tuto šablonu XNA projektu pro VB.NET a uložit ji (nerozbalovat!!!) do adresáře Dokumenty\Visual Studio 200X\Templates\ProjectTemplates\Visual Basic (kde X je buď 5 nebo 8 podle vaší verze Visual Studia). Tím se vám do nabídky pro výběr nového projektu přidá i nový typ Windows Game, což je náš projekt nastavený pro použití XNA.
Zatímco předchozí 3 body jsou oficiální downloady od Microsoftu, tato šablona ne. Našel jsem ji zde a upravil jsem ji pro potřeby tohoto tutoriálu (přeložil jsem některé komentáře a trochu ji pročistil).
Na co se můžete v rámci tohoto seriálu těšit?
Než se vám všechny balíčky stáhnou a nainstalují, budu chvíli psát o tom, na co se můžete těšit. Bude toho docela dost, samozřejmě bude chvíli trvat, než najdu čas na napsání všech dílů.
- Základy XNA a práce s 2D grafikou
- Vykreslování obrázků
- Práce s fonty
- 3D grafika
- Trocha nutné (anebo nudné) teorie - matice, vektory, transformace
- Načtení a vykreslení 3D modelu
- Renderování terénu
- Práce s Vertex a Pixel shadery
Tento seriál nebude o vytváření 3D modelů ani 2D textur, budeme používat volně dostupnou grafiku z Internetu nebo z XNA Starter Kitů.
Náš první projekt
Pokud již máte vše nainstalováno, spusťte svoje vývojové prostředí a vytvořte nový projekt typu Windows Game (VB.NET). V každé verzi Visual Studia to vypadá trochu jinak, ale věřím, že tento typ projektu bez problémů najdete.
Pokud pracujete ve Visual Studio 2008 (včetně Express edition), musíte po vytvoření projektu nastavit ve vlastnostech projektu verzi .NET Frameworku na 2.0. Uložte tedy projekt stisknutím kláves Ctrl+Shift+S a otevřete vlastnosti projektu:
Po vytvoření projektu se nám vygeneruje několik souborů. Pro nás zatím bude nejdůležitější třída Game - je to kostra celé hry. Pokud nyní projekt zkompilujeme a spustíme, objeví se prázdné výchozí okno s XNA hrou:
Jak to v XNA funguje?
To, že se po spuštění projektu zobrazilo prázdné okno, má za sebou poměrně složitou vnitřní režii. My se nyní podíváme na strukturu našeho projektu, abychom pochopili, jak to všecho funguje a kam budeme psát kód. Jak je vidět v podokně Solution Explorer, v našem projektu máme dvě třídy - Program a Game.
Třída Program obsahuje statickou metodu Main, která je vstupním bodem programu, tady se začíná provádět kód. Tato metoda je velmi jednoduchá, vytvoří jen novou instanci třídy Game a zavolá na ní metodu Run.
Třída Game reprezentuje naši hru a má několik základních metod, které mají svou funkci a do kterých budeme zapisovat náš kód.
Metoda Initialize
Tato metoda se spustí vždy na začátku ještě před načítáním herního obsahu. Uvnitř této metody budeme inicializovat některé herní objekty, např. SpriteBatch, který vykresluje 2D obrázky.
Metoda LoadContent
V každé hře používáme nějakou grafiku - obrázky, 3D modely s texturami, shadery. Tato data je třeba někde načíst, správné místo je právě uvnitř metody LoadContent. Tato metoda se totiž spustí vždy, když je načtení objektů potřeba.Typicky pokud uživatel minimalizuje okno aplikace, paměť grafické karty se vyprázdní, protože se musí vykreslovat okna ve Windows, a při návratu do hry je třeba do paměti opět načíst všechny textury atd. Ve všech těchto případech se zavolá metoda LoadContent, takže pokud načítání všeho umístíte sem, budete mít všechen obsah vždy načten.
Metody Update a Draw
Jakmile už hra běží, typicky se vždy přepočítají změny ve scéně a zareaguje se na vstup z klávesnice či z myši, a pak se celá scéna vykreslí. Tento cyklus se opakuje asi tak 60x za sekundu, aby byl pohyb plynulý, záleží samozřejmě také na výkonu počítače, kolikrát tyto náročné procesy stihne provést. Častěji než 60x za sekundu se zase většinou nepřepočítává, protože to je již zbytečné.
Dovnitř metody Update tedy napíšeme kód, kde budeme aktualizovat pozice objektů a modelů ve hře, a samozřejmě také reagovat na stisky kláves atd. V metodě Draw pak provádíme vykreslení celé scény.
Komentáře uvnitř třídy Game jsem přeložil do češtiny, takže by vám všem mělo být jasné, co se kde děje a co který řádek provádí.
Content Pipeline
XNA přineslo mimo jiné i revoluční myšlenku tzv. Content Pipeline. Jedná se o společné rozhraní pro načítání a práci s herním obsahem, tedy s texturami, modely, shadery atd. Pokud programujete s XNA v C#, můžete Content Pipeline využít velmi snadno, protože ji podporuje přímo vývojové prostředí Visual C#. Při kompilaci se všechen herní obsah zabalí a zkonvertuje do obecného formátu xnb. Je to mimo jiné i kvůli kompatibilitě s konzolemi XBox 360, protože ty třeba neumí pracovat pořádně s filesystémem tak, jak jej známe z .NET frameworku. Tímto způsobem jim všechno potřebné snadno zpřístupníme.
VB.NET má s Content Pipeline trochu problémy, ale řeší je naše šablona. Vše, co nahrajeme do složky Content a přidáme do projektu, se automaticky zkompiluje a přidá do Content Pipeline. Kvůli tomu je také v projektu složka VBContentManager se dvěma třídami, které se o tuto funkcionalitu starají (víceméně to funguje tak, že vygenerují fiktivní C# projekt a pomocí kompilátoru MSBuild zkompilují soubory ze složky Content jako obsah Content Pipeline) a přibalí je k tomuto projektu.
Vykreslení 2D obrázku a jednoduchá animace
Dost už bylo teorie, vrhneme se ještě na chvíli do programování. Nyní chceme přidat do projektu obrázek míčku a někam jej vykreslit. Pro obyčejný 2D obrázek se používá datový typ Texture2D, takže si nadeklarujeme proměnnou ball typu Texture2D. Tento řádek přidejte nahoru k deklaracím (ne do procedury!).
Private ball As Texture2D ' obrázek míčku
Nyní si stáhněte tento obrázek míčku (klikněte na něj pravým tlačítkem a vyberte možnost Uložit obrázek jako...) a přidejte jej do projektu do složky Content vybráním položky Add Existing Item z kontextového menu.
Nyní musíme samotný obrázek načíst, abychom jej mohli použít ve hře. Načítání obsahu provedeme v metodě LoadContent, vložte do ní tedy tento kód (pod uvedený komentář):
' >> Zde následuje kód pro načtení herního obsahu
ball = Content.Load(Of Texture2D)("Content\ball")
Možná se pozastavujete nad zvláštní závorkou (Of Texture2D). Metoda Load se dá používat pro jakýkoliv datový typ a protože proměnná ball je typu Texture2D, musí i metoda Load vracet hodnotu datového typu Texture2D. Je to to samé, jako když pracujeme s generickým seznamem - Dim seznam As New List(Of String). Jako parametr předáme metodě Load cestu k danému souboru (bez přípony, ale i s názvem složky Content).
Tímto jedním řádkem jsme načetli obrázek do proměnné ball a můžeme jej použít pro vykreslování v metodě Draw. Dovnitř metody Draw tedy napište tento kód, který nám obrázek vykreslí:
' >> Zde následuje kód pro vykreslení scény
SpriteBatch.Begin()
SpriteBatch.Draw(ball, Vector2.Zero, Color.White)
SpriteBatch.End()
Jak jsem již podotknul na začátku, pro vykreslování 2D záležitostí používáme objekt SpriteBatch. Všechny 2D prvky musíme vykreslit mezi zavoláním metod SpriteBatch.Begin a SpriteBatch.End. Vykreslování obrázků je totiž časově náročnější operace a celé to probíhá podstatně rychleji, pokud vykreslujeme víc obrázků hned za sebou. Když to zjednoduším, tak SpriteBatch.Begin začne "střádat" vykreslované obrázky a SpriteBatch.End je všechny odešle na grafickou kartu najednou, aby to proběhlo rychleji. Všechno vykreslování tedy musíme provést mezi voláním těchto dvou metod.
Samotné vykreslování provedeme zavoláním SpriteBatch.Draw. Jako první parametr předáme obrázek, který chceme vykreslit, jako druhý parametr předáme pozici (datový typ Vector2, který nese souřadnice X a Y) a nakonec barvu světla (v našem případě bílá). Vector2.Zero reprezentuje horní levý roh, pokud bychom chtěli jinou pozici, jednoduše napíšeme např. New Vector2(30, 50), kde [30, 50] jsou požadované souřadnice. Jako barvu světla jsme použili bílou, pokud bychom použili např. zelenou, obrázek by byl zabarven do zelena. Bílá je neutrální, takže s jeho barevností nic neudělá.
Když nyní projekt spustíme, vykreslí se nám obrázek míčku:
Jednoduchá animace
Do deklarací si přidáme dvě pole - ballX a ballY. Každé pole bude mít 6 prvků a budou v nich uloženy souřadnice jednotlivých míčků.
Private ballX(5), ballY(5) As Double ' souřadnice míčků
Nyní musíme v metodě Update každý snímek přepočítávat pozice našich šesti míčků, které budeme vykreslovat. Vložte do ní tedy tento kód:
' >> Zde následuje kód pro přepočítání scény
For i As Integer = 0 To 5
ballX(i) = 400 + Math.Sin(gameTime.TotalGameTime.TotalMilliseconds * 0.01 + Math.PI / 3 * i) * 200
ballY(i) = 300 + Math.Cos(gameTime.TotalGameTime.TotalMilliseconds * 0.003 + Math.PI / 3 * i) * 200
Next
Tato metoda dostane jako parametr proměnnou gameTime, která nám poskytuje detailní informace o tom, jak dlouho trval poslední snímek, a jak dlouho již hra běží. V této proměnné máme 5 vlastností - ElapsedGameTime, ElapsedRealTime nám dávají informaci o délce trvání posledního snímku (přepočítání + vykreslení), vlastnosti TotalGameTime a TotalRealTime nás pak informují o čase, který uběhl od začátku hry. Vlastnosti *GameTime udává tzv. bezpečný čas (pokud by nějaký snímek trval příliš dlouho, neobsahuje tato hodnota skutečnou dobu trvání snímku, ale hodnotu asi 1/15 sekundy; je to kvůli tomu, aby nám např. při chvilkovém pozdržení najednou neposkočilo auto o 2 metry, protože vzdálenost, o jakou se za jeden snímek posuneme, závisí na délce trvání posledního snímku). Vlastnosti *RealTime udávají skutečný čas, takže pokud potřebujeme čas přesně, raději volíme tyto. Poslední je vlastnost IsRunningSlowly, která má hodnotu True, pokud počet snímků za sekundu klesne pod 15 fps. Můžeme tedy v takovém případě třeba vyhlásit, že počítač je příliš slabý na spuštění této hry, to už záleží na nás.
Pozici míčků vypočítávám pomocí funkcí Sin a Cos, jako úhel jim předávám něčím vynásobenou hodnotu gameTime.TotalGameTime.TotalMilliseconds, což je počet milisekund od začátku hry, a ještě k ní přičítám i-násobek třetiny pí, protože máme 6 míčků a potřebujeme je rovnoměrně rozvrstvit. Pokud nechápete, jak tento vzorec funguje, nic se neděje. Můžete si místo toho zkusit napsat odrážení míčků od stěn okna.
Aby se nám vykreslily všechny míčky, musíme také upravit kód, který jsme vložili do metody Draw. Vykreslíme všech 6 míčků na vypočítané souřadnice:
' >> Zde následuje kód pro vykreslení scény
SpriteBatch.Begin()
For i As Integer = 0 To 5
SpriteBatch.Draw(ball, New Vector2(ballX(i), ballY(i)), Color.White)
Next
SpriteBatch.End()
Výsledek vypadá takto, míčky nám poletují v kruzích. Jen podotýkám, že není potřeba míček načítat šestkrát, pouze načtený obrázek míčku vykreslíme na 6 různých pozic.
Závěrem
Doufám, že se vám tento první díl o programování v XNA líbil a že vás navnadil začít si s XNA frameworkem hrát a poznávat jej. Programování her je v něm opravdu snadné a zábavné, to ostatně uvidíte u dalších dílů. Pro dnešek je to vše.