JavaScript tworzenie animacji w grach przy użyciu spritesheet’ów

W dzisiejszym poście pokaże wam jak działa w JavaScript tworzenie animacji w grach. Jak zwykle, nie będę przedstawiał czystej teorii. Pokażę własną grę, w której postaci i obiekty są animowane. Użyłem do tego sprite sheet’a czyli arkusza klatek, ściągniętego z neta.

Ponieważ są święta, moja gra będzie posiada świąteczny motyw 🙂 Aby w nią zagrać, wystarczy kliknąć w obrazek poniżej. Przygotowałem też paczkę z całym projektem. Możecie ją ściągnąć i pobawić się z kodem.

JavaScript tworzenie animacji w grach

Ten wpis będzie krótszy niż kilka ostatnich, ponieważ nie będę pokazywał całego kodu gry z mikołajem. Dla ciekawskich powiem tylko, że jest to w 80 procentach prawie ten sam kod który użyłem w Space Attack. Zresztą chyba nie trudno to zauważyć 🙂 Celem gry jest zebranie jak największej ilości prezentów. Jeżeli pięć prezentów spadnie na ziemie, lub jeżeli mikołaj wejdzie w eksplozje, gra kończy się.

Stany gry oraz pętla uaktualniająca, obsługiwane są przez ten sam mechanizm, który wykorzystywałem wcześniej. Jedyna nowość to tak naprawdę animowanie ruchy obiektów.

Przedstawię kod jednego z obiektów. Jego metody update oraz draw są cyklicznie wywoływane przez silnik gry, co ilość czasu równą sekundzie podzielonej na ilość ustawionych w programie klatek. Obiekt ten porusza się w grze i ruch ten posiada animacje.

JavaScript tworzenie animacji w grach – Przykład – Prezent

Oto kod:

Jako przykład wybrałem klasę Present. Obiekt ten jest uaktualniany cały czas, nie tylko gdy przyciskane są klawisze. Podczas tworzenia instancji przyjmuję dwa argumenty x oraz speedMod. Pierwszy to oczywiście pozioma współrzędna wskazująca na miejsce gdzie prezent pojawia się. Dla każdej instancji będzie ona inna, ponieważ zależy od lokacji samolotu zrzucającego prezenty. Drugi argument to modyfikator do prędkości poruszania się (opadania) prezentu. Jest on zależny od poziomu gry, im wyższy tym szybciej opada prezent.

Pierwsze parę pól powinno wyglądać już znajomo.x oraz y to współrzędne wskazujące na aktualne położenie prezentu. Speed to prędkość opadania prezentu. width oraz height to oczywiście wymiary obiektu, a image to obraz, który go przedstawia. Dla dzisiejszego przykładu ważne są cztery pozostałe pola czyli currFrame, frameNum, frameTick oraz ticks.

Zanim przejdę do dalszego tłumaczenia, najpierw pokażę jak wygląda sprite sheet prezentu:

JavaScript tworzenie animacji w grach

Wymiary obrazka to 90 pikseli długości na 35 pikseli szerokości. Wysokość zgadza się z wartością pola height obiektu Present, ale długość nie. Szerokość obrazka to 90 a wartość pola width to 30. Jednak, jak widać, obrazek składa się z trzech klatek animacji prezentu. Szerokość jednej klatki to 30 (90 dzielone na 3), a to już ma sens. W końcu celem jest wyświetlanie jednej klatki animacji, nie wszystkich na raz. Jeżeli póki co wszystko to wydaje się być beznadziejnie oczywiste, to dobrze 🙂

Ok, ale co zrobić aby te części obrazka rysowały się tak aby tworzyły animację. Do tego służą dwie metody zawarte w obiekcie Present: update i draw. Zanim przejdę do ich omawiania, wrócę do czterech tajemniczych pól tego obiektu czyli currFrame, frameNum, frameTick oraz ticks.

currFrame to skrót od ‚current frame’ czyli aktualna klatka. Podczas tworzenia wynosi ona zero, oznacza to, że jako pierwsze będzie wyświetlać się klatka na początku arkusza klatek. Idealnie. Pole frameNum czyli ‚frame number’ to po prostu liczba klatek na jaką będzie składać się animacja danego obiektu. W tym wypadku jest to trzy. Pozostałe dwa pola, frameTick i ticks, służą do kontrolowania czasu pomiędzy wyświetlaniem się konkretnych klatek. Nie chcę aby klatki zmieniały się z częstotliwością jaką rysowana jest cała gra, czyli w tym wypadku jedna pięćdziesiąta sekundy (setInterval wywołujący funkcję gameLoop ustawiony jest aby powtarzał się co 1 dzielone przez wartość pola fps obiektu config silnika gry, czyli 50. W tym poście opisałem jak to działa). Będzie to stanowczo za często i animacja będzie przez to wyglądała nienaturalnie. Zamiast tego chcę aby klatka zmieniała się co 4 obiegi pętli gry (wartość pola frameTick). Do liczenia obiegów służyć będzie pole ticks.

