Tworzenie gier w JavaScript: requestAnimationFrame

Nie na d艂ugo odszed艂em od tematu tworzenia gier 馃檪 Od pewnego czasu zbiera艂y mi si臋 tematy, z kt贸rymi chcia艂em poeksperymentowa膰. Przede wszystkim z funkcj膮, o kt贸rej s艂ysza艂em, 偶e bardzo usprawnia Tworzenie gier w JavaScript: requestAnimationFrame. Poniewa偶 w 艢wi臋ta cz艂owiek ma sporo wolnego, to zamiast siedzie膰, nudzi膰 si臋 i objada膰 sernikiem, postanowi艂em spo偶ytkowa膰 ten czas i wypr贸bowa膰 par臋 nowych technik programowania gier (no dobra, jedz膮c w tym czasie sernik 馃槈 ).

W taki spos贸b powsta艂 mini-projekcik, z kt贸rego screen widzicie poni偶ej (P贸ki co nie jest to jeszcze gra:)). Du偶o w nim poeksperymentowa艂em i sporo si臋 nauczy艂em. Wszystko oczywi艣cie opisz臋 w tym po艣cie. Projekt mo偶na obejrze膰 klikaj膮c w obrazek poni偶ej. Jak zwykle przygotowa艂em te偶 paczk臋 z kodem, aby ka偶dy m贸g艂 sam sobie pod艂uba膰.

Tworzenie gier w JavaScript - requestAnimationFrame

W projekcie, mo偶emy lata膰 czarownic膮 po lesie zajmuj膮cym teren pomi臋dzy jej chatk膮 a magicznymi g艂azami. Myk polega na tym, 偶e teren ten, jest znacznie wi臋kszy ni偶 obszar wy艣wietlany przez canvas. Gdy czarownica zbli偶y si臋 do kra艅ca wy艣wietlanego, przez p艂贸tno obszaru, pole widzenia przesunie si臋! U偶ytkownik nie jest ograniczony, kraw臋dziami elementu canvas, mo偶e do woli eksplorowa膰 ca艂y 艣wiat projektu.

Do tego, t艂o gry porusza si臋 w innym tempie, co sprawia wra偶enie, 偶e jest dalej. Ten bardzo przyjemny wizualnie efekt, r贸wnie偶 zosta艂 osi膮gni臋ty programowo w canvasie. Opr贸cz tych dw贸ch widocznych cech, nowo艣ci膮 jest te偶 wspomniany wcze艣niej requestAnimationFrame. Teraz ta metoda zajmuje si臋 tym do czego wcze艣niej u偶ywa艂em setInterval i wielu pomocniczych mechanizm贸w obliczaj膮cych up艂ywaj膮cy czas itp.

Ostania innowacja to spos贸b przechowywania grafik z gry. Nie jest to ju偶 wiele plik贸w jak wcze艣niej, a jeden wielki plik png.

Czas przej艣膰 do kodu, jak zwykle HTML nie zawiera nic specjalnego. Jest tu oczywi艣cie element canvas, bez kt贸rego ani rusz. Kod JavaScriptu odpalany jest gdy element body, za艂aduje si臋. Wywo艂ywana zostaje wtedy funkcja startWitch, kt贸ra wygl膮da ona tak:

W tym fragmencie kodu te偶 nie znalaz艂o si臋 nic zaskakuj膮cego. Najpierw pobieram element canvas i przypisuj臋 go zmiennej. Nast臋pnie tworze instancj臋 obiektu gry WitchGame i uruchamiam jej metod臋 loadGame.

Czas przej艣膰 do 鈥歮i臋cha鈥 czyli g艂贸wnego obiektu gry:

Jako argument, konstruktor obiektu przyjmuje referencj臋 do p艂贸tna, kt贸r膮 od razu przypisuj臋 do wewn臋trznego pola. Tworz臋 r贸wnie偶 zmienn膮 self, kt贸ra przechowywa膰 b臋dzie odniesienie do this. self bardzo mi si臋 przyda p贸藕niej. Pobieram te偶 kontekst p艂贸tna i umieszczam w odpowiednim polu. Tworz臋 te偶 pol臋 entities, kt贸re pocz膮tkowo zawiera pust膮 tablic臋. W tej tablicy przechowywane b臋d膮 wszystkie obiekty gry.

