článek

Udělej si vlastní hru – II. část

Udělej si vlastní hru – II. část

Vítejte u dalšího pokračování seriálu o tvorbě hry. Dnešní díl věnujeme zpočátku inicializaci grafického rozhraní a pak se budeme věnovat již záležitosti čistě herní - načtení a vykreslení herního bludiště. Více prozradí ale až náš článek

Grafické rozhraní



Jak jsem již kdysi uvedl, k vykreslování grafiky budu používat rozhraní DirectX. Vzhledem k tomu, že od něj však budu požadovat opravdu jen základní služby, je vcelku jedno, jaké rozhraní použijete vy - jako alternativa se nabízí např. OpenGL, ale vystačíte i se základním GDI. Pokud budete chtít používat zdrojové kódy z tohoto seriálu, pak asi budete muset DirectX použít, měli byste tedy mít k dispozici potřebný vývojářský balíček, který lze stáhnout ze stránek Microsoftu. Při vykreslování grafiky nám půjde v podstatě o jedinou věc - o vykreslování tzv. spritů, tedy 2D objektů představovaných obrázky. Bude se jednat např. o čtverce, z kterých se skládá herní level, o obrázky nepřátel či herní postavy apod. Pokud tedy používáte jiné grafické rozhraní, pak si v něm zajistěte možnost načtení a vykreslení 2D spritu na obrazovku a několik následujících řádků můžete přeskočit. V opačném případě mi dovolte, abych Vás v minimální míře seznámil s inicializací a použitím DirectX v té podobě, v jaké jej budeme používat po celou dobu seriálu.



Použití DirectX




Kód, který byl výsledkem minulého dílu nyní rozšíříme o funkce pro inicializaci a odebrání grafického rozhraní a v rychlosti si též ukážeme, jak pomocí něj načíst a vykreslit sprite. Inicializaci provedeme funkcí InitD3D, během inicializace máme možnost nastavit potřebné parametry, např. zda aplikace poběží v okně či fullscreenově, rozlišení obrazovky apod. Při ukončování hry musíme vše, co jsme vytvořili zrušit (přesněji uvolnit alokovanou paměť) - tak činíme pomocí funkce ReleaseD3D. Pro vysvětlení jednotlivých funkcí a struktur použitých ve funkci InitD3D doporučuji nahlédnout do dokumentace, která je součástí DirectX SDK, či použít nějaký tutorial, který se přímo používáním DirectX (resp. Direct3D) zabývá. Pozornost věnujte především struktuře D3DPRESENT_PARAMETERS - pomocí ní se nastavují důležité parametry zobrazení (viz zdrojový kód). V rámci funkce InitD3D provádíme také načtení potřebných textur (použitých pro kreslení spritů). Pro vykreslení bludiště budeme potřebovat celkem 3 textury - pro prázdný čtvereček, pro železný čtvereček a pro cihlový čtvereček. Textury načteme pomocí standardních funkcí, které máme v Direct3D k dispozici, před tím je (resp. ukazatele na ně) však musíme deklarovat jako globální proměnné, abychom k nim mohli přistupovat:




        IDirect3DTexture9 *pBricksTex = NULL; //textura cihel

        IDirect3DTexture9 *pSandTex = NULL; //textura písku (prázdný čtvereček)

        IDirect3DTexture9 *pIronTex = NULL; //textura železa




Načtení textur provedeme takto:




        D3DXCreateTextureFromFile(pD3DDevice, "data/bricks.bmp", &pBricksTex); //načtení textur

        D3DXCreateTextureFromFile(pD3DDevice, "data/sand.bmp", &pSandTex);

        D3DXCreateTextureFromFile(pD3DDevice, "data/iron.bmp", &pIronTex);



A ještě nakonec musíme vytvořit objekt spritu, používaný při vykreslování:




        D3DXCreateSprite(pD3DDevice,&square); //vytvoření spritu



(Ukazatel na objekt spritu jsme samozřejmě také deklarovali jako globální proměnnou - viz zdrojový kód). Na obrazovku budeme vše potřebné vykreslovat pomocí funkce Render, vykreslení spritu (který jsme pojmenovali square) provedeme takto:



        square -> Draw (pSandTex,NULL,NULL,&pos,0xffffffff); /*vykresleni spritu - misto pSandTex lze samozrejme uzit jinou texturu dle potreby*/



Pro popis funkce Draw a jiných funkcí, použitých uvnitř funkce Render, opět nahlédněte do dokumentace, neboť není v možnostech tohoto
seriálu se jim podrobněji věnovat.











Načtení a vykreslení mapy



