PŘÍRODOVĚDECKÁ FAKULTA UNIVERZITY PALACKÉHO KATEDRA INFORMATIKY
BAKALÁŘSKÁ PRÁCE
Trojrozměrná závodní hra s podporou hraní po síti
2011
David Konečný
Anotace Počítačové hry se těší velké popularitě. Obecně se jedná o software zprostředkovávající zábavnou aktivitu s danými pravidly. Závodní hry se vyznačují snahou překonat ostatní hráče v rychlosti. Vytvářel jsem leteckou závodní hru proti živým hráčům. Závodění s ostatními hráči je realizováno pomocí hry po síti. Hru jsem vytvářel v jazyku C++, k reprezentaci herní situace jsem využil volně dostupné grafické a zvukové knihovny. Podařilo se mi vytvořit klientskou i serverovou část hry. K hernímu serveru se mohou připojit hráči a závodit spolu. Na hře by šlo ještě dále pracovat, například na zlepšení grafické stránky, nebo na vylepšení síťové komunikace.
Děkuji Mgr. Petru Osičkovi za vedení této bakalářské práce.
Obsah 1. Počítačová hra 1.1. Společné prvky her . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Závodní hra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Použité prostředky 2.1. Jazyk C++ . . . . . . . . . 2.2. Irrlicht . . . . . . . . . . . . 2.2.1. Použití . . . . . . . . 2.2.2. Reprezentace modelů 2.3. OpenAL . . . . . . . . . . . 2.4. ENet . . . . . . . . . . . . . 3. Hra Flyable 3.1. Princip hry . 3.2. Ovládání . . . 3.2.1. Server 3.2.2. Klient
. . . .
. . . .
. . . .
. . . .
. . . .
4. Serverová část 4.1. Funkce . . . . . . . . . 4.2. Struktura . . . . . . . 4.3. Třídy . . . . . . . . . . 4.3.1. ServerStatistics 4.3.2. Server . . . . . 4.3.3. ServCom . . . . 4.3.4. GameLogic . . 5. Klientská část 5.1. Struktura . . . . . . . 5.2. Třídy . . . . . . . . . . 5.2.1. Manager . . . . 5.2.2. GameCore . . . 5.2.3. NetGame . . . 5.2.4. TrainingGame . 5.2.5. Checkpoints . . 5.2.6. AirplaneBase . 5.2.7. AirplanePlayer 5.2.8. AiplaneNet . . 5.2.9. Client . . . . . 5.2.10. ClientCom . . . 5.2.11. Statistics . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
4
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
. . . . . .
. . . .
. . . . . . .
. . . . . . . . . . . . .
7 7 8
. . . . . .
9 9 9 9 10 11 11
. . . .
12 12 12 12 13
. . . . . . .
14 14 15 15 16 17 18 19
. . . . . . . . . . . . .
20 20 20 21 22 24 24 24 25 26 27 27 27 30
5.2.12. 5.2.13. 5.2.14. 5.2.15. 5.2.16. 5.2.17.
ControlsLayout AudioSample . AudioLayer . . Effects . . . . . Configuration . GUI . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
31 32 32 33 33 33
6. Postup pro sestavení programů
36
Závěr
37
Conclusions
38
Reference
39
A. Obsah přiloženého CD
40
5
Seznam obrázků 1. 2. 3. 4. 5.
Stavový diagram serveru. . . . . Struktura serveru. . . . . . . . . Struktura třídy Manager. . . . . Struktura třídy NetGame. . . . Struktura třídy TrainingGame.
6
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
14 16 20 21 21
1.
Počítačová hra
Cílem této bakalářské práce bylo vytvořit arkádovou závodní počítačovou hru hranou po síti. V této kapitole je popsáno, o co přesně se jedná. Počítačová hra je hra hraná na počítači. Pojem ”hra” ale nemá žádnou jasnou definici. Definicí tohoto pojmu se budu dále zabývat podle [1]. Několik definic od různých autorů zní: • Hra má ”cíle a prostředky”: úkol, výsledek a skupinu pravidel k jejich dosažení.(David Parlett) • Hra je činnost, zahrnující rozhodování hráče, hledání cílů v rámci ”omezeného obsahu” [tj. pravidel]. (Clark C. Abt) • Hra má šest vlastností: je ”volná” (hraní je dobrovolné a nezávazné), ”oddělená” (předem zasazena do prostoru a času), má neurčitý závěr, je ”neplodná” (ve smyslu že nevytváří žádné zboží ani bohatství – včetně smluvených výměn bohatství mezi hráči, které ovšem nevytváří), je ovládána pravidly a je ”uvěřitelná” (doprovázena vědomím, že hra není skutečný život, ale jistým způsobem sdílí oddělenou ”realitu”). (Roger Callois) • Hra je ”dobrovolné překonávání nepotřebných překážek.” (Bernard Suits) • Hry mají čtyři vlastnosti. Jsou ”uzavřeným, formálním systémem” (je to hezčí způsob, jak říct, že mají pravidla); zahrnují interakci; zahrnují konflikt; a nabízí bezpečí alespoň ve srovnání s tím, co reprezentují. (Chris Crawford) • Hry jsou ”forma umění, jejíž účastníci, zvaní hráči, dělají rozhodnutí s cílem spravovat zdroje skrze možnosti hry ve snaze dosáhnout cíle.” (Greg Costikyan) • Hry jsou ”systém, v němž se hráči angažují v umělém konfliktu s danými pravidly, na jehož konci je měřitelný výsledek”. Žádná z těchto definic nepopisuje dokonale všechny druhy her. Například hry, které nemají žádný závěr, nebo jednoznačný cíl (například role-play hry jako Dungeon & Dragons), do některých definicí nespadají.
1.1.
Společné prvky her
Z předchozích definic vyplývají některé společné prvky her: • Hra je aktivita • Hry mají pravidla 7
• Hry obsahují konflikt • Hry mají cíle • Hry zahrnují tvorbu rozhodnutí • Hry jsou umělé, jsou bezpečné a stojí mimo běžný život • Hry nevytváří na straně hráčů žádný materiální zisk • Hry jsou dobrovolné • Hry mají nejistý výsledek • Hry jsou reprezentací, nebo simulací něčeho reálného, ale samy jsou přitom uvěřitelné • Hry jsou neefektivní (kladou překážky) • Hry mají systémy (uzavřený systém, který znamená, že informace neproudí mezi hrou a reálným světem) • Hry jsou druhem umění
1.2.
Závodní hra
Závodní hra je druh hry, ve kterém se hráč účastní závodění nějakých dopravních prostředků. Tyto prostředky mohou být jak pozemní, tak vzdušné nebo vodní. Většinou se závodí na předpřipravených trasách. Hra má jasně daného vítěze, je jím nejčastěji ten hráč, který překoná své protivníky v čase, za který dorazil do cíle. Závodní hry lze rozdělit dle [2] do dvou kategorií: • Závodní simulátor • Arkádové závodní hry Závodní simulátory se soustřeďují na co nejpřesnější fyzikální model daného dopravního prostředku. Snahou je, aby závodění bylo pokud možno nejreálnější a vyvolávalo co nejvěrnější iluzi skutečnosti. Často i simulují skutečné závodní události, jako je například Formule 1. Arkádové závodní hry se naproti tomu soustřeďují především na zábavnost. Reálnost závodů je odsunuta do pozadí. Často se v takovýchto hrách vyskytují různé ”power-upy” rozmístěné po závodní trati, které přináší závodníkům různá zvýhodnění. Někdy se také závodí s různými groteskními dopravními prostředky namísto skutečných.
8
2.
Použité prostředky
Tato práce vznikla v programovacím jazyku C++ za použití externích knihoven Irrlicht, OpenAL a ENet. Tato kapitola popisuje tyto prostředky. Vývojové prostředí bylo použito Microsoft Visual Studio 2008.
2.1.
Jazyk C++
C++ je objektově orientovaný programovací jazyk [3]. Vznikl v roce 1983 a podporuje několik paradigmat (například procedurální, objektové), není tedy čistě objektovým jazykem. V současné době patří mezi nejrozšířenější programovací jazyky. Jazyk C je podmnožinou jazyku C++.
2.2.
Irrlicht
Irrlicht je open source 3D grafický engine napsaný v jazyku C++. Umožňuje zobrazování grafiky pomocí DirectX, OpenGL nebo vlastním softwarovým rendererem. Je přenosný mezi platformami. Použitá verze 1.7.2 umí například: • Pracovat s modely ve formátech .obj, .3ds, .md2, .X,. . . • Pracovat s texturami ve formátech .bmp, .png, .jpg, .psd, .tga,. . . • Detekovat kolize • Přehrávat animace modelů • Částicové efekty, billboardy,. . . • Používat pixel, vertex a geometry shaderů • atd. Dále používá vlastní souborový formát .irr, který se používá pro popis scény, kterou Irrlicht vykresluje. Jedná se o XML. Engine dokáže do tohoto formátu zapisovat i z něj načítat. Pro přehledné vytváření scén v tomto formátu existuje program CopperCube, který vychází z nyní již nevyvíjeného irrEdit. 2.2.1.
Použití
Základním prvekem enginu je objekt typu IrrlichtDevice, vytvářen funkcí createDevice: IrrlichtDevice* irr::createDevice( video::E_DRIVER_TYPE deviceType = video::EDT_SOFTWARE, const core::dimension2d< u32 > &windowSize = 9
(core::dimension2d< u32 >(640,480)), u32 bits = 16, bool fullscreen = false, bool stencilbuffer = false, bool vsync = false, IEventReceiver *receiver = 0 ) deviceType udává zařízení, které bude použité pro vykreslování grafiky (může nabývat hodnot video::EDT SOFTWARE, video::EDT NULL, video::EDT DIRECT3D9, video::EDT DIRECT3D8, video::EDT OPENGL a video::EDT BURNINGSVIDEO). windowSize udává rozlišení, ve kterém bude engine pracovat bits udává barevnou hloubku fullscreen udává zda bude obraz roztáhnut přes celou obrazovku stencilbuffer udává, zda bude použit stencil buffer [4] vsync udává, zda bude synchronizována frekvence vykreslování snímků s frekvencí monitoru receiver je ukazatel na objekt typu IEventReceiver, který se používá k odchytávání událostí Instance typu IrrlichtDevice poté ukazatele instance tří důležitých tříd: • IGUIEnvironment - používá se pro práci s gui, spravuje fonty atd. • ISceneManager - pro práci se scénou, také zprostředkovává detekci kolizí • IVideoDriver - vykreslování jednotlivých 2D a 3D objektů na obrazovku 2.2.2.
Reprezentace modelů
Pro reprezentaci modelů a všech objektů na scéně se používají objekty tříd, které dědí z třídy ISceneNode. Nejpodstatnějších funkce třídy ISceneNode jsou: • Nastavení textur • Nastavení pozice • Nastavení rotace 10
• Nastavení velikosti • Nastavení viditelnosti • Nastavení animátorů Nody (objekty typu ISceneNode) jsou uspořádány hierarchicky. Každá noda má svého rodiče a může mít i své potomky. Kořenovou nodu vytváří třída ISceneManager. Třídy, které dědí z ISceneNode jsou například: • IMeshSceneNode - pro reprezentaci modelů • IAnimatedMeshSceneNode - pro reprezentaci animovaných modelů • ICameraSceneNode - pro reprezentaci kamery, podle níž je vykreslován výsledný obraz • ILightSceneNode - pro reprezentaci zdroje světla • IBillboardSceneNode - pro reprezentaci billboardu (2D obrázek umístěný v 3D scéně) • ITextSceneNode - pro reprezentaci textu ve scéně • atd.
2.3.
OpenAL
OpenAL je API pro přehrávání zvuků ve 3D prostoru, určené především pro použití ve hrách. Je přenosné mezi platformami. Základní elementy tohoto API jsou Listener, Source a Buffer. Source je zdroj zvuku s definovanou pozicí v 3D prostoru. Může jich být více. Buffer obsahuje zvuková data, může jich být velké množství. Několik zdrojů zvuku může přehrávat jeden Buffer. Listener je posluchač s definovanou pozicí v 3D prostoru. Může být jen jeden. Výsledný zvuk je vytvářen z pohledu posluchače.
2.4.
ENet
ENet je aplikační vrstva nad transportním protokolem UDP. Oproti samotnému protokolu UDP umožňuje i spolehlivý přenos packetů při zachování správného pořadí došlých packetů. Vrstva umožňuje otevřít několik kanálů pro jedno spojení, po kterých jsou posílána data. Dále umožňuje rozlišovat, ze kterého kanálu přišla jaká data.
11
3.
Hra Flyable
3.1.
Princip hry
Jedná se o leteckou arkádovou závodní hru pro více hráčů. Každý hráč ovládá letadlo, které vidí z pohledu třetí osoby (kamera je umístěna za letadlem). Cílem je prolétnout checkpointy, v podobě kruhů, které jsou rozmístěné po herním světě. Vítězem je hráč, který prolétne všemi checkpointy za nejkratší dobu. Do běžící hry se může kdykoliv připojit další hráč, stejně tak se může hráč ze hry odpojit. Pokud ale po odpojení hráče není ve hře dostatek hráčů, hra nepokračuje. Každé kolo hry trvá určitý čas, po kterém se vynulují výsledky a pokud hráči chtějí, začne další kolo. Během hry mohou hráči využívat chat a psát si tak krátké zprávy mezi sebou. Tento popis platí pro hru více hráčů přes internet. Hra má ještě jeden herní režim, trénink. Při tréninku hraje hráč sám a není omezován časem. Neleze při něm také využívat chat.
3.2. 3.2.1.
Ovládání Server
Server je možné spustit s parametry, ty mohou být následující: • -maxp - Maximální počet hráčů, kteří se mohou připojit k serveru. • -minp - Minimální počet hráčů připojených k serveru nutných pro zahájení hry. • -rl - Délka jednoho herního kola ve vteřinách. Všechny parametry jsou nepovinné. Spuštěný server je možné dále ovládat následujícími textovými příkazy: • help - zobrazí seznam příkazů • players - zobrazí seznam hráčů • state - zobrazí stav, ve kterém se právě nachází hra • kick id - odpojí hráče s daným id • quit - vypne server
12
3.2.2.
Klient
Klientská aplikace rozpoznává dva vstupní parametry, první určuje, zda se aplikace spustí v režimu přes celou obrazovku. Může nabývat hodnoty w pro spuštění v okně, nebo f pro spuštění přes celou obrazovku. Druhý parametr udává rozlišení, ve kterém se hra pustí a může nabývat těchto hodnot: • 800x600 • 1024x768 • 1280x720 • 1920x1080 • auto - Automatické rozlišení, přizpůsobí se rozlišení, které je nastavené na počítači. Parametry jsou nepovinné. Implicitní nastavený je režim přes celou obrazovku s automatickým rozlišením. Po spuštění klienta se objeví herní menu, ze kterého je možné připojit se k serveru, zahájit trénink, nebo hru vypnout. Menu je ovládáno výhradně kurzorem, pouze k zadávání textu se používá klávesnice. Při samotné hře hráč ovládá letadlo. K dispozici jsou tyto klávesy: • Směrová šipka nahoru, dolů, doleva, doprava - zdvih, klesání, rotace doleva, rotace doprava • Klávesa R - restart letadla na původní pozici • Klávesa TAB - při podržení klávesy jsou zobrazeny časy všech hráčů • Klávesa ENTER - při stisknutí klávesy se aktivuje chat, do kterého může hráč psát zprávy pro ostatní hráče (v té chvíli nelze letadlo ovládat), zpráva se odesílá opětovným stiskem klávesy ENTER, chat se deaktivuje klávesou ESC nebo odesláním zprávy Hra se vypíná tlačítkem s křížkem, v pravém horním rohu obrazovky. Toto tlačítko vrátí uživatele do hlavního menu.
13
4.
Serverová část
Tento projekt se skládá ze dvou programů, klienta a serveru. Úkolem serveru je uchovávání seznamu připojených klientů s informacemi o nich. Dále řízení průběhu hry a zprostředkovávání informací o stavu hry klientům. V poslední řadě umožňuje správci serveru alespoň základní možnosti sledování a administrace hry. Jedná se o konzolovou aplikaci.
4.1.
Funkce START SERVERU
JE DOSTATEK HRACU? NE ANO
HRA
PROC SKONCILA HRA? NEDOSTATEK HRACU VYPRSEL CAS
NE
CHTEJI VSICHNI HRACI HRAT ZNOVU? ANO
Obrázek 1. Stavový diagram serveru. Server se v každé chvíli nachází v jednom jednoznačně daném stavu. Na 1. obrázku je vyznačený stavový diagram, podle kterého probíhá přepínání těchto stavů. Po spuštění server čeká na klienty, potom, co je připojen dostatečný počet hráčů, je spuštěna hra. Ta může skončit dvěma způsoby, buď po odpojení některého klienta nastane situace, kdy je málo hráčů, nebo vyprší čas stanovený pro jedno kolo hry. V prvním případě čeká server opět až se připojí požadovaný počet klientů. V případě druhém se čeká, až všichni připojení hráči budou chtít hrát znovu, poté začíná další kolo hry. 14
Další funkcí serveru je zajištění, aby všichni hráči měli přehled o herní situaci. Každý klient posílá serveru pravidelně informace o své situaci v herním světě (poloha, časy, . . .). Tyto informace server přečte a pokud je to potřeba, přepošle je ostatním klientům, kteří je dále zpracují. Server také umožňuje během hry odpojovat hráče a sledovat stav hry a připojených hráčů. Připojení klienta k serveru probíhá takto: 1. Klient se připojí k serveru 2. Server zašle klientovi všechny potřebné informace (seznam hráčů, nastavení checkpointů,. . .) 3. Klient pošle serveru informace o sobě (přezdívka, aktuální pozice,. . .) 4. Server zašle všem ostatním hráčům tyto informace od klienta Odpojení klienta od serveru: 1. Klient se odpojí od serveru 2. Server odchytí událost odpojení klienta a pošle tuto informaci všem připojeným hráčům Odpojení klienta od serveru (vyhození serverem): 1. Server pošle klientovi zprávu o odpojení 2. Klient odpojení serveru potvrdí 3. Server odchytí událost odpojení klienta a pošle tuto informaci všem připojeným hráčům
4.2.
Struktura
Na 2. obrázku lze vidět jednotlivé komponenty serveru, včetně toho, jaké komponenty komunikují s kterými. main je hlavní funkce programu, která přečte vstupní parametry a spustí server. Ostatní komponenty na obrázku jsou instance tříd, které budou popsány dále.
4.3.
Třídy
Popis tříd používaných serverem.
15
ServerStatistics
ServCom
GameLogic main
Server
Obrázek 2. Struktura serveru. 4.3.1.
ServerStatistics
ServerStatistics je třída pro uchování a zprostředkování časů průletů jednotlivými checkpointy všemi hráči. Třída využívá návrhový vzor singleton [5], pro celý program tedy existuje její jediná instance, přístupná v celém programu. Časy hráčů jsou uchovány v datovém typu times: typedef map
*> times; Klíčem tohoto mapu je id hráče, uchovávány jsou ukazatele na vektory s jednotlivými časy. Třída má dvě kolekce typu times, jsou to stats a finalStats. Dalšími důležitými proměnnými jsou checkpointsCount typu int, pro počet checkpointů na trati. A newTimes pro reprezentaci hráčů, u kterých je naměřen nový čas. Nejdůležitější funkci této třídy je SetTime, sloužící pro uložení času do statistik: void SetTime(long ticks, int checkpoint, int playerId); Kde ticks je ukládaný čas, checkpoint je číslo checkpointu, ke kterému se čas ukládá a playerId je ID hráče, od kterého je tento čas. Funkce ukládá časy do 16
proměnné stats. Pokud je uloženo tolik časů, jako je počet checkpointů, je poslední čas konečná doba letu hráče. Pokud je tento čas menší, než čas uložený v proměnné finalStats pro daného hráče (nebo v ní ještě nejsou uloženy žádné hodnoty), uloží se naměřené časy do finalStats. Kolekce finalStats tedy obsahuje nejlepší časy každého hráče. Pokud se časy u některého hráče změní, zapíše se tato změna do proměnné newTimes, díky čemuž lze zjistit, kteří hráči mají nové naměřené časy. K tomu slouží funkce HasPlayerNewTime: bool HasPlayerNewTime(int id); Kde id je ID hráče. Funkce vrací true, pokud byla u hráče s tímto ID naměřena nová hodnota. Nové naměřené hodnoty kontroluje instance třídy GameLogic (4.3.4.), který když zjistí změnu, zašle nové hodnoty klientům. K přístupu k časům z kolekce finalStats slouží funkce GetTimes: long *GetTimes(int playerId); Kde playerId je ID hráče. Funkce vytváří a vrací pole časů hráče s daným ID. 4.3.2.
Server
Třída Server vytváří pomocí aplikační vrstvy ENet server, ke kterému se mohou připojovat klienti. Obsluha došlých packetů je prováděna ve vláknu, které tato třída spouští. Třída má ukazatel na třídu typu ServCom(4.3.3.), které předává došlé packety a informace o připojení nebo odpojení klientů. Důležitou proměnnou této třídy je proměnná server typu ENetHost, ve které je, mimo jiné, uložený seznam všech připojených klientů. Každý klient je uložený v datovém typu ENetPeer, který, mimo jiné, umožňuje uložení vlastních dat. Nejdůležitější funkcí třídy je funkce SendData: void SendData(const void* message, int length, ENetPeer *peer, int channel, bool reliable=true); A funkce SendDataToAll : void SendDataToAll(const void* message, int length, int channel, int notID=-1, bool reliable=true); SendData slouží k zaslání určitých dat jednomu klientovi, SendDataToAll slouží k zaslání určitých dat všem klientů (je možné jednoho klienta vynechat). Parametr message je ukazatel na data, která budou odeslána, length je velikost těchto dat, peer je ukazatel na klienta, kterému budou data odeslány, channel je číslo kanálu, na kterém budou data odeslána, notID je ID hráče, kterému data odeslány nebudou a reliable udává, zda budou data odeslána spolehlivě (bude se kontrolovat jejich doručení, popřípadě se odešlou znovu). 17
4.3.3.
ServCom
Hlavním úkolem třídy ServCom je reagovat na příchozí packety a události, které jí předává instance třídy Server (4.3.2.). Tuto instanci třída ServCom vytváří ve svém konstruktoru a předává ji ukazatel na sebe sama. Při připojení nového klienta je zavolána funkce AddNewClient: void AddNewClient(ENetPeer *peer); Kde peer je ukazatel na nově připojeného klienta. Tato funkce zajišťuje následující: 1. Novému klientovi je odeslán seznam připojených hráčů 2. Novému klientovi je odeslán aktuální stav hry 3. Novému klientovi je určeno jednoznačné ID a uloženo k němu 4. Novému klientovi jsou odeslány informace o serveru (počet checkpointů, ID nového klienta) 5. Novému klientovi jsou odeslány pozice checkpointů 6. Novému klientovi jsou odeslány časy všech ostatních hráčů Není třeba odesílat informaci o nově připojeném klientovi všem ostatním klientům, to se provede až ve chvíli, kdy nový klient pošle na server informace o sobě ( svoji přezdívku, aktuální pozici,. . .). Obsluhu příchozích packetů obstarává funkce ReceivePacket: void ReceivePacket(ENetPeer *peer, ENetPacket *packet, int channel); Kde peer je ukazatel na klienta, od kterého packet přišel, packet je ukazatel na příchozí packet, channel je číslo kanálu, přes který packet přišel. Funkce rozeznává packety podle kanálu, přes který došly. Packet může obsahovat • Pozici letadla klienta • Zprávu klienta z chatu • Informace o klientovi (nick,. . .) • Signál (dále se dělí) • Čas průletu checkpointem
18
Podle typu se packet poté dál zpracovává. Packety s pozicí letadla klienta nebo s informacemi o klientovi se uloží ke klientovi na serveru a zároveň se přepošlou všem ostatní hráčům. Zpráva z chatu se pouze přepošle. Čas průletu checkpointem uloží server do statistik. Signál může obsahovat zprávu o havarování nebo obnovení letadla klienta, zpravidla se přeposílá ostatním klientům, jen signál signalizující připravenost klienta pro další hru se nepřeposílá, ale server si tuto informaci uchovává. Při odpojení klienta je zavolána funkce RemoveClient: void RemoveClient(ENetPeer *peer); Kde peer je ukazatel na odpojeného klienta. Všem uživatelům se odešle informace o odpojeném uživateli a ten se uvolní ze serveru. 4.3.4.
GameLogic
Třída GameLogic zprostředkovává herní logiku. Rozhoduje o herních stavech, počítá čas kola a zprostředkovává časy průletů checkpointů určitého hráče ostatním hráčům. Třída má k dispozici instanci třídy ServCom(4.3.3.) a jejím prostřednictvím i instanci třídy Server (4.3.2.). V proměnné gameState má třída uložený aktuální stav hry. Může nabývat hodnot ngsNull (žádný stav), ngsRunning (běžící hra), ngsWaiting (čekání na hráče), ngsResults (konec hry spojený se zobrazením výsledků, čekání, až budou všichni hráči hrát znovu). Při vytvoření třídy se stav nastaví na ngsWaiting. Třída se poté chová podle aktuálního stavu následovně: • ngsRunning: třída počítá uběhnutý čas a pravidelně ho zasílá všem hráčům, aby byli synchronizovaní. Dále kontroluje, jestli je připojen potřebný počet klientů, pokud není, změní stav na ngsWaiting. Také je kontrolován uběhnutý čas kola, když uběhne, změní stav na ngsResults. V poslední řadě kontroluje, jestli se u některého hráče nezměnily časy ve statistikách (třída Statistics(5.2.11.)), pokud ano, zašle nové časy všem klientům. • ngsWaiting: třída kontroluje počet hráčů, ve chvíli, kdy je připojen požadovaný počet, změní stav na ngsRunning • ngsResults: třída kontroluje, jestli chtějí všichni připojení hráči hrát, pokud ano a je připojen dostatečný čas hráčů, změní stav na ngsRunning, pokud není dostatek hráčů, tak na ngsWaiting Pokud není připojen žádný hráč, třída nevykonává žádnou činnost.
19
5.
Klientská část
Hlavním úkolem klientské části je zprostředkování herní situace, o které informuje server, hráči a umožnění hráči interakci. Prostřednictvím klientského programu hráč ovládá své letadlo a program tyto informace odesílá na server. Jedná se o tlustého klienta, který vykonává spoustu vlastní výpočtů a umožňuje hráči hrát i bez připojení k serveru.
5.1.
Struktura
Hlavní třída klienta se jmenuje Manager. Instanci této třídy vytváří a spouští hlavní funkce programu main. Funkce main předává také této instanci některé své vstupní parametry. NetGame/ TrainingGame MANAGAER
GUI_MainMenu
GUI
Obrázek 3. Struktura třídy Manager. Instance třídy Manager vytváří postupně další objekty, jejichž funkce poté volá, jak je naznačeno na 3. obrázků. Těm předává události, jako je stisknutí kláves a podobně. Manager má vždy k dispozici instanci třídy GUI (uživatelské rozhraní). Podle situace vytváří Manager objekt pro hru (síťovou hru nebo trénink), nebo objekt, reprezentující hlavní menu, v instanci třídy GUI. Pro síťovou hru existuje třída NetGame, pro trénink třída TrainingGame. Obě tyto třídy dědí z třídy GameCore. Každá z těchto tříd opět vytváří několik objektů, kterým zasílá zprávy, jak je znázorněné pro NetGame na 4. obrázku a pro TrainingGame na 5. obrázku. Kromě tříd z obrázků používá hra ještě singletony pro zvuk, efekty, rozložení kláves, statistiku a nastavení přezdívky a adresy serveru.
5.2.
Třídy
Popis tříd používaných klientem. 20
GUI GUI elementy...
NetGame AirplanePlayer
AirplaneNet
ClientCom
Client
Checkpoints
Obrázek 4. Struktura třídy NetGame. GUI GUI elementy...
TrainingGame AirplanePlayer
Checkpoints
Obrázek 5. Struktura třídy TrainingGame. 5.2.1.
Manager
Z třídy Manager vychází instance všechny ostatních tříd, dalo by se říci, že je to ”kořenová” třída. Jejím hlavním úkolem je rozhodovat o stavu aplikace, jestli má být spuštěná hra, nebo má být zobrazené hlavní menu. Podle toho třída vytváří a uvolňuje objekty, které danou funkcionalitu obstarávají. Hlavní menu zajišťuje třída GUI MainMenu(5.2.17.), hru zajišťují třídy NetGame(5.2.3.) (hra po síti) a TrainingGame (tréninková hra), které dědí z třídy GameCore. Třída také odchytává výjimky a reaguje na ně, pokud nějaké nastanou. Výjimky se odchytávají dvěma způsoby. První je standardní sekce try a catch. Některé výjimky ale využívají logovací systém enginu Irrlicht. Zalogování chyby vyvolá událost, kterou Manager odchytí a poté ji zpracuje. Odchycená výjimka je zobrazena uživateli, následně je aplikace ukončena. Dalším úkolem této třídy je zajistit, aby rychlost hry nebyla závislá na rychlosti vykreslování. Tedy aby interval aktualizace herního světa byl pořád stejný. Je pevně dán počet aktualizací herního světa za vteřinu a to konstantou CHANGES PER SEC. Udržování této nezávislosti je součástí herního cyklu ve funkci Run: void Run(); 21
Cyklus probíhá dokud není ukončena hra. Má k dispozici proměnné timeStamp, deltaTime a updateFramesCount. Na začátku jsou všechny proměnné vynulovány, jen v timeStamp je uložen aktuální čas. Herní cyklus se skládá z následujících kroků: 1. Pokud je zalogována chyba, zobrazí chybu a skočí na začátek cyklu 2. Do deltaTime zapíše rozdíl mezi aktuálním časem a timeStamp 3. Do timeStamp zapíše aktuální čas 4. Z deltaTime a konstanty CHANGES PER SEC vypočítá, kolikrát se má aktualizovat herní svět (vznikne neceločíselná hodnota) a výsledek přičte k updateFramesCount 5. Pokud je hodnota updateFramesCount větší nebo rovna jedné, hra nebo hlavní menu (záleží na stavu, ve kterém se aplikace nachází) se aktualizuje a to tolikrát, kolik je celá část z čísla updateFramesCount, ta se z této proměnné poté odečte 6. Vykreslí se hra nebo hlavní menu (záleží na stavu aplikace) 7. Skok na začátek cyklu Tímto je zajištěno, že herní svět je aktualizován jen pokud je třeba a rychlost aktualizace není ovlivněna rychlostí vykreslování. Konstanta CHANGES PER SEC je nastavena na hodnotu 30, aby byly změny nepostřehnutelné pro lidské oko. 5.2.2.
GameCore
Z třídy GameCore dědí třídy NetGame(5.2.3.) a TrainingGame(5.2.4.). GameCore obsahuje funkcionalitu shodnou pro obě tyto třídy. Obsahuje objekt plane typu AirplanePlayer (5.2.7.), který reprezentuje letadlo hráče. Tomuto objektu předává stisknuté klávesy, které dostává od instance třídy Manager (5.2.1.). Dále tato třída při svém vytvoření přidá na herní scénu kameru, kterou sleduje hráčovo letadlo. Další důležitou vlastností této třídy je vytvoření objektu checkpoints typu Checkpoints(5.2.5.), který se stará o reprezentaci checkpointů na scéně. O naplnění tohoto objektu checkpointy se ale starají až potomci třídy GameCore. Také si tato třída vytváří instanci třídy GUI (5.2.17.). Třída GameCore může nabývat několika stavů. Tyto stavy jsou reprezentovány výčtovým typem gameState s těmito hodnotami, z nichž každá reprezentuje určitý stav:
22
• gsGame - hra běží, hráč závodí • gsConnecting - klient se připojuje k serveru • gsWaitForPlayers - čeká se, až bude k serveru připojeno dostatečné množství hráčů • gsDead - hráč havaroval, čeká se na jeho restart • gsToExit - hra je z nějakého důvodu ukončena (například nečekané odpojení od serveru), čeká se na stisk mezerníku pro návrat do hlavního menu • gsCountdown - odpočítávání před startem hráče, tento stav nastává po začátku hry, nebo po restartu hráče • gsResults - jsou zobrazeny výsledky odehraného kola, stiskem mezerníku může hráč souhlasit s hraním dalšího kola Potomek TrainingGame využívá jen některé z těchto stavů (zpravidla jde o stavy, které nesouvisí s připojením k serveru). Dvě nejdůležitější funkce této třídy jsou Update a Draw : void Update(u32 framesCount); void Draw(); Kde framesCount je počet snímků, které je potřeba dopočítat od posledního volání této funkce. Ve funkci Update se rozhoduje o některých stavech hry (o některých rozhoduje až NetGame) a aktualizuje se letadlo, gui a pozice kamery. Také se v ní měří čas ve vteřinách kvůli některým stavům, které trvají jen určitý časový interval (například gsCountdown). Ve funkci Draw se volá vykreslování herní scény a vykreslování gui. Dále má třída trojici funkcí MouseClick, KeyDown, KeyUp: void MouseClick(int x, int y); void KeyDown(EKEY_CODE key); void KeyUp(EKEY_CODE key); Kde x a y jsou souřadnice kurzoru myši, kde bylo kliknuto, a key je kód tlačítka, které bylo stisknuto, nebo puštěno. Pomocí těchto funkcí předává instance třídy Manager některé události. Při kliknutí myši se kontroluje, jestli nebylo kliknuto na tlačítko pro ukončení v pravém horním rohu. V případě že ano, je proveden návrat do hlavního menu. Ve funkcích KeyDown a KeyUp se stisknuté klávesy především překládají na akce pomocí třídy ControlsLayout(5.2.12.) a posílají dále instanci třídy PlayerAirplane pro ovládání letadla. 23
5.2.3.
NetGame
Třída NetGame dědí z třídy GameCore(5.2.2.) a její funkcí je zajistit hru s dalšími živými hráči po síti. Ve svém konstruktoru třída vytváří instanci třídy ClientCom(5.2.10.), jejíž funkcí je zprostředkování síťové komunikace. Při vytvoření jsou této instanci předány, mimo jiné, ukazatele na objekt plane a checkpoints, aby mohl objekt nastavovat checkpointy, které dostane od serveru a posílat na server pozici hráčova letadla. Ve funkci Update, kterou třída NetGame dědí od svého předka, se volá aktualizace instance třídy ClientCom, ve které se provádí výše zmíněné úkony (odesílání pozice letadla a nastavování checkpointů, je-li třeba). Tyto činnosti probíhají ve funkci Update, aby byla zajištěna synchronizace vláken. Dále funkce Update předává instanci třídy ClientCom zprávy z chatu na odeslání a zjišťuje z ní aktuální stav hry, který instance přijala od serveru. 5.2.4.
TrainingGame
Funkcí třídy TrainingGame je zprostředkování tréninkové hry pro jednoho hráče. Dědí z třídy GameCore(5.2.2.) Jejím úkolem je tedy v omezené míře simulovat hru po síti a tedy vykonávat některé z funkcí serveru. Díky tomu, že není tréninková hra časově omezená, ani se nemusí čekat na připojení hráčů, není třeba simulovat přepínání stavu. Je ale potřeba nastavit všechny checkpointy, protože jejich pozice není možné získat ze serveru. Toto nastavení provede třída při svém vytvoření. Další důležitá funkce, kterou jinak provádí server, je počítání a ukládání časů hráče a jejich případné přesunování do statistik. Díky tomu, že třída má na starosti jen jednoho hráče, nemusí být struktura pro časy složitá. V praxi pro tento účel slouží jednorozměrné pole typu long. Pole je tak velké, kolik je na scéně checkpointů a po jeho naplnění (tedy po prolétnutí všech checkpointů), se časy přesunou do statistik (třída Statistics(5.2.11.), pokud je výsledný čas lepší, než doposud uložený. 5.2.5.
Checkpoints
Třída Checkpoints spravuje checkpointy v herním světě. Také zaznamenává časy průletů checkpointy a předává je třídě Statistics(5.2.11.). Checkpointy se musí prolétat v určeném pořadí, pokud hráč prolétne checkpoint, bez toho, aby prolétl checkpoint jemu předcházející, průlet se nepočítá. Časy průletů checkpointy jsou získávány funkcí clock : clock_t clock(); 24
Funkce vrací počet hodinových tiknutí od spuštění programu. Typ clock t je přetypovaný typ long. Skutečný čas se z těchto hodnot přepočítává pomocí konstanty CLOCKS PER SEC [6]. Checkpointy jsou reprezentovány objekty ISceneNode uloženými v kolekci vector. Pro přidávání checkpointů na scénu slouží funkce AddCheckpoint: void AddCheckpoint(core::vector3df position, core::vector3df rotation=core::vector3df()); Kde position je pozice nového checkpointu a rotation je natočení tohoto checkpointu. Funkce vytvoří dva objekty typu ISceneNode, první těleso je viditelný kruh, kterým má hráč prolétnout, na tento kruh nejsou testovány kolize. Druhý objekt představu neviditelnou výplň kruhu, na kterou se testují kolize. Tento druhý objekt má jako svého předka uvedený první objekt, díky čemuž je na stejné pozici a pokud je zrušen předek, je zrušen i potomek. Pří průletu letadla checkpointem zavolá instance třídy AirplanePlayer (5.2.7.), která kontroluje kolize, funkci CheckCheckpoint: void CheckCheckpoint(const scene::ISceneNode *checkpoint); Kde checkpoint je ukazatel na daný checkpoint. Funkce zkontroluje, jestli je tento checkpoint v danou chvíli na řadě k průletu. Pokud ano, průlet se započítá do statistik a tento checkpoint se zneviditelní. Vždy jsou vidět následující dva checkpointy (pokud ještě zbývá tolik checkpointů, které nebyly prolétnuté), proto funkce ještě zviditelní jeden checkpoint. Pokud hráč začíná hrát znovu (například po havárii, nebo průletu všech checkpointů), instance třídy GameCore(5.2.2.) volá funkci ClearProgress: void ClearProgress(); Tato funkce navrátí checkpointy do původního stavu, zobrazí tedy jen první dva checkpointy a vynuluje postup v jejich prolétávání. 5.2.6.
AirplaneBase
Třída AirplaneBase obaluje model letadla pohybující se po scéně. Obstarává jeho pohyb, nastavení, zvuk motoru a také stav (letí, je havarované). Dále třída počítá pozici pro kameru, aby na letadlo bylo vidět. K uložení informací o modelu letadla je použita proměnná planeNode typu ISceneNode. O samotné vykreslování letadla se stará manažer scény enginu Irrlicht. O aktualizaci pozice letadla se stará funkce Update: 25
void Update(u32 framesCount); Kde framesCount je počet snímků, které je potřeba dopočítat od posledního volání této funkce. Funkce Update při svém volání spočítá novou pozici letadla ze směru letu a nastavené rychlosti. Tuto pozici nastaví objektu planeNode. Dále funkce přepočítává pozici kamery. 5.2.7.
AirplanePlayer
Tato třída rozšiřuje třídy AirplaneBase(5.2.6.) o funkcionalitu potřebnou k ovládání letadla živým hráčem a zpracovávání kolizí letadla. Kvůli čemuž dědí nejen z třídy AirplaneBase, ale i z třídy ICollisionCallback, která slouží k odchytávání události kolize. Při vytvoření třída je nastaven objekt planeNode typu ISceneNode, aby reagoval na kolize a aby při této události byla volána funkce této třídy. Při kolizi je zavolána funkce onCollision: bool onCollision( const scene::ISceneNodeAnimatorCollisionResponse& animator); Kde parametr animator předává ukazatel na objekt zajišťující tuto kolizi a lze z něj vyčíst pozici letadla při kolizi, kolizní bod, objekt, se kterým letadlo kolidovalo a další užitečné informace. Návratová hodnota funkce onCollision udává, zda byla kolize již vyřešena (true), nebo ji má dále řešit Irrlicht (nedovolí průchod dvou kolidujících těles). Každé těleso ve scéně má určité ID. Checkpointy mají ID 1 a více, ostatní tělesa, na které se kolize testují, mají ID 0. Pokud letadlo narazí do tělesa s ID 0, nastaví se jeho stav na havarovaný, tím se letadlo skryje a na jeho místo se promítne exploze (viz. třída Effects(5.2.15.)). Pokud letadlo narazí do tělesa s ID 1 a více, tato událost se předá instanci třídy Checkpoints(5.2.5.) a kolize se dále neřeší. Aby se dalo letadlo ovládat hráčem, má třída AirplanePlayer dvojici funkcí KeyDown a KeyUp: void KeyDown(CLAYOUT_ACTIONS action); void KeyUp(CLAYOUT_ACTIONS action); Kde action udává funkci tlačítka, které je právě zmáčknuto nebo puštěno (viz. třída ControlsLayout(5.2.12.). Třída obsahuje několik proměnných typu bool, které udávají, zda je dané tlačítko stisknuto, nebo ne. Podle toho se ve funkci Update letadlo naklání do stran, nahoru, nebo dolů. 26
5.2.8.
AiplaneNet
Třída AirplaneNet slouží k reprezentaci letadla ovládaného vzdáleným hráčem. Třída dědí z třídy AirplaneBase(5.2.6.). Třída ClientCom(5.2.10.) vytváří pole instancí této třídy, každý instance reprezentuje jednoho hráče. Třída AirplaneNet má uložený ukazatel plInfo na strukturu typu NET ClientPlayerInfo, kterou třída ClientCom aktualizuje dle příchozích packetů. Podle této struktury se letadlo ovládané vzdáleným hráčem chová. Dále třída vytváří na scéně objekt typu IBillboardTextSceneNode, která umožňuje zobrazit v prostoru text. Tento text se používá pro zobrazení hráčova jména a je vázán na pozici letadla. Ve funkci Update, kterou třída dědí po svém předkovi se kontroluje, jestli se změnila pozice letadla ve struktuře, na kterou ukazuje plInfo. Pokud ano, letadlu se nastaví tato nová pozice, pokud se pozice nezměnila, nová pozice se dopočítá podle předka AirplaneBase. Funkci Update volá instance třídy ClientCom ve své funkci Update, kterou volá třída NetGame(5.2.3.) ve své funkci Update. 5.2.9.
Client
Třída Client navazuje spojení se serverem, od kterého přijímá zprávy a obsahuje i funkce pro odesílání zpráv serveru. Třída má v sobě vlákno, které se stará o příjem packetů od serveru. Při přijmutí packetu je tento předán instanci třídy ClientCom(5.2.10.), která ho dál zpracovává. Funkce přijímající packety se jmenuje CatchingEvents. Pro odesílání dat na server slouží funkce SendData: void SendData(const void* message, int length, int channel, bool reliable=true); Kde message je ukazatel na odesílaná data, length je velikost těchto dat, channel je číslo kanálu, přes který se data budou posílat (viz. 4.3.3.) a parametr reliable udává, jestli packet bude odeslán jako spolehlivý. 5.2.10.
ClientCom
Třída ClientCom slouží jako mezivrstva mezi třídami Client(5.2.9.) a NetGame(5.2.3.). Jejím hlavním účelem je zpracovávat packety doručené od serveru a posílat na server informace o hráči. Při vytvoření tato třída vytvoří instanci třídy Client, které předá ukazatel na sebe sama. Díky tomu poté dostává zprávy od serveru.
27
Třída uchovává informace o vzdálených hráčích v kolekci playerInfos typu map, kde klíčovou hodnotou je id hráče a pro uložení informací o hráči slouží struktura NET ClientPlayerInfo, která nese především informace o pozici hráče, jeho přezdívce nebo ukazatel na objekt NetAirplane (5.2.8.), kterým je reprezentováno hráčovo letadlo. Dále třída uchovává stav klienta, který reprezentuje výčtový typ clientState. Klient může nabývat těchto stavů: • csConnecting - klient se připojuje k serveru • csDisconnected - klient je odpojen od serveru • csRunning - klient je připojený k serveru a komunikace běží • csWaitingForInfo - klient čeká na informace od serveru, konkrétně počet checkpointů a jejich pozice (viz. níže) • csServerUnabled - nepodařilo se připojit k serveru, server je nedostupný Tyto stavy se postupně mění podle zpráv od třídy Client a slouží třídě NetGame, která má k aktuálnímu stavu přístup přes funkci GetState. Funkce, která obsluhuje přijaté packety, je ReceivedPacket: void ReceivedPacket(ENetPacket *packet ,int channel); Kde packet je ukazatel na přijatá data a channel je číslo kanálu, pomocí kterého data přišla. Tuto funkci volá instance třídy Client. Podle čísla kanálu třída ClientCom příchozí data zpracovává. Packet pro klienta může obsahovat: • Pozici vzdáleného hráče - v tom případě se aktualizuje pozice daného hráče v kolekci playerInfos. • Zprávu od jiného hráče napsanou z chatu - zpráva se uloží do kolekce messages typu vector<string>. • Informace o hráči - tento packet obsahuje především přezdívku hráče, ta se uloží k danému hráči v kolekci playerInfos. • Signál - ten může být několika druhů, může znamenat odpojení určitého hráče ze hry, jeho havarování nebo restartování. V těchto případech se informace zapíše k příslušnému hráči v kolekci playerInfos a příslušná akce se provede ve funkci Update(viz. níže). Dále se signály používají ke změně stavu hry (zapíše se do proměnné gameState) a synchronizaci času (příchozí čas se zapíše do proměnné remainingTime). 28
• Čas průletu checkpointy určitým hráčem - od serveru jsou přijímány jen finální časy. Čas se po přijetí předá třídě Statistics(5.2.11.). • Nastavení od serveru - packet obsahuje ID klienta a počet checkpointů, tyto informace si třída ClientCom uloží. • Pozici checkpointu - klient si příchozí packet uloží do kolekce checkpointsPackets typu vector. Ve chvíli, kdy dojdou pozice všech packetů, jsou tyto pozice předány třídě Checkpoints(5.2.5.), to se děje ve funkci Update. Pro obsluhu každého z těchto druhů packetů existuje zvláštní funkce. Funkce ReceivedPacket akorát těmto funkcím předává přijatý packet. Další funkce, které volá instance třídy Client jsou ServerDisconnect, ServerConnect a UnableToConnect: void ServerDisconnect(); void ServerConnect(); void UnableToConnect(); Tyto funkce mění stav klientu. Instance třídy NetGame pravidelně volá funkci Update: void Update(u32 framesCount); Kde framesCount je počet snímků, které je potřeba dopočítat od posledního volání této funkce. Tato funkce provádí následující úkony: 1. Nastavení checkpointů instanci třídy Checkpoints, pokud má klient všechny uložené a nejsou zatím nastavené 2. Zavolání funkce Update všem instancím třídy NetAirplane, čímž je dosáhnuto aktualizace pozic letadel ovládaných hráči přes síť 3. Pokud od posledního volání této funkce živý hráč havaroval, nebo byl restartován, je na server odeslána tato informace 4. Pokud od posledního volání této funkce prolétnul hráč checkpointem, je poslán na server čas průletu 5. Několikrát za vteřinu posílá tato funkce na server pozici živého hráče, kolikrát za vteřinu je dáno konstantou NET UPDATES PER SECOND. Funkce počítá čas a pokud je potřeba odeslat pozici, učiní tak.
29
5.2.11.
Statistics
Třída Statistics vykonává podobnou úlohu jako třída ServerStatistics (4.3.1.) v serverové části. Jedná se o singleton [5]. Hlavní funkcí je zaznamenávání časů průletů checkpointů všech hráčů. K tomuto slouží kolekce finalStats, která má stejnou strukturu jako její jmenovec ve třídě ServerStatistics. Důležitou funkcí této třídy je vytváření výsledného pořadí hráčů. Toto pořadí je zaznamenáno v poli finalOrder typu int, které obsahuje seřazené ID hráčů podle výsledných časů. Dále má třída uloženo ID lokálního hráče, které nastavuje server pomocí funkce SetLocalPlayerID: void SetLocalPlayerID(int value); Kde id je právě toto ID. Uložení v třídě Statistics je zvoleno z toho důvodu, aby byla hodnota snadno dostupná v jiných částech programu. K jejímu získaní existuje funkce GetLocalPlayerID. Dále tato třída slouží také k uložení přezdívek hráčů (funkce SetPlayerNick ) a k přístupu k nim (funkce GetNickName). Pomocí funkce SetLocalPlayerTime se nastavuje čas živého hráče: void SetLocalPlayerTime(clock_t ticks, int checkpoint); Parametr ticks je čas a checkpoint je číslo checkpointu, u kterého byl tento čas zaznamenán. Tyto hodnoty se ukládají do proměnných newestCheckpoint a newestTime. K těmto proměnným přistupuje instance třídy ClientCom (5.2.10.) pomocí funkce GetNewestPlayerTime: clock_t GetNewestPlayerTime(int *checkpoint); Tato funkce vrací hodnotu proměnné newestTime a do proměnné, na kterou ukazuje parametr checkpoint zapisuje hodnotu proměnné newestCheckpoint. Pokud se hodnoty od posledního volání změnily, třída ClientCom pošle tyto údaje na server. Pokud třída ClientCom obdrží čas průletu checkpointem od serveru, předá tento čas instanci třídy Statistics pomocí funkce SetFinalTime: void SetFinalTime(long ticks, int checkpoint, int playerId); Kde ticks je čas, checkpoint je číslo checkpointu, u kterého byl tento čas zaznamenán a playerId je ID hráče, od kterého tento čas pochází. Čas se zapíše do kolekce finalStats. Pokud je zapsán čas posledního checkpointu, provede se aktualizace pole finalOrder, aby v něm bylo uloženo správné pořadí hráčů. K časům a pořadí se přistupuje pomocí funkcí GetFinalTime, GetBestTime a GetFinalOrder : 30
long GetFinalTime(int id); long GetBestTime(int checkpoint); int *GetFinalOrder(); Kde id je id hráče a checkpoint je číslo checkpointu. Funkce GetFinalTime vrací nejlepší čas hráče, jehož ID je zadáno parametrem. Funkce GetBestTime vrací nejlepší čas na checkpointu, který je zadán parametrem. A funkce GetFinalOrder vrací kopii pořadí hráčů od nejrychlejšího. 5.2.12.
ControlsLayout
Účelem třídy ControlsLayout je reprezentace rozložení kláves. Určité klávesy vykonávají jim přiřazenou činnost (zatáčení letadla, restartování letadla,. . .), třída ControlsLayout přiřazuje těmto klávesám dané činnosti. Jedná se o singleton [5]. Možné akce (činnosti), které klávesy vyvolávají jsou reprezentovány výčtovým typem keyboardLayoutActions. Jsou to actNull (nedefinovaná akce), actUp (stoupání), actDown (klesání), actLeft (rotace doleva), actRight (rotace doprava), actRestart (restart letadla), actSlowDown (zpomalení) a actSpeedUp (zrychlení). Poslední dvě akce nejsou ve hře použity, byly implementovány pouze pro testovací účely. K překladu kláves na akce slouží funkce TranslateKeyCode: keyboardLayoutActions TranslateKeyCode(irr::EKEY_CODE key); Kde key je kód klávesy. Funkce vrací akci typu keyboardLayoutActions náležící k této klávese, pokud není žádná akce definována, vrací actNull. Tuto funkci využívá třída GameCore(5.2.2.), která přeložené klávesy předává instanci třídy AirplanePlayer (5.2.7.). Dále třída obsahuje funkci SaveLayout, která aktuální rozložení kláves zapíše do souboru na disk počítače. V konstruktoru se třída pokaždé snaží nejdříve přečíst rozložení kláves z tohoto souboru, pokud soubor neexistuje, aplikuje se defaultní rozložení pomocí funkce SetDefaultLayout. Změna rozložení kláves je možná pomocí funkce BindKeyToAction: void BindKeyToAction(keyboardLayoutActions action, irr::EKEY_CODE keyCode); Kde keyCode je kód klávesy a action je akce, která se na tuto klávesu naváže. Tato funkce se nejdříve pokusí najít klávesu, na kterou je daná akce aktuálně navázána, pokud se podaří takovou vazbu najít, funkce ji odstraní. Poté se uloží nová vazba. 31
5.2.13.
AudioSample
Třída AudioSample reprezentuje zvuk, který je možný přehrávat ve hře. K tomu se používá knihovna OpenAL. Instance této třídy generuje třída AudioLayer (5.2.14.). Každý instance třídy AudioSample má vlastní proměnnou buffer, ve které je uložen zvuk, který přehrává, a proměnnou source, ve které je uložena pozice zdroje zvuku na scéně. Instance této třídy může mít zdroj zvuku nastaven buď na fixní pozici, nebo na nějaký objekt typu ISceneNode (reprezentuje těleso v prostoru). V druhém případě se pravidelně proměnná source aktualizuje vzhledem k danému tělesu. Tato aktualizace pobíhá ve funkce Update. Třída nabízí funkce pro přehrání zvuku (Play), zastavení přehrávání (Stop), nastavení opakovaného přehrávání (SetLoop), nastavení pozice nebo tělesa v prostoru (SetSourcePosition a SetSourceNode) a zjištění stavu přehrávání (IsPlaying). 5.2.14.
AudioLayer
Třída AudioLayer spravuje zvuky ve hře, k tomu využívá knihovnu OpenAL. Jedná se o singleton [5]. Zvuky třída ukládá v kolekci samples typu list. Pro reprezentaci jednotlivých zvuků jsou použity instance třídy AudioSample(5.2.13.). Tyto instance třída vytváří funkcí LoadAudioFile: AudioSample *LoadAudioFile(char *filename, scene::ISceneNode *sourceNode = NULL); Kde filename je jméno souboru se zvukem a sourceNode je ukazatel na těleso v prostoru, ke kterému se bude zvuk vztahovat. Funkce vrací ukazatel na instanci AudioSample, kterou ve funkci vytvoří. Dále třída obsahuje funkci na odstraňování vytvořených zvuků RemoveSample: void RemoveSample(AudioSample *sample); Kde sample je ukazatel na zvuk, který bude zrušen a odstraněn z kolekce samples. Nastavení pozice posluchače umožňuje funkce SetListenerNode: void SetListenerNode(scene::ISceneNode *node); Kde node je ukazatel na těleso na scéně, jehož pozice bude brána jako pozice posluchače. Nejčastěji se této funkci předává kamera, která celou scénu pozoruje. Protože se pozice zvuků i posluchače postupně mění, je třeba je aktualizovat, k tomu slouží funkce Update: 32
void Update(); Při jejím zavolání se aktualizuje pozice posluchače, kterou spravuje třída AudioLayer a také je zavolána funkce Update u všech zvuků uložených v kolekci samples, díky čemuž se aktualizuje i pozice zdroje těchto zvuků. 5.2.15.
Effects
Třída Effects je určená k zobrazování různých audiovizuálních efektů na scéně. Jedná se o singleton [5]. V aktuální podobně obsahuje jediný efekt a to zobrazení exploze, což se používá při havarování letadla. Funkce, sloužící k zobrazení exploze, je AddExplosion: void AddExplosion(core::vector3df position); Kde position je pozice, na které bude výbuch zobrazen. Třída při svém vytvoření pomocí třídyAudioLayer (5.2.14.) vytvoří objekt typu AudioSample(5.2.13.), ve kterém je uložený zvuk exploze. Dále načte do pole boomTextures jednotlivé snímky výbuchu. Pří zavolání funkce AddExplosion je poté přehrán zvuk exploze a v herním světě je vytvořen objekt typu IBillboardSceneNode, který přehrává snímky exploze. 5.2.16.
Configuration
Třída Configuration slouží k uložení některých údajů, konkrétně je to přezdívka hráče a adresa serveru, ke kterému se hra připojuje. Jedná se o singleton [5]. Tyto dvě informace jsou uloženy v proměnných nickName a serverAddress, což jsou pole typu char. K přístupu k těmto údajům slouží funkce GetNickName a GetServerAddress, k jejich nastavení slouží funkce SetNickName a SetServerAddress. Při nastavení nějakého z těchto údajů je jejich aktuální stav uložen do souboru na disku počítače. Při vytvoření se třída Configuration nejdříve pokusí přečíst údaje ze souboru na disku počítače, pokud soubor neexistuje, je použito jejich defaultní nastavení. 5.2.17.
GUI
GUI je třída, která funguje jako manažer spravující všechny elementy uživatelského rozhraní. Stará se o vytváření těchto elementů, jejich aktualizaci i odstraňování. Dále nahrává do paměti fonty a dává k nim přístup. Třída rozlišuje tři druhy fontů, které reprezentuje výčtový typ fontType. Tyto druhy jsou: • fSmall - Malý font, používá se pro položky v menu a také pro zobrazení výsledků. 33
• fMedium - Používá se pro zobrazování zpráv ve hře (havarování, odpojení od serveru,. . .), dále k odpočítávání zbývajícího času, v chatu a zobrazení postupu v prolétávání checkpointů. Je větší než fSmall. • fBig - Největší font, použitý k odpočítávání před začátkem hry a také k zobrazování jmen hráčů u letadel v herním světě. Tyto fonty jsou načteny při vytvoření instance této třídy a jsou uloženy v objektu typu IGUIFont. Je k nim možný přístup pomocí funkce GetFont: IGUIFont *GetFont(fontType type); Funkce vrací ukazatel na font typu určeného parametrem type. Všechny elementy gui jsou uloženy v kolekci objects typu list. Každý element gui je objekt, který dědí z třídy GUI Object. Třída GUI Object obsahuje virtuální funkce KeyPressed, CharacterPressed a MouseClick pro události stisknutí kláves a kliknutí myší. Dále obsahuje funkce Draw a Update které potomci překrývají kódem pro vykreslování elementu a jeho aktualizaci. Pro práci s viditelností elementu slouží funkce SetVisibility a IsVisible. Pro práci s focusem slouží funkce SetFocus a IsFocused. Třídy, které dědí z třídy GUI Object, jsou: • GUI Button - Tlačítko, je možné nastavovat mu text a reagovat na kliknutí na něj. • GUI Chat - Herní chat, do kterého lze zapisovat zprávy a odesílat je. Při odeslání zprávy se tato uloží do kolekce userMessages typu list<string>. K těmto zprávám je možný přístup pomocí funkce GetUserMessage, toho využívá instance třídy NetGame(5.2.3.), která takto získané zprávy předává instanci třídy ServCom(4.3.3.), ta zprávy odesílá serveru. • GUI Countdown - Odpočítávání před začátkem kola. Element je po dokončení odpočítávání zrušen. • GUI Edit - Element, do kterého je možno psát text. Pro přístup k tomuto textu slouží funkce GetValue. Text je možné i nastavit funkcí SetValue. Element může mít stanovenou maximální délku textového řetězce, který je možný do něj zapsat. • GUI Text - Slouží k zobrazování textu. Je možné nastavit zobrazovaný text funkcí SetText a pomocí funkce SetFont je možné nastavit i font. Dále je možné nastavovat pozici textu a jeho barvu. Pokud je pomocí funkce SetDimension nastavena nenulová velikost, je text zarovnán na střed.
34
• GUI FadeText - Rozšíření třídy GUI Text o nastavení průhlednosti (funkce SetAlpha) a efekty postupného objevování a mizení. Funkce SetFadingSpeed slouží k nastavení rychlosti objevování nebo mizení. Funkcí DoFade se spouští efekt, parametrem funkce je stanoveno, jestli se spustí efekt objevování nebo mizení. • GUI MainMenu - Element zobrazující hlavní menu. Přizpůsobuje se podle nastaveného rozlišení. Skrz něj je možný přístup k přezdívce a adrese serveru, kterou uživatel uvedl (funkce GetNickName a GetServerAddress). • GUI MenuGame - Část hlavního menu, která obsahuje instance třídy GUI Edit pro zadávání hráčovi přezdívky a adresy serveru. • GUI Message - Element pro zobrazení zprávy uživateli. Zpráva je vystředěná na střed obrazovky, její obsah může být zadán buď v konstruktoru nebo funkcí SetText. • GUI Results - Zobrazení výsledků hry. Časy třída získává z instance třídy Statistics(5.2.11.). • GUI ServerMessages - Zobrazení zpráv od serveru v levém horním rohu obrazovky. Tyto zprávy obsahují například informace o připojených hráčích. Instance třídy NetGame(5.2.3.) kontroluje nové zprávy od serveru a pokud nějaké jsou, předá je tomuto elementu pomocí funkce AddMessages. • GUI TimeCounter - Element zobrazující odpočet času do konce kola. Je umístěn do horní části obrazovky. Zbývající čas ve vteřinách se mu nastavuje pomocí funkce SetRemainingTime, toto nastavení provádí instance třídy NetGame. Všechny tyto elementy třída GUI vytváří. Některé z nich v sobě vytváří další elementy. K aktualizace všech instancí těchto tříd, které má uložené třída GUI slouží funkce Update: void Update(u32 framesCount); Kde framesCount je počet snímků, které je potřeba dopočítat od posledního volání této funkce. Tato funkce projde všechny objekty v kolekci objects a zavolá u nich funkci Update, ve které jim předá framesCount. Dále uvolňuje objekty, které mají být zrušeny (například GUI Countdown po dokončení odpočítávání). Pro vykreslování slouží funkce Draw : void Draw(); Tato třída projde všechny objekty v kolekci objects a zavolá u nich funkci Draw. Tím se vykreslí každý jednotlivý element. 35
6.
Postup pro sestavení programů
Pro sestavení programů (server a klient) je potřeba Microsoft Visual Studio 2008. V tomto programu se otevře soubor Flyable.sln. Poté je možné stisknutím kombinace kláves CTRL+SHIFT+B programy sestavit. Stisknutím klávesy F5 se programy sestaví a spustí se klientská část.
36
Závěr Podařilo se mi vytvořit aplikace v jazyku C++ dle zadání práce. Klientská i serverová část hry jsou funkční. Ve vývoji aplikací by šlo nadále pokračovat, například zlepšením grafické stránky hry, přidáním dalších herních možností, nebo vylepšením bezpečnosti síťové komunikace.
37
Conclusions I managed to create application in C++ language according to assignment of the bachelor thesis. Client and server parts are working. It is possible to continue in development of these applications. Future development should improve graphics of the game, add new game options, or improve security of net communication.
38
Reference [1] SCHREIBER, Ian. Gamedesign [online]. ROCH, Michal. 2010 [cit. 201107-01]. Škola game designu Iana Schreibera: Level 1. Dostupné z WWW: http://gamedesign.cz/?p=1692. [2] Wikipedia [online]. 2011 [cit. 2011-07-01]. Racing video game. Dostupné z WWW: http://en.wikipedia.org/wiki/Racing_video_game. [3] Wikipedia [online]. 2011 [cit. 2011-08-08]. C++. Dostupné z WWW: http://cs.wikipedia.org/wiki/C%2B%2B. [4] Wikipedia [online]. 2011 [cit. 2011-08-08]. Stencil buffer. Dostupné z WWW: http://en.wikipedia.org/wiki/Stencil_buffer. [5] Wikipedia [online]. 2011 [cit. 2011-08-08]. Singleton. Dostupné z WWW: http://cs.wikipedia.org/wiki/Singleton. [6] cpluplus.com [online]. 2011 [cit. 2011-08-10]. clock. Dostupné z WWW: http://www.cplusplus.com/reference/clibrary/ctime/clock/.
39
A.
Obsah přiloženého CD
bin/ Instalátor setup.exe a soubor FlyableInstaller.msi potřebný ke správné instalaci. doc/ Dokumentace práce ve formátu PDF, vytvořená dle závazného stylu KI PřF pro diplomové práce, včetně všech příloh, a všechny soubory nutné pro bezproblémové vygenerování PDF souboru dokumentace (v ZIP archivu), tj. zdrojový text dokumentace, vložené obrázky, apod. src/ Kompletní zdrojové texty programu Flyable se všemi potřebnými zdrojovými texty, knihovnami a dalšími soubory pro bezproblémové vytvoření spustitelných verzí programu. readme.txt Instrukce pro instalaci a spuštění programu Flyable, včetně požadavků pro jeho provoz. Navíc CD obsahuje: install/ Instalátor knihovny OpenAL, která je nutná pro provoz programu. U veškerých odjinud převzatých materiálů obsažených na CD/DVD jejich zahrnutí dovolují podmínky pro jejich šíření nebo přiložený souhlas držitele copyrightu. Pro materiály, u kterých toto není splněno, je uveden jejich zdroj (webová adresa) v textu dokumentace práce nebo v souboru readme.txt.
40