Gdy to ju偶 gotowe, tworz臋 pol臋 Entity, kt贸re zawiera obiekt. Jest to jakby szablon, wed艂ug kt贸rego tworzone wszystkie obiekty w pojawiaj膮ce si臋 w grze. Problemem w moich poprzednich grach by艂o to, 偶e wiele kodu w obiektach gry powtarza艂o si臋. Tworz膮c szablon, chc臋 tego unikn膮膰. Wprawdzie mo偶e tego nie by膰 wida膰 w tym projekcie (tworz臋 tylko 3 obiekty, z czego dwa to t艂a ;)), ale w przysz艂o艣ci powinno si臋 sprawdzi膰.

Ok szybki rzut okiem na pola, kt贸re zawiera m贸j szablon. Ju偶 po samych nazwach mo偶na domy艣li膰 si臋 do czego s艂u偶膮. B臋d膮 one przechowywa艂y dane, potrzebne do funkcji rysuj膮cej drawImage. Pierwsze cztery wskazuj膮 na miejsce z obrazka 藕r贸d艂owego, a kolejne cztery wskazuj膮 na miejsce na p艂贸tnie. drawImage opisa艂em w jednym z wcze艣niejszych post贸w. Klasa Entity zawiera te pola, poniewa偶 wszystkie obiekty, kt贸re pojawi膮 si臋 w tym projekcie b臋d膮 ich potrzebowa艂y. Tym razem mam tylko jeden obrazek 藕r贸d艂owy, i ka偶dy z tych obiekt贸w musi zosta膰 najpierw z niego pobrany. Obrazek 藕r贸d艂owy polecam obejrze膰 w paczce z kodem projektu.

A oto instancje klasy Entity:

Mam trzech aktor贸w. background, moonStars oraz witch. Wszytskie tworz臋 za pomoc膮 metody globalnego obiektu Object, create. W skr贸cie, create zwraca kopi臋 obiektu przekazanego jej jako argument i przypisuje j膮 do zmiennej. Dzi臋ki temu wiersz:

powoduje, 偶e obiekt background, ma identyczne pola jak Entity. Oczywi艣cie niekt贸re musz臋 nadpisa膰.

Obrazek t艂a znajduje si臋 na samej g贸rze obrazka 藕r贸d艂owego i zajmuje ca艂膮 jego szeroko艣膰. Nie musz臋 wi臋c zmienia膰 pol x i y a jedynie rozmiar obrazka. Rozmiar po wklejeniu do p艂贸tna b臋dzie r贸wny rozmiarowi w 藕r贸dle.

moonStars jest bardzo podobny. Zawiera on obrazek gwiazd i ksi臋偶yca. Poniewa偶, chc臋 aby by艂y ruchome, nie umie艣ci艂em ich bezpo艣rednio na tle. Zamiast tego znajduj膮 si臋 na samym dole 艂膮czonego obrazka i posiadaj膮 prze藕roczyste t艂o.

Obiekt witch, reprezentuje oczywi艣cie bohaterk臋 gry. Opr贸cz p贸l opisuj膮cych lokacj臋 obrazka w 藕r贸dle i na p艂贸tnie, posiada par臋 dodatkowych. speed zawiera obiekt z dwoma polami x oraz y, b臋da one przechowywa艂y informacje o tym z jak膮 pr臋dko艣ci膮 (o ile pikseli na klatk臋) porusza si臋 czarownica. goingUp, goingDown, goingLeft oraz goingRight, to pola o warto艣ciach boolowskich. True na kt贸rym艣 z tych p贸l oznacza, 偶e czarownica porusza si臋 w danym kierunku. Ostatnie pole czarownicy to facing i oznacza ono stron臋 w kt贸r膮 zwr贸cona jest czarownica.

Kolejne dwa obiekty s膮 do艣膰 wyj膮tkowe

To dzi臋ki tym dw贸j obiektom, gra 鈥歱rzesuwa si臋鈥. Wyja艣nie jak to dok艂adnie dzia艂a troch臋 dalej. P贸ki co opisz臋 mniej wi臋cej informacje, kt贸re przechowuj膮. Pierwsze co mo偶na zauwa偶y膰 to to, 偶e nie s膮 to instancje klasy Entity. Obiekty, te nie s膮 rysowane na planszy, wi臋c nie potrzebuj膮 p贸l Entity. To tylko abstrakcyjne modele, wed艂ug kt贸rych, obliczane b臋dzie co powinno zosta膰 wy艣wietlone na p艂贸tnie. Teraz mo偶e wydawa膰 to si臋 ma艂o zrozumia艂e, ale gdy dojd臋 do obja艣niania mechanizm贸w kt贸re tym zarz膮dzaj膮, powinno by膰 ok.

Obiekt gameWorld ma wymiary r贸wne obiektowi background. To dlatego, 偶e ca艂y 艣wiat gry jest przedstawiony na tamtym obrazku. Ten obiekt potrzebny b臋dzie do kontrolowania, aby widok gry nie 鈥歸yjecha艂鈥 poza zdefiniowany obszar 艣wiata.

