Pole widzenia wrogów

Użytkownicy Makera nierzadko próbują urozmaicić swój projekt różnymi bardziej zaawansowanymi mechanikami. Jedną z bardziej popularnych jest implementacja pola widzenia wrogów w różnych formach. Niestety, w praktyce najczęściej okazuje się to być zbyt frustrujące dla wielu graczy.

Ale w czym problem?

Systemy pola widzenia w makerówkach okazują się aż nazbyt prymitywne. Pół biedy, jeśli chodzi tylko o potworki na mapie widzące w 360 stopniach, największą zmorą są sekcje skradankowe. Posłużę się przykładem – znaną wielu scenówką Spamthirowy Czołg autorstwa Ska’cia. Część graczy utknęła na dłuższą chwilę podczas minigry z kradnięciem paliwa: o ile można przeboleć nieznany zasięg widzenia strażnika, tak rentgen w jego oczach przyprawia o ból głowy. Pola, które widzi żołnierz, są ustawione „na sztywno” bez sprawdzania, czy między graczem a NPC jest jakaś ściana.

Co robić?

O ile takie podejście może się sprawdzić, kiedy zasięg widzenia jest mały, tak przy większych rozmiarach staje się to karkołomne. Na pewno da się to zrobić jakoś łatwiej, prawda? Dajmy na to taką sytuację:

Pomyślmy — w jaki sposób sprawdzić, czy między eventem a graczem znajduje się przeszkoda? Najprościej byłoby poprowadzić promień (ang. raycast) od eventu do gracza i sprawdzić, czy na jej drodze nie stoi ściana. Tylko jak to zrobić? Iść piksel po pikselu? Można spróbować — jeśli gracz stoi na tych samych koordynatach X lub Y ekranu co event to sprawa jest prosta, ale gdy jest inaczej to będziemy potrzebować kąta pod jakim poprowadzony jest promień.

A skąd kąt? Z funkcji trygonometrycznych… i w tym momencie użytkownicy 2k/2k3 (ci bez Maniacs Patcha) mogliby obejść się ze smakiem. Ale to nie jedyna wada tego podejścia, aby móc dokładnie odwzorować pole widzenia i nie przecinać rogów, promień musi stawiać odpowiednio małe kroki, z kolei im mniejsze te kroki tym więcej operacji musimy wykonać w ciągu jednej klatki. W przypadku wielu przeciwników rysujących linie w ten sposób nawet nowsze makery mogłyby dostać zadyszki, w końcu nie jest to silnik nastawiony na największą wydajność. Gdy wszystko wydaje się stracone, z pomocą przychodzi…

Algorytm Bresenhama

Metoda starsza od prawdopodobnie kogokolwiek zainteresowanego RPG Makerem, ale wciąż niesamowicie przydatna. Jej siła leży w prostocie: wystarczy możliwość porównania, dodawania i odejmowania dwóch liczb, a na dodatek nie trzeba uciekać się do liczb zmiennoprzecinkowych, co jest koniecznością, jeśli używamy funkcji trygonometrycznych. Pozwoli to operować na zwykłych koordynatach X/Y kafelków mapy zamiast ekranu, co znacznie zmniejsza ilość operacji dokonywanych na jedną klatkę.

Wróćmy do przykładu. Chcemy poprowadzony promień przerobić na ciąg kafelków, przez które przechodzi. Łatwo zauważyć, że w tym w przypadku zawsze stawiamy krok w lewo i jedynie czasem dodatkowo w dół. Funkcja sprawdzania ID terenu pozwoli stwierdzić, czy na drodze stoi ściana. Prawie się udało, tylko jak ustalić, w którym momencie pójść w dół?

Pora na trochę matematyki. Skoro znamy współrzędne dwóch punktów (eventu i gracza), to łatwo możemy wyprowadzić wzór na prostą przechodzącą przez te punkty. Dla uproszczenia załóżmy, że współczynnik kierunkowy a znajduje się w przedziale <0 ; 1>.

f(x) = ax + b

Stawiamy jeden krok w poziomie. Wtedy otrzymujemy:

f(x + 1) = a(x + 1) + b = ax + b + a = f(x) + a

Co oznacza, że przy stawianiu jednego kroku w poziomie stawiamy a kroków w pionie, banalne. Na razie zaeventujmy to, co mamy. Stwórzmy nowe zdarzenie Strażnik i ustawmy je na Parallel Process. Przypomnijmy sobie, czym jest nasze a.

a = \frac{y_{2} - y_{1}}{x_{2} - x_{1}} = \frac{\Delta y}{\Delta x}

Zatem poustawiajmy kilka zmiennych i główną pętlę.

Niestety, już widać pierwszy problem – współczynnik a rzadko kiedy będzie wartością całkowitą i nie będziemy mogli sprawdzić pola o koordynatach np. X = 1.5, Y = 3. Wprowadźmy zatem zmienną decyzyjną Błąd. Skoro zawsze stawiamy krok w bok, zwiększamy nasz błąd o wielkość a, jeśli nie idziemy w pionie. Krok w górę/dół wykonujemy, dopiero kiedy błąd przekroczy 0.5 pola i w konsekwencji odejmujemy 1 od wartości błędu.

Ale, ale, Axer, przecież miało nie być liczb zmiennoprzecinkowych! I faktycznie ich nie będzie — wystarczy przemnożyć wartości związane z błędem przez 2, zatem nasz błąd będziem zwiększać o 2a, porównywać do 1 i ewentualnie odejmować 2. Zaktualizujmy nasz event.

,

Nadal pozostaje problem niecałkowitego a. Ale skoro wiemy, że a = \frac{\Delta y}{\Delta x}, to możemy przeprowadzić podobny zabieg co przed chwilą — przemnożyć wszystko związane z Błędem przez \Delta x. Wtedy otrzymujemy zwiększanie błędu o 2 \frac{\Delta y}{\Delta x} \Delta x = 2\Delta y, porównywanie do \Delta x i odejmowanie 2\Delta x. Można to zoptymalizować jeszcze bardziej poprzez przesunięcie startowego błędu oraz porównania o \Delta x, żeby zamienić przyrównanie do zmiennej na przyrównanie do stałej. W konsekwencji dostajemy odpowiednio startowy błąd 2\Delta y - \Delta x, zwiększanie o 2\Delta y, porównanie do 0 i odejmowanie 2\Delta x. Wyliczanie współczynnika a wyrzucamy, bo nie jest już nam potrzebne. Wnętrze eventu powinno wyglądać mniej więcej w ten sposób:

Uff, czy to już wszystko? Nie do końca — tak jak przyjęliśmy na początku, skrypt działa dopiero wtedy, kiedy 0 \leq a \leq 1. Przyjrzyjmy się temu bliżej…

a \leq 1

\frac{\Delta y}{\Delta x} \leq 1

\Delta y \leq \Delta x

Okej, ale co jeśli \Delta y jest większe od \Delta x? Nic prostszego, wtedy zmieniamy naszą oś wiodącą z OX na OY, tzn. najpierw robimy krok w pionie, a dopiero potem ewentualnie w bok. Z tego powodu zamieniamy też \Delta x z \Delta y. Po poprawkach skrypt jawi się następująco:

Po odpaleniu okazuje się, że nadal coś nie gra. Powodów jest kilka:

  • SprawdzaneX i SprawdzaneY są zawsze inkrementowane, nawet jeśli którakolwiek z delt jest ujemna
  • delta symbolizuje dystans, powinniśmy zatem używać wartości bezwzględnych, w końcu odległość nie może być ujemna!

Zatem tworzymy dwa dodatkowe warunki: Jeśli DeltaX < 0Jeśli DeltaY < 0, w których przemnażamy odpowiednią detlę przez -1, oraz dwie dodatkowe zmienne ZnakXZnakY. Ich wartości ustawiamy początkowo na 1, które zmieniamy na -1 odpowiednio we wspomnianych warunkach. Nie zapomnijmy ustawić inkrementowania SprawdzaneX i SprawdzaneY o te zmienne!

Zasięg widzenia

Skrypt już jest gotowy, ale przydałoby mu się jeszcze kilka szlifów. Jeśli chcemy, przykładowo, żeby nasz gracz był mniej zauważalny, możemy ograniczyć dystans, z którego strażnik może nas zauważyć. W tym przypadku zastosujemy znane wszystkim twierdzenie Pitagorasa. Jako że nie mamy dostępu do operacji pierwiastkowania, zasięg widzenia podajemy do kwadratu, np. jeśli chcemy, żeby dystans wynosił 5 kratek w linii prostej, przygotowujemy zmienną o wartości 25, jeśli 6 kratek, to 36, etc. Po modyfikacji początek skryptu powinien wyglądać mniej więcej tak:

Ograniczanie kąta widzenia

Jak na razie nasz strażnik ma oczy dookoła głowy. Jeśli nie odpowiada nam takie rozwiązanie, można ograniczyć kąt jego widzenia bardzo prostym sposobem. Wymagane jest jedynie zwykłe sprawdzanie znaku delty w zależności od kierunku, w który patrzy event. Przykładowo, jeśli wiemy, że strażnik patrzy w dół i znajdujemy się poniżej niego, to wystarczy, że przed uzyskaniem wartości bezwzględnej sprawdzimy czy \Delta y < 0. Jeśli tak — kończymy sprawdzanie. W ten prosty sposób zmniejszyliśmy kąt widzenia strażnika z 360 stopni do 180. Gdybyśmy chcieli go zawęzić do 90, dorzucamy kolejny warunek po uzyskaniu wartości bezwzględnej sprawdzający \Delta y < \Delta x i anulujemy sprawdzanie kiedy jest prawdziwy. Analogicznie postępujemy dla pozostałych kierunków.

Aby uzyskać inny kąt, trzeba poeksperymentować z wartościami bezwzględnymi delt. Poniżej jedno z przykładowych ustawień.

Voilà! Nasz strażnik jest gotowy do obserwowania i można go powielać metodą kopiuj-wklej w dowolnych ilościach. Poniżej udostępniam przykładowy projekt opatrzony komentarzami, na wypadek problemów w reprodukowaniu tutoriala.

Axer

– Download przykładowego projektu –

2 thoughts on “Pole widzenia wrogów

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.