Korak 0: JOŠ MALO SUVOPARNOG FILOZOFIRANJA
Evo nas na pragu da počnemo i sa samim kodom. Ali ne lezi vraže, nismo mi te sreće: prethodno moramo da odradimo još malo opšte teorije. Trudiću se da svedem bullshit na minimum... gest unapred osuđen na propast :)
Sve igre na svetu funkcionišu na nekoliko zajedničkih principa. U ovom koraku ću objasniti te principe, na kojima će se zasnivati i naš VectorBall. A vi ćete mi oprostiti ako ovde kažem nešto što već odavno znate.
Sveta trijada programiranja: input, process, output.
Igra je, u suštini, aplikacija kao i svaka druga, u tom smislu da mora da ispuni sledeće: a) uzima podatke od igrača, b) procesuje te podatke i c) izbacuje rezultate na ekran. Ovaj isti princip pokreće i Excel, i Pong, i FarCry. Za sada ćemo se držati Ponga kao dobre polazne tačke za opisivanje. Igra Pong sadrži dva reketa, jednu lopticu, i pokazivač rezultata, i to je sve što je potrebno da savršeno ilustruje način na koji funkcionišu igre.
Početnici će verovatno pitati: Kako to igra uspeva da radi? Kako pomera rekete? Kako odbija lopticu?
Jedna reč: varijable.
Mnogo varijabli.
Njeno veličanstvo varijabla
Sve što postoji u game-worldu mora biti uskladišteno u memoriji u vidu varijabli. Svaki reket, svaka loptica, svaki tenk, vojnik i konjanik, svaki space invader. Mi, programeri, pomoću varijabli kompjuteru opisujemo poziciju, ponašanje i stanje
svega što u našoj igri postoji. Za procesor je to samo gomila brojeva u RAM memoriji. Ali takva gomila brojeva koje će on iskoristiti da na pravi način nacrta loptu, rekete, rezultat, tenkove - tako da to ima smisla igraču.
Uzmimo lopticu u Pongu i pretpostavimo da stoji u mestu. Šta nam je neophodno da bismo opisali kompjuteru našu lopticu? Da bi igrač postao svestan loptice, kompjuter je mora nacrtati na ekranu, a da bi je kompjuter nacrtao na ekranu, on mora znati njenu poziciju. A pozicija se može iskazati brojevima. I eto naših prvih varijabli. :)
Bez preteranog ulaska u Dekartove koordinatne sisteme (koji nas čekaju za nekoliko koraka), za sada ćemo se zadovoljiti time da poziciju loptice opišemo njenim rastojanjem od gornje i leve ivice ekrana. Našu lopticu možemo da opišemo tako što ćemo reći kompjuteru da se nalazi 60 piksela od leve ivice (ovo se zove X koordinata) i 100 piksela od gornje ivice ekrana (Y koordinata). Ili, skraćeno, koordinate loptice su: X = 60 i Y = 100. Sve što nam sada treba jeste da kompjuteru kažemo da nacrta sličicu loptice na ovim koordinatama (kasnije ćemo detaljno obrađivati crtanje na ekranu).
Znači, u našem programu se već nalaze dve varijable: X koordinata lopte i Y koordinata lopte. Nazovimo ove dve varijable LoptaX i LoptaY (u ovom koraku još se ne bavimo kodiranjem, već samo principima programiranja, pa nećemo ovde deklarisati varijable).
Da bismo crtali rekete takođe nam treba njihova pozicija na ekranu, pa ćemo imati i varijable koje će opisivati njihovu poziciju. Takođe ćemo imati varijable koje će čuvati rezultat. I na kraju ćemo imati varijable koje će opisivati kretanje loptice i reketa.
I eto, samo desetak-dvadesetak varijabli nam je dovoljno da napravimo jedan Pong. Ali te varijable nam ne vrede ništa ako tek tako stoje i kuliraju. Pong u kojem loptica i reketi stoje u jednom mestu ne bi bio preterano uspešna igra, zar ne? Naravno da ne. Treba nam način da pokrećemo lopticu, da pomeramo rekete, da odbijamo lopticu od njih. Kako se to postiže? Prosto - menjamo vrednost varijabli. Razmislite. Ako hoćemo da pomerimo lopticu 10 piksela udesno, samo treba da promenimo vrednost varijable LoptaX (LoptaX = LoptaX + 10). Međutim, kako menjamo te vrednosti? Koliko često? Kojom prilikom? Kojim zakonitostima?
E, to nas dovodi do drugog glavnog principa kako funkcionišu igre: petlja.
Naša igra je jedna velika petlja
Petlje i multitasking: Nekada davno, u vreme Komodora 64, programiranje je bilo relativno jednostavno. Izvršavao se jedan program u jednom trenutku. Nije bilo multitaskinga, paralelnih procesa, multithreadinga i ostalih bakrača. A onda su došle platforme poput PCja i Macintosha, čiji su operativni sistemi polako ali sigurno počeli da se snalaze u multitasking sredini.
I tako su počeli problemi za programere igara.
Igra nije namenjena za multitasking. Kada pravimo igru, mi hoćemo da ona besramno uzurpira kompletan procesor, celu radnu memoriju, i polovinu hard diska. Ne želimo da nam neki tamo Windows traći dve trećine radne memorije. Šta će nam multitasking? Ko još igra Quake 3 i onako usput paralelno sa tim kucka dokument u Wordu?
Ono što je problematično je to što je i dragi nam Visual Basic praktično dizajniran za programiranje multitasking projekata. Pravljen je tako da program koji napišemo reaguje na određene događaje - pritisak dugmeta na formi, pritisak na enter, prelazak strelicom preko nekog polja - itd. Ostatak vremena, program stoji i ništa ne radi. Ovo se zove event-driven programiranje ("programiranje pokretano događajima"). Što je sjajno, ako pravite bazu podataka.
Ali mi smo programeri igara. Mi ne pravimo baze podataka. Mi preziremo baze podataka. Mi se gnušamo baza podataka. Mi mrzimo programere baza podataka, i tučemo se sa njima do smrti kada ih sretnemo na ulici.
Minesweeper i šah i sl. su jedine igre koje mogu bez blama da koriste event-driven programiranje. Za te igre to može da posluži. Ali mi, gospodo, pravimo arkadnu igru. Nama event-driven programiranje ne odgovara. Zato, mi odbacujemo ovaj tip programiranja. I zato ćemo mi sada da malo pričamo o petljama.
Kao što sam već rekao, varijable ne vrede ništa ako samo stoje i ne menjaju se. Ali kada ih menjamo? Koliko često apdejtujemo njihove vrednosti?
Zavisi. Ako igrate RTS, Pong, ili neku pucačinu, ne želimo novi frejm svake dve sekunde, zar ne? Ne, mi želimo onoliko frejmova koliko naša jadna napaćena konfiguracija može sebi da priušti. Dakle, mi već pomenutu svetu trijadu programiranja ponavljamo nebrojenih XY puta svake bogovetne sekunde (uzimamo input; apdejtujemo vrednosti varijabli; crtamo elemente na ekranu).
Recimo da je prihvatljivo da apdejtujemo varijable 25 puta u sekundi. (kasnije ćemo naučiti da tempiramo procesor da ovo radi). Ovo nećemo postići ako čekamo neki event, čak i ako je to Timer event. I zato moramo da se okrenemo petlji.
Do...Loop petlja je sasvim prihvatljiva. Da ne bismo potpuno uzurpirali resurse windowsa, moramo da obezbedimo i drugim procesima šansu da "dišu" (inače će se kompjuter zaglaviti). Za ovo nam služi komanda DoEvents, koja otprilike kaže windowsu "odradi šta treba, a onda se pod hitno vrati na izvršavanje ovoga koda".
Da li vam je sada jasno kako igra radi?
Code:
Public Sub MainProcess
Dim Igrac_Signalizirao_Izlaz As Boolean
Do
DoEvents
Uzmi_Input ' miš, tastatura, štagod
Procesuj_Input ' npr. ako je igrač pritisnuo Escape, onda Igrac_Signalizirao_Izlaz = True
Procesuj_Gameworld ' obradi svaku jedinicu, metak, loptu, kap kiše i cvetić na ekranu
Procesuj_Output ' izbriši ekran, nacrtaj sve što treba, započni/prekini zvukove itd.
Loop Until Igrac_Signalizirao_Izlaz
Naravno, ovo je VRLO pojednostavljen prikaz celog procesa. Pre ove procedure treba odraditi inicijalizaciju varijabli; treba pripremiti prozor; treba obraditi error handling; itd, itd. Ali ovo ionako nije zvaničan kod naše igre. Ovo je samo ilustracija.
Praktična primena
Šta sve ovo znači primenjeno na Pong ili naš VectorBall? (i dalje smo samo u teoretisanju; u slučaju da se zbunite ili zagubite čitajući sledeće redove, to nije problem, oni su tu da bi vam pokazali kako jedna prosta ideja evoluira; objašnjenje korak po korak će sigurno biti dato kada krenemo sa samom igrom).
Sećate se varijabli LoptaX i LoptaY? Pretpostavimo da se loptica kreće udesno po ekranu (povećava joj se X koordinata) brzinom od 50 piksela u sekundi. Ako se petlja izvršava 25 puta u sekundi, to znači da svaki prolaz kroz petlju (koji ću od sada, pa za vjeki vjekova, ja zvati
tick ili
ciklus) treba da pomeri lopticu za 2 piksela udesno.
Što će reći da je cela mudrost pokretanja loptice udesno u našoj igri sledeća: da u našu petlju (tačnije, proceduru Procesuj_Gameworld) stavimo LoptaX = LoptaX + 2. Naravno, mi ne želimo da loptica ide samo udesno (otišla bi sa ekrana pre nego što biste stigli da kažete "hej, pa ova loptica će otići sa ekrana"), pa na primer možemo da uradimo sledeće korake :
- umesto "LoptaX = LoptaX + 2" stavićemo "LoptaX = LoptaX + DeltaLoptaX",
gde će DeltaLoptaX će biti varijabla, koja će imati neku početnu vrednost (npr. 3).
- u svakom ciklusu ćemo proveravati da li se loptica sudarila sa reketom broj 1, i ako jeste, onda ćemo joj promeniti DeltaLoptaX:
If LopticaSeSudarilaSaReketom Then DeltaLoptaX = - DeltaLoptaX
što će efektivno promeniti smer njenog kretanja po horizontali.
Slično tome, možemo da ubacimo: promenu DeltaLoptaY; uslove za odbijanje od vrha i dna ekrana; uslove da ako loptica izađe kroz levu i desnu stranu ekrana (igrač postigao pogodak) da se poveća rezultat za 1 u korist jednog igrača; i tako dalje, i tako dalje.
E sad, šta je sve ovo trebalo da znači?
Manipulišući vrednostima LoptaX i LoptaY na različite načine, ubacujući provere, uslove i grananja, mi možemo da od vrlo jednostavne premise, i manipulišući sa 10ak varijabli, napravimo vrlo lep Pong.
Metodologija rada
Tokom prethodne briljantne demonstracije, trebalo je da primetite kako radi mozak programera: postavi premisu, vidi problem, smisli rešenje. Svi programi su puni bagova i problema. To je normalno. Umesto da odmah krenete da pišete kod celog Ponga, naravno da ćete prvo napisati npr. kod za pozicioniranje lopte. Onda ćete ustanoviti da vam se loptica ne pomera. To posmatrate kao problem koji treba rešiti. Onda smišljate privremeno rešenje, da vam se loptica kreće udesno 2 piksela u svakom ciklusu. Dalje bi tok misli verovatno išao ovako:
Problem : loptica se kreće samo u desnom smeru
Rešenje : kretanje loptice levo-desno staviti u varijablu DeltaLoptaX i svakog ciklusa LoptaX = LoptaX + DeltaLoptaX
Problem : loptica opet samo stoji u mestu
Rešenje : inicijalizovati DeltaLoptaX pri startu programa na 2
Problem : loptica se i dalje kreće samo desno
Rešenje : ubaciti proveru za sudar sa reketima; ako dođe do sudara, loptica će se odbiti. Odbijanje uzrokuje da DeltaLoptaX pomnožimo sa -1
Problem : loptica ide samo levo-desno
Rešenje : ubacićemo DeltaLoptaY
Problem : loptica se ne odbija od gornje i donje ivice ekrana:
Rešenje : ubaciti proveru da li je loptica izašla iz ekrana i ako jeste vratiti je malo prema ekranu i pomnožiti DeltaLoptaY sa -1
I tako dalje, i tako dalje... shvatili ste poentu.
ZAKLJUČAK
Ukratko, ova lekcija (ciljana prvenstveno na početnike) nam je objasnila kako srž naše igre čini petlja koja mnogo puta u sekundi (npr. 25 puta) menja gameworld, tako što menja varijable igre po pravilima koje mi (programeri) postavljamo, a zatim koristi te iste varijable kako bi nacrtala elemente na ekranu... i taj proces se ponavlja... i ponavlja... i ponavlja...
In a game of chess you must never let your opponent see your pieces - Zap
Brannigan