Teraz opis metody update to tylko formalność 🙂 Jeżeli pole ticks ma większą wartość od pola frameTick, wartość pola currFrame jest podnoszona o jeden a ticks ustawiane jest z powrotem na zero. Jeżeli currFrame osiągnie wartość równą frameNum, również ustawiane jest na zero (dlaczego równą a nie wyższą, to się okaże za chwilę). Jeżeli zamiast tego, ticks nie osiągnęło jeszcze wartości frameTick, jest ono inkrementowane o jeden. Na koniec tego wszystkiego metoda update aktualizuje położenie prezentu.

Teraz czas na metodę draw, ale najpierw znów pozwolę sobie na dygresję. Przedstawię strukturę metody drawImage dostępnej przez kontekst elementu canvas

Choć normalnie wrzuciłbym wszystkie argumenty do jednej linijki, dla czytelności rozpisałem je w trzech. Argumentów jest 9. Pierwszy to odnośnik do obiektu Image, który ma być narysowany. Kolejne cztery do parametry źródła. x i y przedstawiają punkt na obrazku, z którego program ma zacząć wycinać prostokąt o bokach równych szerokości i wysokości. Punkty x oraz y reprezentować będą oczywiście lewy górny wierzchołek tego prostokąta. Oznacza to, że można wyciąć jedynie fragment obiekty Image. To co zostanie wybrane ze źródła wklejane jest w prostokąt na płótnie zdefiniowany w czterech ostatnich argumentach. x oraz y to współrzędne lewego górnego wierzchołka tego prostokąta a szerokość i wysokość to rozmiar boków. Czyli wklejony obraz może być większy niż obraz źródłowy.

Mam nadzieję, że to wszystko zaczyna układać się już w całość 🙂 Metoda draw obiektu Present rysuje tylko aktualną klatkę z arkusza klatek. Zapiszę metodę drawImage z kodu gry w bardziej czytelny sposób:

Pierwsza linijka argumentów to obrazek zdefiniowany w obiekcie, czyli arkusz klatek z trzema klatkami prezentu. Druga linijka to definicja źródła tego co ma zostać narysowane. X równe jest szerokości zdefiniowanej w obiekcie pomnożonej przez obecną klatkę. Y zawsze jest równe zero. Jeżeli currFrame równe będzie 0, x źródła będzie równe zero czyli wskazywany będzie lewy górny wierzchołek arkusza klatek. Jeżeli currFrame będzie wynosić jeden, obszar wycięcia ‚przesunie’ się o szerokość prezentu, czyli na drugą klatkę. Jeżeli currFrame równe będzie dwa, obszar wycięcia wskazywać będzie na trzecią klatkę. Przy currFrame równym trzy, jest ono resetowane przez update aby wynosiło 0… i tak w kółko. Mam nadzieję, że jest to w miarę zrozumiałe. Jeżeli chodzi o cel wyrysowania, to jest to już prosta sprawa. Obrazek zostanie narysowany na canvas w aktualnych współrzędnych obiektu. Jedyna nowość to to, że powiększyłem rozmiar rysowanego obrazka. Prezent widziany ostatecznie przez gracza będzie o 20 pikseli szerszy i wyższy.

I tak to działa w mojej grze. Wszystkie obiekty animowane są w identyczny sposób. Zachęcam do przestudiowania kodu gry i próby rozgryzienia tego na własną rękę. Dobrym przykładem, który można rozgryźć na własną rękę jest obiekt samolotu – ToyPlane. Jego arkusz klatek posiada dwa rzędy, jeden to klatki animacji samolotu poruszającego się w prawo a drugi w lewo. To, który jest rysowany zależy od wartości pola direction. Podpowiem, że może ono wynosić jeden albo zero 🙂

Mam nadzieję, że ten post pomoże w zrozumieniu jak w JavaScripcie tworzone są proste animacje z użyciem arkuszy klatek. Jeżeli są jakieś wątpliwości, nie wahajcie się pytać. Na wszelkie pytania zamieszone na blogu lub na mojej stronie na facebooku na pewno odpowiem.

Na koniec napiszę jeszcze ważną informację. Nie jestem autorem grafik użytych w tej grze. Nie jestem aż tak utalentowany 🙂 Pochodzą one z fantastycznej strony spriters resource. Zostały one zripowane z gry „Daze Before Christmas” przez kogoś kto podpisał się nickiem redblueyellos 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *