(WIP) JSetpac – gra co miesiąc: luty – aktualizacja

Pracę nad grą lutego trwają w najlepsze. Doszło sporo nowości 🙂 . W aktualnej wersji, gra tworzy dość zgraną całość, dlatego postanowiłem, że czas na wpis.

Gra już trochę bardziej przypomina oryginał niż ostatnio. Wiele rzeczy robię z pamięci, więc nie gwarantuje, że wszystko będzie działać identycznie jak w pierwotnym JetPacu.

JavaScript Noob Tworzenie Gier

W aktualną wersję gry, zagrać można klikając w obrazek powyżej. Jak zwykle udostępniam też paczkę z kodem.

Celem gry jest zbudowanie rakiety i napełnienie jej paliwem. Gdy jest już gotowa, można do niej wsiąść i odlecieć do kolejnego poziomu. Cały ten mechanizm jest już zaimplementowany.

Teraz po rozpoczęciu gry, na ekranie pojawią się trzy części rakiety. Dwa są nie na miejscu. Można je podnieść podlatując na nie i wciskając ‚z’. Aby dodać część do rakiety, wystarczy podlecieć nad miejsce, w którym stoi. Jeśli przypadkiem podniesiona zostanie nie ta część co trzeba, można ją odłożyć wciskając ‚x’.

Po zbudowaniu rakiety, na planszy zaczną pojawiać się pojemniki z benzyną. Traktuje się je tak samo jak część rakiety. Można je podnieść wciskając ‚z’ i wystarczy polecieć z nimi nad stojącą rakietę.

JSetpac – aktualizacja – Kod

Czas przejść do kodu. Jak zwykle przy większych projektach, nie będę tłumaczył go linijka po linijce. Zamiast tego ogólnie opiszę działanie tego co co doszło. Zakładam, że każdy czytelnik jakoś poradzi sobie z resztą. Jeśli jednak coś będzie niezrozumiałe, śmiało pytaj w komentarzach. Postaram się wszystko wyjaśnić.

Większość nowego kodu znajduje się w głównym stanie gry. Po za nim zmieniło się tylko parę drobnych rzeczy. Stan storyState zmienił się na announceState. Nie będę wyświetlał graczowi fabuły gry, tak jak wcześniej zakładałem. Zamiast tego, wykorzystam stan to powiadomienia go, na którym jest etapie.

Odkryłem też duży błąd. W poprzedniej wersji, przypisywałem setIntervale do lokalnego scope’a metody init aktualnego stanu. Przez co nie były zamykane po zakończeniu stanu. Błąd został poprawiony, i pętle są przypisywane do obiektu stanu tak jak powinny od samego początku.

Potrzebowałem systemu do wyświetlania w grze komunikatów, które informowałyby gracza o jego poczynaniach. Stworzyłem w tym celu obiekt messageMachine, oto kod:

Wewnątrz obiektu tworzę dwie tablice. Jedna będzie przechowywała tekst do wyświetlenia a druga czasy przez jaki jest widoczny. dispLimit, to czasowy limit wyświetlania określony w ilościach obiegu pętli. Ostanie pole, to pozioma współrzędna w jakiej chcę umieścić tekst. Będzie to środek ekranu.

Dalej znajdują się dwie metody, addMsg oraz removeMsg. Pierwsza przyjmuje jako parametr łańcuch znaków, następnie wrzuca go do tablicy texts, a do tablicy dispTimes wrzuca zero. Druga metoda usuwa z obu tablic element znajdujący się pod indeksem przekazanym w parametrze. Ponieważ czasy i teksty usuwane i dodawane są parami, ten sam indeks wskazuje wiadomość oraz przypisany jej czas wyświetlania.

Metoda updateMsgs przechodzi pętlą przez czasy wyświetlania. Jeżeli któryś jest większy niż dispLimit, wywoływana jest metoda removeMsg z jego indeksem. W przeciwnym wypadku czas jest zwiększany o jeden.

Ostatnia metoda, drawMsgs, przy pomocy pętli przechodzi przez tablic tekstów i wypisuje je na ekranie.

Kolejna porcja nowego kodu to obiekty reprezentujące części rakiety.

Najpierw tworzę na podstawie ogólnego obiektu spriteObject, obiekt rocketPart. Będzie on wzorem na pozostałe części rakiety. Zawiera on trzy ważne pola o wartości boolwskiej: fallingOnPlace, onPlace oraz isCarried. Wszystkie domyślnie mają wartość false.

