(WIP) Wiedźma – gra co miesiąc: styczeń – update 2

Nic nie daje takiego natchnienia do pracy jak zbliżający się termin 🙂 Do końca miesiąca został tylko tydzień a styczbiowa gra nie jest jeszcze gotowa. Na szczęście nie zostało zbyt wiele pracy. W tej aktualizacji dodałem sporo nowości.

W lesie pojawiają się teraz potwory, które przeszkadzają wiedźmie w zbieraniu ziół. Drugą rzeczą mając na celu utrudnić czarownicy życie, są drzewa i kamienie, które blokują jej lot i muszą być omijane

javascript noob gra co miesiąc styczeń

W aktualną wersję gry pograć można klikając w obrazek powyżej. Jak zwykle jest też paczka z kodem i grafiką gry.

Tym razem naprawdę nie będę przedstawiał każdej nowej linijki kodu. Było by trudno, bo łącznie gra ma już ich prawie 900 a tej aktualizacji doszło ich około 300 🙂

Pierwszą duża zmianą jest pojawienie się w grze potworów. Podczas inicjalizacji głównego stanu gry, tworzę nowy obiekt creep. Będzie on wzorem, na podstawie którego powstawać będą potwory:

Zanim do gry dodany zostanie nowy potwór, w miejscu, którym ma się pojawić, wyświetla się animacja bąbelków. Animacja ta ma 11 klatek i trwa około 40 wywołań requestAnimationFrame. W trakcie pojawiania się wartość pola spawning potwora równe jest true. Za ten mechanizm odpowiada pierwsze parę pola w klasie creep.

Kolejne pola definiują czy potwór strzela i jakiego jest typu (czaszka, duch, nietoperz, oko). Są też pola obsługujące wyświetlanie animacji potwora. Definujące jego prędkość oraz fakt jak często może strzelać. Ważnym jest jeszcze pole shotDown, oznaczające czy potwór został zestrzelony. Potrzebuję tego dlatego, że potwór nie znika od razu po zestrzeleniu, w miejscu gdzie był znów pojawia się animacja bąbelków.

W funkcji update, doszła się spora ilość kodu obsługująca zachowanie potworów.

Najpierw sprawdzam czy w tablicy creeps, czyli tablicy przechowującej wszystkie potwory, które są aktualnie w grze, jest mniej niż 15 elementów. Jeśli tak i minął czas określający z jaką częstotliwością rodzą się potwory. Wywoływana jest metoda spawnCreep. Jest też resetowany licznik, odliczający czas do kolejnego potwora.

Od tego miejsca zaczyna się pętla for, która wykonuje całą resztę kodu dla każdego elementu w tablicy creeps

Kolejne parę linijek to oczywiście kod obsługujący animacje. Na początku, każdy potwór wyświetla animacje bąbelków. Kolejne wyrażenie if sprawdza czy potwór nie wyleciał poza granice ekranu plus sto pikseli. Jeżeli tak, to jest usuwany z tablicy creeps.

Dalej znajduje się kod, który sprawdza czy potwór wciąż się rodzi. Jeżeli czas potrzebny aby potwór się narodził minie, jego pole spawning ustawiane jest na false i wywoływana jest metoda chooseCreep.

Kolejne wyrażenie warunkowe, sprawdza czy aktualny potwór przestał już się pojawiać. Jeżeli tak, wywoływana jest jego metoda update, jest ona inna dla każdego typu potwora.

Następnie sprawdzane jest czy potwór strzela i czy minął wymagany czas od ostatniego strzału, jeżeli te warunki są spełnione, wywoływana jest metoda castEvilMissile.

Ostatnie dwa warunki obsługują sytuacje, w której potwór został zestrzelony. Jeżeli pole shotDown potwora równe jest true, ale jego typ, nie jest równy dead. Potwór przechodzi w stan wybuchu. Jego type otrzymuje wartość dead, a jego obrazek ustawiany jest na 6 klatkę bąbelków. Jeżeli potwór ma już typ równy dead i jest aktualnie na dziesiątej klatce, oznacza to, że minęła animacja pękającej bańki i potwór usuwany jest z tablicy creeps.

