* Nawigacyjne wyszukiwanie ścieżki siatki

15

Więc stworzyłemodgórną grę 2D Java w tej strukturze o nazwie Greenfoot i pracowałem nad sztuczną inteligencją dla facetów, z którymi będziesz walczył. Chcę, aby mogły one realistycznie przemieszczać się po świecie, więc wkrótce zdałem sobie sprawę, że między innymi potrzebuję jakiegoś rodzaju znalezienia ścieżki.

Zrobiłem dwa prototypy A *. Jeden jest oparty na siatce, a potem stworzyłem taki, który działa z punktami trasy, więc teraz muszę znaleźć sposób, aby przejść z „mapy” przeszkód / budynków na 2D do wykresu węzłów, z którego mogę zrobić ścieżkę. Rzeczywiste wyszukiwanie ścieżek wydaje się być w porządku, tylko moje otwarte i zamknięte listy mogłyby użyć bardziej wydajnej struktury danych, ale przejdę do tego, kiedy i kiedy będę tego potrzebować.

Zamierzam użyć siatki nawigacyjnej ze wszystkich powodów wymienionych w tym poście na ai-blog.net . Jednak problem, z którym się spotkałem, polega na tym, że to, co A * uważa za najkrótszą ścieżkę od centrów / krawędzi wielokąta, niekoniecznie jest najkrótszą ścieżką, jeśli podróżujesz przez dowolną część węzła. Aby uzyskać lepszy pomysł, możesz zobaczyć pytanie, które zadałem na stosie .

Mam dobrą odpowiedź dotyczącą wykresu widoczności. Od tego czasu kupiłem książkę ( Geometria obliczeniowa: algorytmy i aplikacje ) i czytałem dalej w tym temacie, jednak nadal opowiadam się za siatką nawigacyjną (patrz „ Zarządzanie złożonościąw notatkach Amita na temat znajdowania ścieżek ). (Na marginesie, być może mógłbym użyć Theta * do przekształcenia wielu punktów na jedną linię prostą, jeśli pierwszy i ostatni nie są zasłonięte. Lub za każdym razem, gdy cofam się, sprawdzaj przedtem punkt na drodze, aby sprawdzić, czy mogę przejść prosto z to do tego)

Tak więc w zasadzie to, czego chcę, to siatka nawigacyjna, w której po przejściu przez algorytm lejkowy (np. Ten z Digesting Duck ) uzyskam prawdziwą najkrótszą ścieżkę, zamiast tej, która jest najkrótszą ścieżką prowadzącą tylko od węzła do węzła, ale nie najkrótsza, biorąc pod uwagę, że można przejść przez niektóre wielokąty i pominąć węzły / krawędzie.

Aha i ja też chcę wiedzieć, jak sugerujesz przechowywanie informacji dotyczących wielokątów. Dla przykładu prototypu waypointa, który stworzyłem, miałem po prostu każdy węzeł jako obiekt i zapisałem listę wszystkich innych węzłów, do których możesz podróżować z tego węzła. Zgaduję, że to nie zadziała z wielokątami? i jak powiedzieć, czy wielokąt jest otwarty / można go przechodzić, czy też jest to obiekt stały? Jak przechowywać, które węzły tworzą wielokąt?

Wreszcie, dla przypomnienia: chcę sam to zaprogramować od zera, mimo że są już dostępne inne rozwiązania i nie zamierzam (ponownie) używać tego kodu w niczym innym niż ta gra, więc to nie ma znaczenia nieuchronnie będzie niskiej jakości.

angrydust
źródło
Nie jestem pewien, ale te linki mogą pomóc: gamedev.stackexchange.com/questions/1327/… gamedev.stackexchange.com/questions/8087/... także było inne pytanie o znalezienie ścieżki, którego nie mogę teraz znaleźć , który dostał nagrodę i miał bardzo dobrą odpowiedź.
Ali1S232,
Tak, w drugim łączu widać moją podstawową troskę, algorytm A * dałby ścieżkę dookoła dna jako najkrótszą z wykorzystaniem punktów środkowych krawędzi, ale ścieżka wokół szczytu przeszkody jest w rzeczywistości najkrótsza. Chcę wiedzieć, w jaki sposób mogę uzyskać A *, aby podał mi ścieżkę wokół szczytu, którą następnie wyprostuję (na przykład algorytmem lejka), aby uzyskać prawdziwie najkrótszą ścieżkę, gdzie tak, jakby to dało mi tę wokół dolnej nawet jeśli to wyprostuję, to nadal objazd.
angrydust
Właśnie przeczytałem artykuł w gamedev.stackexchange.com/questions/8087/... i wydaje się, że działa, znajdując trasę z A *, a następnie obliczając jej rzeczywisty koszt za pomocą zmodyfikowanego algorytmu ścieżki, a następnie znajdując inną trasę i obliczając ją prawdziwy koszt ponownie i sprawdzenie, czy jest on krótszy od drugiego. Powtarza się, dopóki nie dowie się, że znalazła najkrótszą trasę. To rzeczywiście rozwiązuje mój problem, jednak wydaje się, że będzie to dość powolne, ponieważ powtarzasz zarówno prostowanie, jak i wyszukiwanie ścieżki, co byłoby dość kosztowne.
angrydust
Przechowywanie wielokątów: przechowuj tylko widoczne wielokąty - lub przypisz znacznik do każdego wielokąta (pamiętaj, że każdy wielokąt musi być okrągłą listą wierzchołków); podobnie z węzłami możesz przechowywać identyfikator wielokąta, z którego pochodzą - ale nie powinienem ci tego mówić: to elementarne przechowywanie danych. Wreszcie, dlaczego zależy ci na najkrótszej drodze? Twoja gra może biegać powoli lub możesz mieć nieco niepoprawne ścieżki: wybierz jedną. Uzyskanie prawdziwej najkrótszej ścieżki WYMAGA pełnego przeszukiwania w pierwszej kolejności (przynajmniej na wykresie węzłów).
Jonathan Dickinson
@JonathanDickinson wiem, że nie trzeba ścieżkę, która będzie w 100% z dokładnością do ostatniego piksela, i wiem, że mogę korzystać z A * do produkcji ścieżek, które będą co najwyżej p dopuszczalny. Rzecz tak wyraźnie omija przeszkodę, jak w moim poprzednim pytaniu dotyczącym przepełnienia stosu lub w gamedev.stackexchange.com/questions/8087/... to po prostu za dużo. Nie mogę pozwolić mojej sztucznej inteligencji ominąć przeszkodę!
angrydust

Odpowiedzi:

14

Radzę, że nawet jeśli masz zamiar napisać cały swój kod, pobierz aplikację Przekształć i skompiluj przykładową aplikację, ponieważ ma ona wizualizacje pokazujące wygenerowany navmesh i pozwala przetestować wyszukiwanie ścieżki za pomocą jednego punktu i kliknięcia berło. Możesz się wiele nauczyć, grając z tym.

Jak już zauważyliście, istnieją dwa kroki do stworzenia dobrze wyglądającej ścieżki - znajdowanie ścieżki A *, a następnie kolejne przetwarzanie, które obejmuje prostowanie ścieżki (eliminacja wszelkich zakrętów, które nie są konieczne do uniknięcia przeszkód), a być może także dodawanie krzywych w punktach zwrotnych.

Znalezienie drogi

Masz opanowaną część A *, co jest świetne. Zauważyłeś również, że A * nie zawsze znajdzie najprostszą ścieżkę. Ważne jest, aby zrozumieć, że dzieje się tak, ponieważ A * to algorytm znajdujący najkrótszą ścieżkę przez wykres (ponieważ jest to koncepcja matematyczna, w której węzły są bezwymiarowe), kiedy zastosujesz go do siatki, musisz jakoś odwzorować węzły na elementy siatki.

Najbardziej oczywistą rzeczą jest nawigacja od punktu środkowego wielokąta do punktu środkowego i oparcie kosztów na tej odległości, ale ma to pewne problemy. Po pierwsze, nie zawsze znajdzie najkrótszą geometrycznie ścieżkę, a po drugie, jeśli spróbujesz podążać ścieżką, którą tam obliczyłeś, zauważysz, że linia prosta od jednego środka do drugiego może przecinać wielokąt, który nie jest to część ścieżki (i może w ogóle nie być możliwa do nawigacji). Nie jest to okropny sposób, aby kosztować przejście wykresu podczas wykonywania A *, ale najwyraźniej nie jest odpowiedni do żadnego celu przejścia.

Kolejnym najprostszym rozwiązaniem jest wykonanie A * od krawędzi do krawędzi przez siatkę. Łatwiej jest pomyśleć, jeśli wyobrażasz sobie, że zamiast jednego węzła na wielokąt masz jeden na krawędź na wielokąt. Twoja ścieżka wiedzie od punktu początkowego do najbliższej krawędzi, przecina się do krawędzi sąsiadującego wielokąta, a następnie do następnej krawędzi w tym samym wielokącie i tak dalej. Powoduje to częstsze tworzenie najkrótszej ścieżki, a ponadto ma tę zaletę, że można ją pokonywać, jeśli nie chcesz wykonywać kroku prostowania ścieżki.

Prostowanie ścieżki

Algorytm użyty w Objeździe (biblioteka nawigacyjna dołączona do Recast) jest dość prosta. Powinieneś zauważyć, że to tylko wyprostuje ścieżkę w granicach wielokątów znalezionych podczas wyszukiwania A *. Jako taki, jeśli to nie znajdzie ciasnej ścieżki wokół przeszkody, nie uzyskasz również ciasnej ścieżki po uruchomieniu tego algorytmu. W praktyce siatki nacięcia wytworzone przez przekształcenie mają zwykle jeden wielokąt, przez który można przejść podczas nawigowania punktu dławika (najbliższego punktu między dwiema przeszkodami), dlatego A * zawsze tworzy listę węzłów, które są tak blisko przeszkody, jak możliwy. Jeśli używasz kafelków jako navmesh, tak nie będzie i ten bardzo prosty algorytm wstawi fałszywe zakręty.

Algorytm prostowania ścieżki objazdu nie jest dość złożony (O), ponieważ gdy stwierdza, że ​​musi wstawić zakręt, wstawia go w punkcie, w którym ostatnio dokręcił lejek w lewo (podczas skręcania w lewo i odwrotnie) i następnie ponownie rozpoczyna śledzenie węzłów od tego miejsca.

Jeśli chcesz wyprostować ścieżkę poza wielokątami, które tworzą część trasy A *, sprawy stają się znacznie bardziej złożone. Musisz wdrożyć procedurę rzutowania promieni, która może sprawdzić, czy dwa punkty w twoim navmesh mogą się widzieć (powinieneś to mieć i tak, abyś mógł sprawdzić, czy w ogóle potrzebujesz A *). Robisz to, przecinając segment linii utworzony przez początek-> cel z łączącymi się krawędziami wielokąta zawierającego początek, a następnie testując łączące się krawędzie wielokąta, który cię przenosi i tak dalej. Jeśli przecinasz niepołączoną krawędź (nazywam je krawędziami granicznymi), to trafiłeś w przeszkodę.

Możesz następnie wykonać ten test rzutowy, ilekroć algorytm lejkowy ustali, że musi wstawić zwrot, aby zobaczyć, czy rzeczywiście działa, ale myślę, że będziesz musiał wykonywać ten test w każdym węźle, dopóki nie wstawisz zwrotu (w który punkt możesz przywrócić do algorytmu prostej ścieżki). To będzie drogie, powodując, że ścieżka wyprostuje się w przybliżeniu O (n ^ 2).

Reprezentujący navmesh

Możesz przedstawić siatkę jako tablicę klas wielokątów. Klasa wielokątów może być tak prosta, jak tablica wierzchołków i tablica odniesień do sąsiedniego wielokąta dla każdej krawędzi, jeśli taka istnieje. Oczywiście prawdopodobnie możesz wymyślić sposoby przechowywania tego w bardziej kompaktowy sposób. Ponieważ wierzchołek jest zwykle współużytkowany przez kilka wielokątów, normalne jest posiadanie jednej dużej tablicy wierzchołków, a następnie umieszczenie w tej tablicy indeksów każdego wielokąta. W zależności od właściwości navmesh możesz mieć średnią liczbę krawędzi łączących, która wynosi tylko 50% lub mniej liczby krawędzi. W takim przypadku możesz zapisać link do innego wielokąta i indeks krawędzi zamiast przechowywać link dla każdej krawędzi. Polecam również przechowywanie indeksu wielokąta w tablicy wielokątów navmesh zamiast używania odwołania do klasy.

U62
źródło
Właśnie przeczytałem o tym krótko, ale rozumiem, że nigdy nie powinieneś używać kwadratu odległości (ani nie pierwiastka kwadratowego) dla A *: teorii.stanford.edu/~amitp/GameProgramming/…
angrydust
Naprawdę nie martwię się, jak właściwie przejść do bankomatu prostowania ścieżki, martwi mnie to, co mówisz: „Powinieneś zauważyć, że to tylko wyprostuje ścieżkę w granicach wielokątów znalezionych podczas wyszukiwania A * Jako taki, jeśli to nie znajdzie ciasnej ścieżki wokół przeszkody, nie uzyskasz również ciasnej ścieżki po uruchomieniu tego algorytmu. ”
angrydust
Chcę mieć siatkę nawigacyjną, w której A * zawsze znajdzie ścieżkę, która raz wyprostowana jest najkrótsza, niezależnie od kosztów podróży przez wierzchołki / punkty środkowe. Rozumiem, że można to zrobić za pomocą wykresów widoczności, ale chcę użyć navmesh, ponieważ ma on wiele innych zalet i ponieważ złożoność wykresu widoczności może bardzo szybko wzrosnąć
angrydust
@theguywholikeslinux możesz użyć odległości euklidesowej sqrt(x*x + y*y)- ale nie tańszej na węzeł x*x + y*y.
Jonathan Dickinson
@JonathanDickinson Wiem, stąd mój punkt widzenia.
angrydust