Nyní se konečně dostáváme k záležitostem ohledně hry samotné. Nejprve si ujasněme, jak přesně bude herní mapa - bludiště - vypadat. Bude se jednat o čtvercovou síť, jejíž čtverce budou tří různých typů - pískový (volně průchozí), cihlový (průchozí po odpálení bombou - toho se dočkáme v příštích dílech) a železný (neprůchozí). Vzhledem k tomu, že mapa tedy bude dvourozměrnou čtvercovou sítí, nabízí se pro její reprezentaci v paměti použití dvourozměrného pole celých čísel (např. typu int). Číslo uložené v tomto poli na pozici [y,x] bude vyjadřovat, jaký typ čtverečku se nachází v bludišti na pozici [x;y] - obrácení písmen je záměrné, neboť jsem chtěl zachovat x-ové souřadnice na přímce jdoucí zleva-doprava a y-ové na přímce jdoucí shora-dolu. Hodnota 0 bude značit pískový čtvereček, hodnota 1 čtvereček cihlový a hodnota 2 železný. Dohodněme se, že velikost mapy stanovíme na 40x30 čtverečků. Deklarace pole pro uchování mapy pak může vypadat takto:




        int map[30][40]; //herni plan



Než budeme moci mapu vykreslit, budeme ji muset načíst do paměti. Mapy pro jednotlivé levely budou uchovány v textových souborech s příponou .map v datové složce hry. Hodnoty v poli map budeme inicializovat pomocí funkce LoadMap (volané při každé změně levelu), která načte potřebné údaje ze souboru s mapou pro aktuální level. Tělo funkce LoadMap bude vypadat následovně:




void LoadMap(const char* file_name)

{

       FILE *file;



       file = fopen(file_name,"r");



       for(int x = 0; x < 30; ++x)

       {

               for(int y = 0;y < 40; ++y)

               {

                       fscanf(file,"%d",&map[x][y]);

               }

       }

       fclose(file);

}



Funkce LoadMap postupně projíždí soubor s popisem mapy a načítá informace o typech jednotlivých políček (čtverečků) do pole map. Otevřete si soubor s mapou v poznámkovém bloku a prohlédněte si jej, případně si zkuste změnit některé zde uložené hodnoty (pozor, volte pouze hodnoty 0,1 nebo 2, nechtěl jsem kód zbytečně komplikovat ošetřením proti nesprávnému vstupu) - usnadní Vám to pochopení výše vyloženého. Příklad: V souboru 1.map je na pozici [4;1] (přesně je to tedy 7. znak na 2. řádku) uloženo číslo 0. To znamená, že na mapě je na souřadnicích [4;1] čtvereček typu 0, tedy prázdný (pískový) čtvereček. Po úspěšném načtení mapy můžeme přistoupit k jejímu vykreslení. To budeme provádět uvnitř funkce Render:




       D3DXVECTOR3 pos;



       for (int i = 0; i < 40; ++i)

       { //prochazeni ctvercu mapy

               for(int j = 0; j < 40; ++j)

               {




                       pos.x = j * 32;

                       pos.y = i * 32;

                       pos.z = 0;




                       if (OnScreen(pos))

                       {


                               square->Begin(NULL);


                               switch (map[i][j])

                               {


                               case 0:

                               {

                                   square->Draw(pSandTex,NULL,NULL,&pos,0xffffffff);

                                   break;

                               }


                               case 1:

                               {

                                   square->Draw(pBricksTex,NULL,NULL,&pos,0xffffffff);

                               break;

                               }


                               case 2:

                               {

                                   square->Draw(pIronTex,NULL,NULL,&pos,0xffffffff);

                                   break;

                               }

                               }


                               square -> End();

                               }

                       }

       }


V principu se jedná o podobnou záležitost jako při načítání mapy - procházíme sekvenčně pole map a pro každou hodnotu vykreslíme čtvereček s příslušnou texturou podle jeho typu. Pozici, na kterou se má právě kreslený sprite (čtvereček) vykreslit, předáváme pomocí struktury D3DXVECTOR3 (viz dokumentaci SDK). Předávaná pozice musí být uvedena v pixelech, proto souřadnice každého čtverečku násobíme číslem 32 - na obrazovce má totiž čtvereček rozměry 32x32 pixelů. Možná Vás zaujalo použití funkce OnScreen - tuto funkci (v podstatě by to mohlo být makro) jsme si sami vytvořili a slouží pro rozhodování, zda se právě kreslený čtvereček nachází ve viditelné části mapy. Vzhledem k velikosti okna hry totiž bude celá mapa levelu podstatně větší než okno samo, a proto nebude vždy mapa vidět celá - "kamera" se bude jakoby posouvat s pohybem hráče (probereme v dalších dílech). Čtverečky, které nebudou zrovna vidět, není nutné kreslit, protože bychom tím jen ztratili potřebný čas – proto tedy každý čtvereček před jeho případným vykreslením takto otestujeme pomocí funkce OnScreen. která může vypadat takto:




bool OnScreen(D3DXVECTOR3 pos)

{ //je objekt na pozici pos vidět?


        bool OnScreen(D3DXVECTOR3 pos) //je objekt na pozici pos videt?

        {

                if (pos.x>=-32 && pos.x<=672 && pos.y>=-32 && pos.y<=512) return true;

                /* 672 = 640 + 32 (32 je velikost čtverečku, nezapomeňte, že krajní čtverečky je potřeba také kreslit, proto 672 a 512 a nikoliv 640 a 480! ) */

                return false;

        }




Poznámka: Funkce OnScreen počítá, možná trochu krátkozrace, s rozlišením okna 640*480 pixelů. Pokud byste chtěli situaci při inicializaci Direct3D změnit, musíte provést potřebné úpravy i ve funkci OnScreen. Bylo by proto vhodné deklarovat rozlišení pomocí konstant na začátku programu - námět pro Váš domácí úkol... :-)



Co příště?



V následujícím pokračování budeme pokračovat v kreslení - vykreslíme herní postavičku a rozpohybujeme ji. Ještě nevím, jestli se toto všechno vejde do jednoho dílu, ale snad ano.
Za domácí úkol si projděte (pokud to potřebujete) dokumentaci SDK, vyzkoušejte si jiné hodnoty proměnných ve struktuře D3DPRESENT_PARAMETERS při inicializaci Direct3D a ujistěte se, že rozumíte algoritmu pro načítání a vykreslování mapy a způsobu její reprezentace v paměti. Zkuste si též změnit některé hodnoty v souboru s mapou (doporučuji někde
v levém horním rohu mapy, aby byly pozorovatelné) a sledujte jak se projeví. Budu se těšit na setkání příště!



Zdrojové kódy: 312_druha_cast.zip



Související odkazy:









jan_kohout

autor
/ jan_kohout

Publikováno: 20.05.2007


další články této kategorie





diskuze

odeslat

Nejsi automat? Napiš výsledek 4 + 1 =

Vozka | 24.06.08 v 10:28

"sice není multiplatformní, ale hraní pod linuxem není dost efektivní, jelikož oficiální ovladače pro linux mají max 1/3 výkonu co pod widlema a pokud máš neoficiální tak další 1/3 z té 1/3"

Opravdu by mě zajímalo, jak jsi přišel na tohle:))

Luďa | 23.06.08 v 17:52

jaksedá vytvořit hra

Esaras | 01.06.07 v 09:41

Patolog said: "jde především o to aby čtenář věděl jak má vypadat ten herní engine..."
No při vší úctě k tomuto seriálu, tak tento seriál tě nenaučí jak má vypadat herní engine, má ti jen ukázat jak si udělat jednoduchou hru a popisuje základní mechanismy. Kdyby ses chtěl opravdu dozvědět jak se dělá herní engine, tak by si musel hodně přitlačit na pilu a to už by vyžadovalo dobrou znalost C++, případně i winAPI(které je pro tvorbu hry ideální kvůli své rychlosti, sice není multiplatformní, ale hraní pod linuxem není dost efektivní, jelikož oficiální ovladače pro linux mají max 1/3 výkonu co pod widlema a pokud máš neoficiální tak další 1/3 z té 1/3). A zvuk můžeš udělat za pomocí knihovny fmod (která ti umožní HW akcelerovaný zvuk až v 7.1 a je jednoduchá na použití) na které běží dost komerčních her jako třeba world of warcraft, unreal tournament apod.

Patolog | 24.05.07 v 16:14

taky bych dal raději přednost SDL...ale asi je to jedno..jde především o to aby čtenář věděl jak má vypadat ten herní engine...

Dominik | 21.05.07 v 19:22

Jak se to pousti? :D

lifo | 21.05.07 v 11:05

No volba API mi nepripada nejako extra dobra, Winapi a DX su veci ktore niesu moc univerzalne, standardne a ani jednoduche. Nebolo by lepsie SDL a OpenGL? To chape skoro kazdy a bezi to na kazdom operacnom systeme.

Někdo | 20.05.07 v 11:54

XD místo přípravy na maturitu, tohle