Jeśli chodzi o metody spawnCreep oraz chooseCreep, ich kod wygląda następująco:

spawnCreep, jest proste. Tworzony jest nowy obiekt potworka na podstawie obiektu creep. Następnie, losowo wybierane są współrzędne dla niego. Przygotowany potwór jest wrzucany do tablicy creeps. Należy pamiętać, że na początku potworek to tylko bąbelki, dopiero kiedy skończy się rodzić wywoływana jest funkcja chooseCreep.

W chooseCreep losowo wybierany jest jeden z typów potwora. Za pomocą konstrukcji switch, obiekt otrzymuje odpowiednie dane co do nowego obrazka, liczby klatek, czasu wyświetlania, typu potworka oraz czy jest on strzelający czy nie. Do tego, każdy typ ma swoją funkcję update. Ta funkcja inna dla każdego rodzaju. Jej celem jest, znaleźć położenie potwora w następnej klatce. Dlatego niektóre potwory poruszają się w poziomie, inne w pionie a jeszcze inne po łukach. Jeden potworek nie porusza się wcale :).

Ostatnia rzecz tycząca się bezpośrednio potworków, to wypuszczane przez nie pociski. Są one tworzone na podstawie klasy evilMissile, która z kolei tworzona jest w metodzie init na podstawie magicMissile:

Instancje tego obiektu tworzone są w metodzie castEvilMissile, która wywoływana jest w metodzie update, gdy tylko u któregoś licznik mierzący okres czasu między strzałami się wyzeruje

Używam wbudowanej funkcji trygonometrycznej atan2, aby ustalić kąt pomiędzy potworem a wiedźmą. Dzięki tej informacji będę mógł spowodować, że pocisk będzie leciał dokładnie w miejsce w którym znajduje się wiedźma podczas jego wystrzału. Obliczam to w metodzie update

Najpierw oczywiście aktualizowane są klatki animacji pocisku. Następnie używany jest kąt w funkcjach trygonometrycznych sin oraz cos. Dzięki nim, mogę wyliczyć położenie pocisku poruszającego się pod odpowiednim kątem. Nie będę teraz opisywał dokładnie jak to działa, poświęcę temu osobny post w niedalekiej przyszłości 🙂 . Przy okazji sprawdzam, też czy pocisk potwora nie wyleciał daleko poza ekran, jeżeli tak, usuwam go z tablicy.

Teraz pozostało już tylko wykrycie kolizji, pomiędzy czarownicą a stworami. Oczywiście dzieje się to w metodzie update:

W pierwszej pętli sprawdzam czy czarownica styka się z potworem, który nie rodzi się (tylko bąbleki) lub nie został już zestrzelony (już tylko bąbelki). Jeżeli tak, potworek zostaje oznaczony jako trafiony (shotDown). Do tego jeśli czarownica nie ma pola recovering równego true, traci 10 z wartości pola lifePoints (początkowo równe 100) i recovering ustawiane jest na true. Oznacza to, że po zderzeniu z potworem, czarownica traci punkty życia a potwór ginie.

To samo tyczy się pocisków potworów, jeśli nastąpi kolizja pomiędzy nimi a czarownicą, traci ona punkty życia jej pole recovering ustawiane jest na true, a pocisk jest usuwany z gry.

O co chodzi z recovering? Jest to mechanizm, który sprawia że przez pewien czas po otrzymaniu obrażeń, czarownica nie może otrzymać kolejnych. Obsługiwane jest to w metodzie update na podstawie trzech pól obiektu witch:

Jest tu oczywiście licznik, oraz czas jaki trwać będzie niewrażliwość czarownicy. Fragment metody update który to obsługuje wygląda następująco:

Oprócz odliczania czasu, co 10 obiegów sprawiam, że zamiast na obrazek wiedzmy pole sourceY obiektu wskazuje na puste miejsce na arkuszu obrazków. Dzięki temu, po otrzymaniu obrażeń, czarownica miga.

Oczywiście w metodzie update nie zabrakło też wykrycia kolizji pomiędzy potworami a pociskami czarownicy:

Każdy pocisk sprawdzany jest z każdym potworem. Jeżeli występuje kolizja, a potwór nie jest w trakcie rodzenia się. Potwór oznaczany jest jako ustrzelony, a pocisk usuwany jest z gry.

W ten bardzo ogólny sposób udało mi się opisać dodanie potworów czyli główną zmianę w grze i wszystkie jej następstwa 🙂

Kolejna trochę mniejsza zmiana, zamiana drzew i kamień na mapie w obiekty, które zatrzymują czarownicę. Nie są one już częścią obrazka tła jak wcześniej a są pełnoprawnymi obiektami dodawanymi w trakcie inicjacji gry. Tak wygląda ich kod:

Jak widać, są to zwykłe obiekty ze zdefiniowanymi współrzędnymi i rozmiarami. W tym miejscu warto zwrócić uwagę, że rozmiar ich obrazków jest większy od faktycznego rozmiaru. To dlatego, że chcę aby czarownica mogła nalecieć na część obrazka drzewa (wlecieć w liście), ale zatrzymać się na jego środku (na pniu 🙂 ). Te obiekty definiowane są w metodzie init. W tej samej metodzie, zaraz po definicji obiektów, wywoływana jest metoda populateObstacles.

W głównym obiekcie stanu zdefiniowałem tablicę, zawierającą współrzędne X każdego z obiektów. Metoda powyżej przechodzi przez tę tablicę i dla każdego elementu tworzy obiekt obstacle. Losowo wybierane jest czy będzie do dąb, sosna czy kamień. Losowo wybierana jest też jego współrzędna Y, chociaż tak, żeby położenie przeszkody miało sens. Dla drzew tworzę też pole drawX, wskazujące gdzie ma być narysowany obrazek. W ten sposób sam obiekt będzie umieszczony wewnątrz obrazka.

Teraz potrzebna jest tylko funkcja obsługująca kolizje pomiędzy czarownicą a przeszkodami. Tak wygląda fragment kodu metody update, który za to odpowiada:

Co każdy cykl gry, dla każdej przeszkody wywoływana jest metoda blockRectangle. Sprawdza ona kolizję pomiędzy daną przeszkodą a wiedźmą:

Nie będę dokładnie opisywał działania tej metody. Zasługuje na osobny post, ponieważ daje bardzo dużo ciekawych możliwości. Opowiem tylko ogólnie jak działa. Najpierw pomiędzy dwoma prostokątnymi obiektami wykrywana jest kolizja. Jeżeli zachodzi, metoda sprawdza z której strony obiekty się stykają. Następnie drugi obiekt, cofany jest w tę stronę o tyle pikseli o ile obiekty się pokrywają. W ten sposób pierwszy obiekt, blokuje drugi.

I to wszystkie nowości. Tym razem nie opisywałem każdej nowej zmiennej. Większość mechanizmów się powtarza. Jak zwykle jeśli coś nie jest jasne, pytaj w komentarzu. Z chęcią odpowiem na każde pytanie.

Myślę że gra zaczyna nabierać bardzo konkretnych kształtów. Kolejny krok to dodanie GUI oraz zmiana stanów pomiędzy główną grą tak aby były bardziej atrakcyjne. I to będzie już cała gra. Ostatniej aktualizacji można spodziewać się pod koniec tego tygodnia. Jeżeli chcesz być pewny, że jej nie przegapisz, zachęcam do polubienia mojej strony na facebooku. Zamieszczam tam wszystkie informacje o nowościach na bieżąco 🙂

Dodaj komentarz

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