Gdy gracz podleci nad lądowisko rakiety, z odpowiednią częścią, zaczyna ona sama opadać. Wtedy fallingOnPlace równe jest true. Gdy opadnie już na swoje miejsce, wartość onPlace także zmieni się na true true. Jak łatwo się domyśleć wartość isCarried otrzyma wartość true, gdy część rakiety będzie niesiona przez gracza.

W tym obiekcie znajduje się również pole onPlaceX. Jest to pozioma współrzędna, w której rysowane będą części, gdy zostaną już umieszczone na swoim miejscu.

Obiekt rocketPart posiada dwie metody. Pierwsza z nich to update. Jej działanie to po prostu dodanie do wartość y obiektu, wartości grawitacji. Dzięki temu obiekty będą poddawane sile przyciągania ziemskiego 🙂 wartość grawitacji dzielę przez dwa, ponieważ chcę aby spadały one trochę wolniej.

Druga metoda, to draw, która wyrysowuje obiekt na płótnie. Warto zwrócić uwagę, że dopóki wartość onPlace obiektu nie jest prawdziwa, wyrysowywany jest normalnie. W przeciwnym wypadku, obiekt rysowany jest na swoich współrzędnych onPlaceX oraz onPlaceY.

onPlaceX jest takie samo dla wszystkich części, onPlaceY jest różne. To dlatego, że razem obiekty tworzą całość rakiety i są ustawianie na sobie w pionie.

Dalsza część kodu to instancje obiektu rocketPart, dziedziczą one jego wszystkie właściwości. Posiadają też jednak własne pola. Wspomniany wcześniej onPlaceY. Do tego pole type, określające typ części, oraz pole prev określające jaka część powinna znajdować się przed nią. Obiekt reprezentujący silnik rakiety, na początku jest już ustawiony onPlace, więc nie posiada normalnych wartości x oraz y. Nie posiada też pola prev, ponieważ jest ono nie potrzebne.

Wyjątkowym obiektem dziedziczącym po rocketPart, jest obiekt fuelTank, reprezentujący benzynę do rakiety. Ponieważ będzie on się pojawiał w czasie gry parę razy, ma trochę inne pola. Od zwykłych części różni się też rozmiarem, co odzwierciedlają odpowiednie właściwości. Kolejna różnica to pole onScreen, które określa czy jest akurat widoczny. Aby przypadkiem nie został narysowany na płótnie zbyt wcześnie, jego x oraz y ustawiłem tak aby wskazywały poza planszę.

Gdy obiekty części rakiety są już gotowe wrzucam je do odpowiedniej tablicy, zadeklarowanej jako pole stanu:

Nie trafia tam obiekt benzyny, ponieważ będzie traktowany trochę inaczej.

Kolejny obiekt tworzony w metodzie init to rocketLandingZone:

Ten obiekt reprezentuje miejsce, w którym budowana będzie rakieta. W odpowiednich polach zawiera wymiary oraz lokacje tego miejsca. W tablicy onPlaceParts, przechowuje również informacje, które obiekty są już na miejscu. Początkowo jest to silnik rakiety.

rocketLandingZone posiada także jedną metodę: draw. Służy ona oczywiście do rysowania dodanych do lądowiska części rakiety. Przechodzi pętlą przez onPlaceParts i wywołuje metodę draw obiektów, których nazwa znajduje się w tablicy. Te części będą miały wartość onPlace równą true, więc na pewno zostaną narysowane gdzie trzeba.

Ostatni obiekt związany z rakietą, który tworzę w metodzie init to rocket:

Ten obiekt to cała rakieta, która pojawi się w grze, gdy gracz ją zbuduje, napełni paliwem i wejdzie do środka. Reguluje to pole blastOff, które przez całą grę równe będzie false, aż do momentu spełnienie wyżej wymienionych warunków. Metoda obiektu draw nie robić nic zaskakującego. a metoda update zmniejsza wartość pola y rakiety. W ten sposób rakieta ‚uniesie się’, ku przestworzom 🙂

Z nowości w metodzie init to tyle. Jedynie obiekt player, otrzymał dwa nowe pola inRocket oraz carrying. Oba początkowo ustawione są na false. Pierwsze, definiuje czy gracz znajduje się już w gotowej do startu rakiecie. Drugie czy niesie przy sobie część rakiety lub benzynę.

Następnie mam metodę update stanu gry, w której zmieniło się sporo:

Teraz aktualizuje postać gracza tylko jeśli nie jest w rakiecie. Przy okazji też sprawdzam czy coś podnosi przy pomocy metody handlePlayerPickups. Jeżeli na ekranie znajduje się kanister z benzyną, również jest aktualizowany.