Obiekt screen reprezentuje 鈥歸y艣wietlacz鈥 czyli obszar obecnie wy艣wietlany na p艂贸tnie. Jego pocz膮tkowe wsp贸艂rz臋dne x oraz y r贸wne s膮 zero. B臋d膮 zmienia膰 si臋 gdy ekran 鈥歱rzesunie鈥 si臋 aby pokaza膰 dalszy fragment 艣wiata. To znaczy, zmienia膰 b臋dzie si臋 x, poniewa偶 wysoko艣膰 wy艣wietlacza, 艣wiata oraz p艂贸tna s膮 takie same. Szeroko艣膰 orz wysoko艣膰 obiektu screen s膮 takie samy jak wymiary p艂贸tna. Pola speedX oraz prevX, s艂u偶膮 do obliczenia pr臋dko艣ci z jak膮 porusza si臋 ekran, co zarazem pozwoli p贸藕niej na przesuni臋cie gwiazd i ksi臋偶yca.

Obiekt screen posiada te偶 dwie metody: rightScrollZone oraz leftScrollZone. pomagaj膮 one zdefiniowa膰 obszary po lewej i prawej stronie wy艣wietlacza o szeroko艣ci r贸wnej jednej czwartej jego rozmiaru. A dok艂adniej to zwraca warto艣ci graniczne tych obszar贸w. Dzi臋ki funkcjom, kt贸re om贸wi臋 za chwil臋, ekran zacznie przesuwa膰 si臋, kiedy czarownica przekroczy te granice.

Czas przej艣膰 do metod g艂贸wnego obiektu gry. One wykorzystuj膮 informacje w polach kt贸re opisa艂em powy偶ej. Najpierw loadGame, kt贸ra jest odpalona zaraz po za艂adowania strony:

W tej metodzie tworz臋 nowe pole masterSprite. B臋zie ono zawiera艂o nowy obiekt Image, reprezentuj膮cy obrazek ze wszystkimi grafikami. Opr贸cz adresu obrazka do obiektu przypisuj臋 r贸wnie偶 eventListner, kt贸ry nas艂uchuje na event load. To jest nowo艣膰. Funkcja przekazana do listenera zostanie wykonana dopiero kiedy obrazek sko艅czy si臋 艂adowa膰. Poniewa偶 obrazek jest poka藕nych rozmiar贸w, jest to praktycznie niezb臋dne. Unikn臋 dzi臋ki temu sytuacji, w kt贸rych obiekty zostan膮 narysowane na p艂贸tnie zanim ich grafiki si臋 wczytaj膮, przez co nie b臋d膮 widoczne.

Gdy obrazek ju偶 si臋 za艂aduje wywo艂ana zostaje metoda start:

Najpierw dodaj臋 wszystkie obiekty, kt贸re maj膮 by膰 rysowane do tablicy entities. Dodane na pocz膮tku b臋d膮 rysowane jako pierwsze. Musz臋 o tym pami臋ta膰, 偶eby przypadkiem nie przykry膰 czarownicy obrazkiem t艂a. Nast臋pnie dodaj臋 dodaj臋 do gry nas艂uchiwania na przyci艣ni臋cie klawiszy. Nie jest to nic nowego, moje poprzednie projekty, korzysta艂y ju偶 z takich listener贸w. Je偶eli zostanie naci艣ni臋ta kt贸ra艣 ze strza艂ek, odpowiednie pole w obiekcie czarownicy otrzymuje warto艣膰 true. Gdy przycisk zostanie puszczony przez u偶ytkownika, pole otrzymuje warto艣膰 false. W przypadku strza艂ek w lewo i prawo zmieniam r贸wnie偶 warto艣膰 pola facing czarownicy. Dzi臋ki temu, b臋d臋 m贸g艂 zmienia膰 obrazek tak aby patrzy艂a w stron臋 w kt贸r膮 leci 馃檪 . Nie jest to mo偶e idealny system, kontroli postaci ale na potrzeby tego projektu si臋 sprawdza.

Na ko艅cu funkcja start uruchamia funkcj臋 update:

I tutaj zaczyna si臋 ju偶 robi膰 ciekawie. Najpierw uruchamiam requestAnimationFrame. Ta jedna linijka zast臋puje ca艂y mechanizm setInterval z moich poprzednich projekt贸w. Przyjmuje ona dwa parametry. Pierwszy to funkcja, kt贸ra ma wywo艂a膰 kiedy przegl膮darka jest gotowa na wy艣wietlenie kolejnej klatki animacji, a druga to odno艣nik do canvas, na kt贸rym animacja ma miejsce. I tyle. Wszystkie inne obliczenia zwi膮zane z klatkami, up艂ywem czasu i tak dalej, s膮 wykonywane automatycznie. Idealnie.

