Radoslav Glinsky - hruscak_vs_hurnak - dokumentacia Tento text sluzi ako doprovodna dokumentacia k zapoctovamu programu k predmetu Programovani2 (letny semester 2007) "hruscak_vs_hurnak". Je to hra, 2D strielcka. Nazov hry bol silne inspirovany dvomi ludmi, ktori sa "nahodou" vyskytli v hre ako hlavne postavy...Preto je nazov odvodeny z ich priezvisiek. Program je vytvoreny v objektovo orientovanom jazyku C++. Na graficke vykreslovanie je pouzita kniznica OpenGL. Pouzite datove struktury: class hrac { public: float x,y,z; int smer, cas_nah_pohybu, pom_cnp, trojita_strela,neviditelny,nesmrtelny,zabil, mrtvy; float pauza; bool zivy; long cas_smrti; hrac(); void vynuluj_sa(float a, float b, float c, int s); inline hrac(float a, float b, float c, int s); }; class strela { public: int smer,povodca,trojita; float x,y,z; char farba; strela(); strela(float a, float b, float c, int s, int p, int t, char f); }; class ob_cas { long zac; long akt; public: ob_cas(); int daj_cas(); void vynuluj() ; }; class kokocina { public: bool zobrazit; float x,y,z; kokocina(); kokocina(float a, float b, float c) ; void zmen_polohu(float a, float b, float c); virtual void zobraz()=0; int over_prelinanie_kokociny(); }; class prekazky { public: float x,y,polomer; prekazky(float a, float b, float r); void zobraz(); }; globalne premenne: int pocet_hracov, pocet_prekazok, cas, prvy_hrac; hrac pole_hracov[], prekazky *prek[]; ob_cas hodiny; std::list v; pole_hracov - pole s objektami triedy hrac(prvy ludsky hrac je pole_hracov[1] a pripadny druhy pole_hracov[0]) pocet_hracov - defaultne nastaveny na dvoch pocet_prekazok - defaultne nastaveny ziadne cas - defaultne nastaveny na 60 sekund prvy_hrac - v podstate urcuje to, ci sucasne hraju obaja ludsky hraci(vtedy je prvy_hrac==0) alebo len jeden(vtedy je prvy_hrac==1) defaultne nastaveny na 1, t.j. hra len jeden clovek. - to sa mi vidi uzitocne v casto vyuzivanej konstrukcii typu: for(i=prvy_hrac;izobrazit=false). Vynulujem casove pocitadlo (respektive zapamatam si kedy sa nasa hra zacala) funkciou hodiny.vynuluj(); Najdolezitejsia je hadam while(!done) smycka, kde prebieha samotna hra volanim funkcie render(). Dolezita vec, ktoru bolo potrebne akymsi sposobom zabezpecit je to, aby hra bezala "rovnako rychlo" pre 2 hracov, ako aj pre 8 s velkym poctom prekazok. To som sa snazil zabezpecit asi tak, ze do float premennej timex som si vzdy na zaciatku render() ulozil cas, kt. uplynul od minuleho volania render(). Na to posluzi funkcia exaGetElapsedTime(), kt. prave vracia cas v sekundax od svojho posledneho volania vo vystupnej float hodnote. Cim vacsiu ma timex hodnotu, tym "pomalsia" je hra. Dalej postupujem tak, ze vsetky rychlosti, ktore sa v hre vyskytuju(pohyb hracov a pohyb striel) nasobim premennou timex. Teda takymto sposobom sa zaistilo, aby hra mala jednaky spad, aj ked mnozstvo vykonavanych operacii sa niekolkonasobne lisi. Musime osetrit to, aby hrac nemohol strielat "kedy sa mu zachce", ale aby medzi jednotlivymi strelami boli nejake casove pauzy. Kazdy hrac ma v sebe polozku pauza(urcuje, kolko sekund preslo od posledneho hracovho vystrelu), a ta sa zvysuje kazdym priechodom funkcie render(). V nasom konkretnom nastaveni je pauza medzi strelami definovana na 1 sekundu. Snad najkomplikovanejsie bolo naprogramovat tzv. "umelu inteligenciu" PC hracov. Ta sa sklada z takych 3 hlavnych casti: 1. kedy sa ma pohybovat a kedy ma stat -vramci funkcie osetri_stisknute_klavesy() program reaguje na stisk sipiek a klavies 'w','a','s','d'. Ak doslo k ich stlaceniu, funkcia odovzda kontrolu druhej, a to funkcii ci_sa_da_ist(int smer, int plejer). Ta ma na starost overit, ci po stisknuti pohybovej klavesy je vobec mozne vramci pravidiel pohnut v prislusnom smere hracom, to znamena inkrementovat alebo dekrementovat x alebo y-ove suradnice hracov. Funkcia ci_sa_da_ist(int smer, int plejer) vola dalsie funkcie, ktore overuju, ci hrac nenarazil uz do nejakeho ineho hraca(funkcia over_bod), alebo do nejakej z pripadnyx prekazok(funkcia over_prelinanie_prekazok), alebo ze ci nenarazil na koniec hracej ploxy. Co sa tyka PC hracov, tak u nich su dolezite polozky pom_cnp a cas_nah_pohybu. Nulove pom_cnp znamena, ze sa PC hrac nema pohybovat. cas_nah_pohybu urcuje, ako dlho ma PC hrac zotrvat v tom pohybe(alebo bez pohybu), v akom je momentalne. Za normalnych okolnosti si PC hrac nahodne voli smer pohybu aj ako dlho v nom bude pokracovat(vynimku tvori situacia, kedy sa PC hrac bude snazit dostat ludskeho hraca a priblizit sa k nemu alebo k predmetu, kt. moze zbierat po hracom plane). 2. zmena smeru PC hraca -ta nastava v 2 pripadoch: bud sa zmeni kvoli ludskemu hracovi(ak hraju obaja, tak kvoli tomu, ktory je blizsie vzdialeny od PC hraca) alebo kvoli objavujucim sa predmetom na ploche. Pricom ludsky hrac ma najvyssiu prioritu, potom nasleduju tie predmety. Pri zmene kvoli ludskemu hracovi vyuzivame funkcie zmen_smer_blizko a zmen_smer_daleko. zmen_smer_blizko urci, ci sa ludsky hrac nachadza v tesnom okoli PC hraca. Ak ano, nastavi novy smer PC hraca v pripade, ze PC hrac je schopny uspesne vystrelit na ludskeho hraca. Ak nie, funkcia zmen_smer_daleko urci, ci sa PC hrac nachadza niekde vo vertikalnej alebo horizontalnej linii ludskeho hraca. Vtedy sa smer PC hraca patricne zmeni. Pri zmene kvoli predmetom na ploche sa volaju funkcie zmen_smer_kokocina_blizko a zmen_smer_kokocina_daleko. Pracuju velmi podobne ako vyssie spominane, akurat ze sa zaoberaju prave tymi "predmetmi". Zaujimava situacia je okrem toho ta, ked medzi PC hracom a objektom, pre ktoreho PC hrac zmeni smer, stoji nejaka prekazka. Vtedy logicky by PC hrac nemal tento novy smer "prijat", ale drzat sa toho stareho. Prave to realizujeme nasledovne: spociatku si zapamatame povodny smer do pomocnej int premennej(tentokrat u nas je to premenna j, smery su reprezentovane cislicami 1 az 4, 1 je smerom hore, 2 vpravo, 3 dole, 4 vlavo). Teraz zavolame funkciu ignoruj, ktora nam vrati vysledok, ze ci prave zmenenemu smeru nebrani nejaka prekazka. Ak ano, hracov smer sa opat prepise na ten povodny(ulozeny v premennej j). Tu je dolezite rozlisit medzi tym, ci PC hrac zmenil svoj smer kvoli ludskemu, alebo kvoli ostatnym "predmetom". Ked to bolo kvoli ludskemu hracovi, staci, aby smerom od PC hraca k ludskemu bola pristupna len priamka, po kt. PC hrac striela. Naopak, kvoli "predmetom" je to silnejsia poziadavka, lebo vtedy musi byt volny cely "pas" tak, aby PC hrac sa priamo dostal pozadovanemu predmetu bez toho, aby sa "zasekol" o nejaku prekazku. 3. kedy ma PC hrac vystrelit -PC hrac striela dvojako: bud len tak sem-tam nahodne, alebo vzdy ked nie je blizsi ludsky hrac neviditelny a nachadza sa v takom "krizi" z pohladu PC hraca. Striela samozrejme vzdy len ked mu to dovoli pauza strely. Inak vsetky strely su vytvarane nasledovne: Pre ludi plati to, ze musia mat aspon sekundu pauzu pred tym, nez chcu znova vystrelit stlacenim medzernika alebo pripadne 'q'. Tu sa rozhodne, ze ci strela je len jednoducha, alebo "brokovkova". To sa zisti vlastnostou hraca trojita_strela. Ak ano, do spojoveho zoznamu sa prida, okrem jednej priamej strely, na koniec objekt typu strela. Jej konstruktor potrebuje parametry: suradnice zaciatku strely, smer pohybu strely, hrac ktory strelu vystrelil (aby sme vedeli, komu pripisat skore ak ta strela nahodou niekoho trafi), cislo strely(pretoze kazda strela okrem priamej sa pohybuje pod inaksim uhlom) a farba strely(jednoducha ma zelenu farbu, brokovkova cervenu). Spojovy zoznam je realizovany cez STL kniznicu, co nam to celu situaciu ulahcilo. Vykreslenie pozadia prebieha tak, ze kreslime "obycajny" stvoruholnik pomocou OpenGL -ovskeho glBegin(GL_QUADS);....glEnd(); akurat este pred tym dame namapovat texturu 'pozadie1'. Tymto postupom zobrazujeme aj prekazky, aj samotnych hracov, aj "predmety" objavujuce sa na ploche. Ak je hrac neviditelny(pole_hracov[i].neviditelny>0) alebo nesmrtelny (pole_hracov[i].nesmrtelny>0) dekrementujeme jeho "neviditelnost"/"nesmrtelnost". Na rade je overit, ze ci niektoreho z nasich hracov "nezasiahla" strela nepriatela. To vsetko spravi funkcia over_strely, ktora pre kazdeho hraca zisti, ze ci sa suradnice niektorej z aktivnych striel nenachadzaju niekde v rozmedzi hracovej velkosti. Ak ano, zasiahnuty hrac si pripise minusovy bod, avsak vlastnik tejto strely plusovy...To sa ale nestane, ak je zasiahnuty hrac nesmrtelny. Ak nie je ale nesmrtelny, nastane "vybuch". Zabezpecuje ho funkcia createparticle volana od objektu ps typu exaParticleSystem. Okrem toho sa este nastavi hracova premenna cas_smrti, kt. sluzi na kontrolu, kedy sa zastreleny hrac opat vrati do hry. Hraca vykreslujeme ak je zivy a nie je neviditelny. Ak je nesmrtelny, tak ho tesne pred koncom "nesmrtelnosti" 3x po sebe striedavo vykreslime/nevykreslime. Pre kazdy smer hraca existuje vlastna textura, kt. sa tesne pred tym nez je vykreslena namapuje. Pre oboch ludskych hracov je texturovany obrazok kamarata Hruscaka, pre vsetkych ostatnych PC hracov kamarata Hurnaka... :-D Pre vsetky "predmety" zobrazovane na hernej ploche urobime: 1. ak su na ploche zobrazovane -overime ci sa neprelinaju s nejakym hracom funkciou over_prelinanie_kokociny(). Ak sa neprelina, nechame dany "predmet" vykreslit. Inak(to znamena, ze nejaky hrac predmet prave vzal) nastavime prislusne zmeny(pocet trojitych striel prepiseme na 5 / neviditelnost alebo nesmrtelnost nastavime na nejake kladne cislo / ak hrac vzal "bombu", kazdy zivy okrem neho vybuchne, znizia sa im skore, ale hracovi sa zvysi o prave tolko hracov) 2. ak nie su zobrazovane -generujeme nahodne cislo, ak je nulove, vtedy mame zacat znovu zobrazovat dany predmet. Pouzijeme funkciu generuj_nahodne_suradnice_hracov, ktora urci polohu novo zobrazovaneho predmetu. V nej je schovana verifikacia, ci sa nova poloha neprekryva s nejakou prekazkou, alebo sa nenachadza prilis blizko nejakeho hraca. S touto polohou sa nastavi aj p_k[i]->zobrazit=true; a predmet sa nam uz (aspon dokym ho nejaky hrac opat nevezme) bude zobrazovat. V dalsej casti sa vsetky "disable-ovane" strely dealokuju v.erase(nejaky_iterator). Prejdeme si vsetky strely cez std::list::iterator. Funkcia zmen_strelu disabluje strelu, ak narazila do prekazky, na koniec hracieho planu. Strelu vykreslime pomocou glBegin(GL_POINTS);glVertex3f(.....);glEnd(); ako nejaky bod, ktoremu sme zvacsili rozmery. Dalej si spocitame kolko zivych hracov sa prave nachadza na ploche, ak ich je menej ako udava premenna pocet_hracov, tak pre kazdeho "mrtveho" hraca overime, ze ci uz uplynula dostatocne doba(u nas je to teraz 3 sekundy), aby sa mohol vratit spat do hry. Ak sa vracia, uz spominanou funkciou generuj_nahodne_suradnice_hracov sa vygeneruju nove suradnice tak, aby sa hrac neprekryval ani so ziadnou prekazkou, ani sa nenachadzal velmi blizko ineho hraca na ploche. Na uz existujuci objekt hraca v poli pole_hracov zavolame len clensku metodu vynuluj_sa, kt. objektu zmeni nastavenie, ako keby bol prave vytvoreny. Tymto postupom si ale usetrim zbytocne dealokovanie a alokovanie pamati pre objekt. Funkcia skore() ma na starost zobrazovat v lavej casti obrazovky obrazky hracov, ich skore, popripade male obrazky predmetov, ktore vzali a su stale platne. Uplne naspodku ukazuje cas, ktory zostava do ukoncenia hry. Na ten cas pouziva objekt hodiny. render() sa opakuje az dokym naprerusime hru nasilne pomocou ESC alebo hra neskonci prirodzene vyprsanim casu. Ak hra skonci "normalne", zobrazi sa nam vyledok so statistikou jednotlivych hracov zapojenych do diania hry. Takisto obsahuje while(!done) smycku, a z nej vola funkciu vysledok(). V nej si displej rozdelime rovnomerne podla poctu zapojenych hracov. V kazdej casti vypiseme najprv meno hraca, pod nim obrazok ako vyzeral, dalej nazbierane skore s podrobnostami kolkokrat bol hrac "zastreleny" a kolko inych hracov on "zastrelil". Vysledok hry sa zobrazuje az dokym nestlacime ESC alebo ENTER. Potom sa zmaze obsah zoznamu v so strelami. V ramci while(!total_end) znovu vstupujeme do uvodneho menu, a tento cyklus sa opakuje dovtedy, kym vyslovene nezrusime cely program. Zaver: Na zaver tejto dokumentacie by som len chcel podotknut, ze na "hruscak_vs_hurnak"ovi sa este budem snazit popracovat, doladit nejake detaily, niektore casti programu pravdepodobne uplne prepisat. Uvedomujem si vela chyb, ktore som musel urobit uz len v dosledku toho, ze v jazyku C++ toto bol moj prvy program vobec, a s OpenGL kniznicou to iste. Pri ladeni som si vsimol niekoko bugov, tykajuce sa prevazne spravania PC hracov. Niekedy jednoducho "urobili kejkle", takze to povazujem za prvorade opravit. Co by bolo azda este lepsie je to, aby tie "kejkle" urobili naschval, ako aj obycajny clovek robi stale chyby. A samozrejme este vylepsit hru o uzivatelsky zaujimave doplnky(snad vopred predurcene herne plany, urovne obtiaznoti, nejake bonusove graficke efekty...)