(WIP) Space Attack – Gra napisana w HTML5 canvas

Nadszedł czas na nowy projekt. Od węża nie zamiesciłem nic stworzonego w technologii canvas. Dziś wielki powrót do tego cuda. Muszę przyznać, praca z ‚płótnem’ sprawia mi największą radość. Kod praktycznie pisze się sam 🙂 .

Nowy projekt to Space Attack – gra napisana w HTML5 canvas. Będzie to klasyczny space shooter, coś na kształt kultowej gry Galaga. Obecnie nie jest to pełna gra. Do teraz stworzyłem tylko główną scenę. Znajduje się na niej statek kontrolowany przez gracza. Może on poruszać się na boki, oraz odpalać rakiety. Do tego w tle gry poruszają się gwiazdy. Na razie to wszystko. To taka wczesna wersja alfa 🙂 .

Zachęcam do sprawdzenia dema gry. Tym razem przygotowałem również paczkę z plikami projektu do pobrania.

Space Attack - gra napisana w HTML5 canvas

Space Attack – gra napisana w HTML5 canvas – kod

Przejdę do konkretów, czyli do kodu. Tym razem nie wrzuciłem wszystkiego do jednego pliku html. Zastosowałem trochę bardziej profesjonalne podejście. W folderze projektu, stworzyłem dwa podfoldery. Jeden dla grafik i jeden dla kodu JS. Wszystko można znaleźć w paczce z kodem źródłowym gry.

Zacznę od pliku HTML. Większość kodu jest tutaj standardowa. Dodaje style dla elementów canvas. Elementowi body przypisuję funkcję odpalaną w evencie onload. na końcu dokumentu, dołączam plik JS. Ciekawe jest to, że dodaje dwa elementy canvas. Jeden będzie przedstawiał tło gry, a drugi samą grę. Stylami umieściłem je na sobie, ale canvas odpowiedzialny za tło, otrzymał z-index o wartości -1. Dzięki temu będzie zawsze wyświetlany na spodzie.

Czas na plik JS. Na początek, kod funkcji startSpaceAttack. Jest to funkcja odpalana podczas onload elementu body

Najpierw pobieram oba elementy canvas i zapisuję je do odpowiednich zmiennych. Następnie tworzę instancję obiektu Game i odpalam jego metodę initGame. Jak widać konstruktor obiektu Game przyjmuje 5 argumentów. Pierwsze dwa to utworzone wcześniej zmienne wskazujące na elementy w dokumencie HTML. Kolejne argumenty to obiekt odpowiedzialny za obsługę tła, obiekt przedstawiający statek gracza oraz obiekt przedstawiający pocisk statku gracza.

Tym razem nie zacznę od opisu głównego obiektu Game. Najpierw skupie się na klasach, które przyjmuje jako parametry. Jako pierwszy będzie to Background.

Zachowanie tego obiektu nie jest zbyt skomplikowane. Metoda initStars tworzy tablicę obiektów, reprezentujących gwiazdy. Pola tych obiektów mieszczą informacje na temat lokacji, rozmiaru i szybkości poruszania się gwiazd. Dla każdej gwiazdy wartości te ustalane są losowo.

Metoda updateStars przesuwa gwiazdy w dół płótna, aktualizując wartość ich pola y. Jeżeli któraś z gwiazd dotrze do dolnego krańca pola gry, zostaje przeniesiona na górną krawędź. Warto tutaj zwrócić uwagę, że stosuję nowy sposób obliczania o ile przesunął się obiekt. Tym razem canvas odświeżany jest fps razy na sekundę. Dzięki temu łatwo kontrolować mi liczbę wyświetlanych klatek na sekundę. Obliczenie faktycznego przesunięcia gwiazdy polega teraz na podzieleniu 1 przez fps (co daje nam faktyczny czas jaki minął od ostatniej klatki) i pomnożenie tego przez prędkość gwiazdy (pole speed). Wartość fps przekazywana jest obiektowi jako argument konstruktora podczas tworzenia.

Metoda drawStars, używając pętli for, rysuje wszystkie gwiazdy na płótnie.