Nast臋pnie mam 6 wyra偶e艅 if. Pierwsze cztery, zmieniaj膮 pr臋dko艣膰 czarownicy w zale偶no艣ci w jakim kierunku si臋 porusza (Pisz膮c pr臋dko艣膰 mam oczywi艣cie na my艣li o ile zmieni si臋 jej po艂o偶enie wzgl臋dem osi wsp贸艂rz臋dnych na canvasie, ale to ju偶 wszyscy wiemy 馃檪 ). Oczywi艣cie pod warunkiem, 偶e nie porusza si臋 w tym samym czasie w przeciwnym kierunku. Dwa ostatnie ify, zmieniaj膮 pr臋dko艣膰 czarownicy na zero, je偶eli nie rusza si臋 w og贸le. Logiczne.

Kolejne dwie linijki, zmieniaj膮 po艂o偶enie wied藕my, zale偶nie od jej aktualnej pr臋dko艣ci.

Nast臋pnie pojawiaj膮 dwie linijki, kt贸re sprawiaj膮, 偶e czarownica nie wyleci poza zdefiniowany przez gameWorld 艣wiat gry. Wykorzystuj臋 do tego bardzo sprytne funkcje obiektu Math: max oraz min. Funkcja max zwraca najwi臋ksza z podanych jako argumenty. Funkcja min robi to samo z t膮 r贸偶nic膮 偶e zwraca najmniejsz膮. Jak si臋 to ma do po艂o偶enia czarownicy.

Moim celem jest utrzymanie x oraz y czarownicy w granicach pomi臋dzy zerem a rozmiarami 艣wiata. Dlatego musz臋 sprawdzi膰 czy po ich po艂o偶enia czarownicy, te warto艣ci nie zosta艂y przekroczone. Funkcja min zwraca to co jest mniejsze, obecny x/y czarownicy, lub rozmiar 艣wiata. Je艣li czarownica przekroczy granice, zostanie cofni臋ta. Wynik tej operacji jest przekazany jako argument wraz z zerem do funkcji max. To niez艂y trick. Nie ma mo偶liwo艣ci aby zosta艂a zwr贸cona warto艣膰 mniejsza ni偶 zero lub wi臋ksza od rozmiaru 艣wiata.

Kolejne dwa wyra偶enia warunkowe sprawdzaj膮 czy czarownica nie przekroczy艂a kt贸rej艣 z granic przesuwnia wy艣wietlacza. Je偶eli tak, x wy艣wietlacza, zmniejsza lub zwi臋ksza (zale偶nie od tego, w kt贸r膮 stron臋 leci czarownica) o tak膮 warto艣膰 o jak膮 czarownica, przelecia艂a przez dan膮 granic臋. Dlaczego nie testuj臋 warto艣ci y? Nie ma to sensu, wysoko艣膰 wy艣wietlacza jest taka sama jak wysoko艣膰 艣wiata. Jednak gdyby zasz艂a taka potrzeba, m贸g艂bym bez problemu doda膰 strefy przewijania przy g贸rnej i dolnej kraw臋dzi wy艣wietlacza, a w tym miejscu sprawdza膰 czy nie zosta艂y przekroczone.

Powoli zbli偶am si臋 do ko艅ca metody update. Nast臋pna jej cz臋艣膰 to zn贸w dwa 鈥歩fy鈥. Pierwszy sprawia, 偶e x wy艣wietlacza nie b臋dzie mniejsze od zera a drugi, 偶e nie b臋dzie wi臋kszy od wielko艣ci 艣wiata minus szeroko艣膰 wy艣wietlacza. Dzi臋ki temu, wy艣wietlacz b臋dzie pokazywa艂 tylko obiekty w granicach 艣wiata.

Ostatnie trzy linijki odpowiadaj膮 za to, 偶e gwiazdy i ksi臋偶yc przesuwaj膮 si臋 wolniej ni偶 ziemia i drzewa. OK, postaram wyja艣ni膰 jak to dzia艂a. Przede wszystkim, drzewa i ziemia nie poruszaj膮 si臋 w og贸le. Porusza si臋 wy艣wietlacz, co powoduje, 偶e patrz膮cemu wydaje si臋, 偶e poruszaj膮 si臋 obiekty. To tak jak po paru godzinach w PKP patrz膮c przez okno wydaje si臋, 偶e to pola i lasy p臋dz膮 obok stoj膮cego poci膮gu. To tylko iluzja. Natomiast gwiazdy i ksi臋偶yc naprawd臋 si臋 przesuwaj膮. Ale przesuwaj膮 si臋 wolniej ni偶 wy艣wietlacz, przez co po pewnym czasie zostaj膮 w tyle! Wystarczy co ka偶d膮 klatk臋 troch臋 zwi臋kszy膰 warto艣膰 ich pola x, przez co zostaj膮 w wy艣wietlaczu d艂u偶ej. Dzi臋ki temu uzyskiwany jest ten przyjemny dla oka efekt, 偶e znajduj膮ce si臋 daleko obiekty poruszaj膮 si臋 wolniej. OK, tyle teorii, teraz praktyka

Najpierw obliczam o ile pikseli przesun膮艂 si臋 wy艣wietlacz. Od jego poprzedniej warto艣ci x odejmuj臋 aktualn膮. (poprzedni膮 warto艣膰 zapisuj臋 co ka偶d膮 klatk臋, aby mie膰 do niej 艂atwy dost臋p). Gdy ju偶 to wiem, przesuwam gwiazdy i ksi臋偶yc o troch臋 mniej. I gotowe! Do tego mechanizm ten dzia艂a niezale偶nie od tego w kt贸r膮 stron臋 przesuwa si臋 wy艣wietlacz. Je偶eli przesunie si臋 w lewo, warto艣ci b臋d膮 ujemne i wszystko zadzia艂a jak nale偶y, gwiazdy 鈥歸r贸c膮鈥 na swoje miejsce.

Ostatnia linijka metody update, to wywo艂anie metody draw.

To ju偶 prawie koniec. Ta metoda nie powinna by膰 zaskoczeniem dla kogo艣 kto zna moje poprzednie projekty. Po prostu wyrysowuj臋 wszystkie obiekty, w ich aktualnym stanie, na p艂贸tnie. Je偶eli obiekt posiada pole facing, to znaczy, 偶e jet czarownic膮 i trzeba narysowa膰 inny ni偶 zwykle obrazek.

Zosta艂a tylko jedna rzecz do om贸wienia, Na pocz膮tku pojawia si臋 metoda kontekstu save a na ko艅cu restore. Metody te s艂u偶膮 do kolejno, zapami臋tania aktualnego punktu na kt贸ry wskazuje x:0 i y:0 oraz do przywr贸cenia go. Potrzebne s膮 mi, poniewa偶 zmieniam po艂o偶enie tego punktu, robi臋 to metod膮 translate. Metoda translate przyjmuje dwa parametry, po jej wywo艂aniu, punkt o wsp贸艂rz臋dnych okre艣lony tymi dwoma parametrami, jest teraz przez p艂贸tno traktowany jako punkt 0,0.

Co ja przekazuj臋 metodzie translate? Aktualny x oraz y wy艣wietlacza. Ale odwracam te warto艣ci, co oznacza, 偶e punkt rysowania zostanie przesuni臋ty o x wy艣wietlacza w lewo i o y wy艣wietlacza w g贸r臋. Potem gdy obiekt background zostanie narysowany wzgl臋dem tego punktu, w wy艣wietlaczu poka偶e si臋 dok艂adnie to co chc臋 aby si臋 pokaza艂o.

To w艂asnie ostatni kawa艂ek uk艂adanki. Po przestudiowaniu tego kodu par臋 razy, na pewno zobaczycie, jak prosta idea kryje si臋 za przesuwanym 艣wiatem gry. Nic nie stoi na przeszkodzie, 偶eby stworzy膰 鈥毰泈iaty鈥 kt贸re s膮 wy偶sze ni偶 wy艣wietlacz. St膮d kr贸tka droga do tworzenia platform贸wek i wielu innych ciekawych gier.

A co z czarownic膮? Szczerze m贸wi膮c, nie wiem. Je艣li macie jakie艣 pomys艂y na to jak rozwin膮膰 ten projekt, podzielcie si臋 nimi w komentarzach. Mo偶na te偶 kontaktowa膰 si臋 ze mn膮 przez moj膮 stron臋 na facebooku. Przy okazji zach臋cam mocno do polubienia. Dzi臋ki temu na pewno b臋dziesz na bie偶膮co ze wszystkimi nowinkami z bloga. Nowe posty ju偶 nied艂ugo 馃檪

Dodaj komentarz

Tw贸j adres email nie zostanie opublikowany. Pola, kt贸rych wype艂nienie jest wymagane, s膮 oznaczone symbolem *