Następnie pętlą sprawdzam każdy element rocketParts. Każdy z nich jest aktualizowany. Jeśli któryś z nich jest niesiony przez gracza (isCarried), wywołane są metody dropItemToZone oraz updateCarriedItem. Na koniec jeśli aktualny przedmiot ma pole itemFallingOnPlace równe true, wywołuję itemFallingOnPlace. Wszystkie te metody jako argument przyjmują aktualny element rocketParts.

Ten sam proces odbywa się na obiekcie fuelTank, o ile jego pole onScreen równe jest true.

Kolejne parę linijek to sprawdzanie czy któreś z obiektów, łącznie z graczem, nie wpadają na platformy.

Dalej jest proste sprawdzenie, czy rakieta jest kompletna (3 elementy w tablicy onPlaceParts obiektu rocketLandingZone), czy kanister nie jest na ekranie (!this.fuelTank.onScreen) i czy bak rakiety nie jest pełny (this.rocketFuelMeter < 100). Jeśli tak oznacza to, że należy dodać nowy kanister do gry. Robię to za pomocą metody spawnFuelTank.

Następnie sprawdzam czy bak rakiety jest pełny. Jeżeli tak i jeżeli wykryta zostanie kolizja pomiędzy postacią a lądowiskiem, odpalam rakietę. Pola inRocket obiektu player oraz blastOff obiektu rocket ustawiam na true;

Następne linijki aktualizują rakietę (jeśli jest odpalona), oraz wyświetlane wiadomości.

Ostatni fragment kodu sprawdza, czy rakieta wyleciała już poza ekran. Jeżeli tak, aktualizuje odpowiednie pola, zamykam stan i przechodzę do następnego.

Metody draw nie będę opisywał. Można obejrzeć ją na własną rękę w kodzie źródłowym (zresztą mam nadzieję, że i tak każdy tam zerka 🙂 ). Obiekty rysowane są warunkowo, zależnie od ich stanów. Nie powinno być tam niczego trudnego do zrozumienia.

Pozostały tylko metody pomocnicze. Większość z nich też jest mało skomplikowana. Omówię tylko dwie itemFallingOnPlace oraz handlePlayerPickups.

Ta metoda, pomimo pokaźnych rozmiarów, nie jest specjalnie skomplikowana. Najpierw ustawiam x obiektu tak aby wycentrował się nad lądowiskiem. Następnie sprawdzam, czy obiekt opadł już na tyle nisko, żeby dodać go do rakiety. Jeżeli tak, po typie sprawdzam co powinno stać się dalej. Jeżeli to nie benzyna, dodaje typ tego obiektu do tablicy onPlaceParts obiektu rocketLandingZone. Następnie ustawiam jego x oraz y tak aby znalazły się poza planszą (nie chcę aby przypadkiem zaszła jakaś interakcja pomiędzy niewidocznymi już obiektami a graczem). Na koniec wyświetlam odpowiednie powiadomienie.

W przypadku benzyny, sytuacja wygląda podobnie. Po prostu zwiększam wartość rocketFuelMeter o 20.

Następna funkcja odpowiada za podnoszenie i upuszczanie przedmiotów przez gracza.

Pierwsza połowa tej metody jest pomimo swoich rozmiarów, dość prosta. Jeśli wciśnięte jest ‚z’ sprawdzam każda część rakiety oraz zbiornik z paliwem, czy nie ma kolizji pomiędzy nimi a graczem. Jeżeli tak, aktualny obiekt zmienia wartości odpowiednich pól na true. Pole carrying gracza również ustawiam na true. Oczywiście po udanej próbie podniesienie przedmiotu, wyświetlam odpowiedni komunikat.

Porzucenie przedmiotu wygląda bardzo podobnie. Jeżeli wciśnięte jest ‚x’ i któryś z przedmiotów ma wartość pola isCarried równą true. Zmieniam tę wartość oraz wartość pola carrying gracza na false. Jak wcześniej, wyświetlany jest też odpowiedni komunikat.

Uff… Jakoś dotarłem do końca. Mam nadzieję, że wszystko jest w miarę zrozumiałe.Jeżeli masz jakieś pytania, daj znać w komentarzu. Postaram się wszystko wyjaśnić. Kolejna aktualizacja będzie już ostania i powinna pojawić się niedługo. Postaram się aby były to okolice weekendu. Więcej czasu już nie mam bo to prawie koniec miesiąca 🙂 . Jeżeli chcesz być na bieżąco z aktualizacjami tej gry, zachęcam do polubienia mojej strony na facebooku. Regularnie zamieszczam tam informacje o wszystkich nowościach na blogu.

Dodaj komentarz

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