Na koniec metoda initbg najpierw inicjalizuje gwiazdy a następnie ustawia interval, które co sekundę podzieloną przez fps, wywołuje metody updateStars oraz drawStars.

I takim prostym sposobem tworzy się tło animowanego kosmosu.

Kolejna część kodu to klasa Rocket.

Konstruktor tej klasy przyjmuje cztery parametry. Adres obrazka rakiety w formie łańcucha znaków, wskaźnik do elementu canvas (to już ten główny, nie ten dla tła), liczbę klatek na sekundę oraz poziome położenie początkowe rakiety. Dlaczego tylko poziome? bo nowa rakieta zawsze będzie pojawiać się na tej samej wysokości, zaraz nad statkiem 🙂 Podczas tworzenia nowego obiektu, wystarczy wiedzieć gdzie statek jest na płótnie w poziomie, ponieważ nie porusza się w pionie.

Jeśli chodzi o pola obiektu, większość jest dość oczywista. Te, na które warto zwrócić uwagę to entityType, offBoard oraz rocketPic. entityType to łańcuch znaków, który będzie wykorzystywany w głównym obiekcie gry. Podobnie jest z polem offBoard. Przyjmuje ono wartość boolowską, oznaczającą czy wystrzelony pocisk znajduje się jeszcze na planszy, czy już ją opuścił. Ostatnie ważne pole to rocketPic. Temu polu przypisywana jest nowa instancja obiektu Image. Jest to obiekt wbudowany w języku JS. Wartość pola src tego obiektu to przekazany w konstruktorze adres obrazka. Taki obiekt potrzebny jest aby na elemencie canvas narysować obraz zaimportowany z pliku.

Klasa Rocket posiada też dwie metody. update oraz draw. Każdy obiekt, który pojawi się w grze będzie posiadał te metody. Służą one do zaktualizowania lokacji obiektu oraz narysowania go na canvasie. Jak widać, w przypadku rakiety aktualizowane jest jej tylko pionowe położenie. Przy okazji, sprawdzane jest, czy rakieta nie wyleciała poza element canvas, jeżeli tak, jej pole offBoard ustawiane jest na true.

Metoda draw wykorzystuje wbudowaną metodę kontekstu canvas. W moim przypadku przyjmuje trzy parametry. Pierwszy to instancja obiektu Image, którego pole src zawiera adres do obrazka, który ma być narysowany. Następne dwa parametry to współrzędne na elemencie canvas, które będą wskazywać na lewy górny róg narysowanego obrazka.

Następna klasa, którą opiszę to Ship.

Podobnie jak z klasą Rocket, większość pół w klasie Ship nie jest niczym nadzwyczajnym. Te, na które dobrze jest zwrócić uwagę to onCooldown, cooldownTimer oraz cannonCooldownTime. Te pola będą przechowywać informacje potrzebne do sterowania mechanizmem przegrzania dział statku. Za każdym razem gdy statek wystrzeli pocisk onCooldown zmieni się na true. Dopóki będzie w takim stanie, statek nie może strzelać. onCooldown będzie znów wynosić false, kiedy zmienna cooldownTimer (inkrementowana co przebieg głównego interval gry) nie osiągnie wartości równej cannonCooldownTime.

Reszta pól jest standardowa. Statek początkowo umieszczany jest na środku elementu canvas, przy jego dolnej krawędzi.

Klasa Ship posiada dwie metody. draw, oraz move. Działanie draw jest dość standardowe, obrazek z pola zawierającego obiekt Image jest rysowany na canvas, na zdefiniowanych w klasie współrzędnych. Natomiast metoda move jest trochę wyjątkowe. Wbrew temu co pisałem wcześniej, klasa statku nie posiada metody update a właśnie draw. Wynika to z tego, że statek nie porusza się automatycznie, a jedynie kiedy gracz naciśnie odpowiednie przyciski. kod klawisza, przekazywany jest jako parametr metodzie. W zależności, od tego która strzałka została naciśnięta, wartość x statku wzrośnie, lub pomniejszy się. Formuła, wg której jest to obliczane jest taka sama jak w przypadku rakiety, zależna od pól speed oraz fps. Do tego sprawdzam, czy statek przypadkiem nie wychodzi poza krawędzie canvas.

I tak dochodzę do ostatniego elementu opisywanego programu, czyli klasy Game.

Tę klasę opiszę dokładniej. Zacznę od pól.

  • fps – liczba klatek na sekundę gry. Wartość ustawiona na 60.
  • gameBackground – zmienna wskazująca na element canvas. Wartośc przekazana jako parametr konstruktora.
  • backgroundHandler – do tego pola przypisana jest instancja klasy Background.
  • gameBoard – zmienna wskazująca na element canvas. Wartośc przekazana jako parametr konstruktora.
  • ctx – kontkest głównego elementu canvas.
  • shipPicSrc – adres obrazka statku.
  • rocketPicSrc – adres obrazka rakiety.
  • ship – instancja klasy Ship
  • rocket – zmienna wskazująca na klasę Rocket. Nie tworzę tu instancji, tak jak w przypadku statku, ponieważ rakiet będzie na planszy wiele.
  • actors – tablica, która zawierać będzie wszystkie aktualnie znajdujące się na planszy obiekty

Metody.

  • initBackground – Ta metoda uruchamia instancję klasy Background
  • initGame – Ta metoda wywołuje metody initBackground oraz makeKeysWork. Następnie do tablic actors dodawana jest instancja klasy ship. Gdy to jest już gotowe, odpalana jest interval, która co sekundę dzieloną na fps wywołuje metody updateActors oraz drawActors.
  • updateActors -Ta metoda, za pomocą pętli for, przechodzi przez każdy element tablicy actors. Dla każdego elementu, kod sprawdza czy typ obiektu to ship czy rocket. Jeżeli ship, którego pole onCooldown równe jest true, pole cooldownTimer jest dekrementowane. Jeżeli cooldownTimer dojdzie do zera, onCooldown zmieniane jest na false.
    Jeżeli typ aktualnego elementu tablicy actors to rocket, wywoływana jest jej medtoda update. Jeżeli pole offBoard jest równe true, ten obiekt usuwany jest z tablicy.
  • drawActors – Ta metoda, podobnie do poprzedniej, przechodzi przez tablicę actors i wywołuje metodę draw każdego jej elementu.
  • makeKeysWork – Ta metoda do obiektu window przypisuje nasłuchiwanie eventu keydown. Jeżeli zostanie naciśnięta spacja, kod wywołuje metodę fireRocket. Jeżeli naciśnięta zostanie strzałka w lewo lub w prawo, kod wywoła metodę moveShip.
  • moveShip – Ta metoda wywołuje metodę move instancji obiektu ship.
  • fireRocket – Ta metoda, ustawia pole onCooldown instancji ship na true. Następnie polu cooldownTimer tego samego obiektu przypisuje wartość pola cannonCooldownTime. Na koniec do tablicy actors dodawana jest nowa instancja klasy Rocket.

I to jest cały kod! Mam nadzieję, że udało mi się w miarę zrozumiale przekazać tok myślenia, który stał za budowaniem tej gry. W następnej kolejności dodam wrogów, do których będzie można strzelać 🙂 . Na pewno też zastanowię się nad obecną strukturą, pewne rozwiązania nie są dla mnie idealne, ale póki co nic lepszego nie wpadło mi do głowy.

Ponieważ moje skrypty stają się coraz dłuższe, muszę ograniczać tłumaczenie każdego ich aspektu. Pewne sprawy tłumaczę powierzchownie mając nadzieję, że są zrozumiałe. Jeżeli jednak coś jest niejasne dajcie mi znać. Na każde pytanie na pewno odpowiem i postaram się wytłumaczyć wszelkie niejasności.

Najlepiej zwracać się do mnie przez moją stronę na facebooku. Warto też ją polubić, aby zawsze być na bieżąco z nowymi postami. A tych będzie coraz więcej i będą coraz ciekawsze 🙂

Dodaj komentarz

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