Tworzę grę, która składa się z wielu obiektów na ekranie, z których jednym jest gracz. Muszę wiedzieć, które obiekty kolidują podczas każdej iteracji.
Zrobiłem coś takiego:
for (o in objects)
{
o.stuff();
for (other in objects)
if (collision(o, other))
doStuff();
bla.draw();
}
Ma O (n ^ 2), co, jak mi powiedziano, jest złe. Jak mogę to zrobić bardziej efektywnie, czy to w ogóle możliwe? Robię to w Javascripcie, a n zwykle będzie niższe niż 30, czy będzie problem, jeśli pozostanie taki sam?
Odpowiedzi:
Mając maksymalnie 30 obiektów, nie powinieneś potrzebować żadnej optymalizacji poza tym, aby nie sprawdzać tych samych dwóch par więcej niż jeden raz na klatkę. Obejmuje to poniższy przykład kodu. Ale jeśli interesują Cię różne optymalizacje, z których skorzystałby silnik fizyki, czytaj dalej resztę tego postu.
Potrzebna będzie implementacja partycjonowania przestrzennego , taka jak Octree (dla gier 3D) lub Quadtree (dla gier 2D). Dzielą świat na podsekcje, a następnie każda podsekcja jest dalej dzielona na ten sam dwór, dopóki nie zostaną podzielone na minimalny rozmiar. Pozwala to bardzo szybko sprawdzić, które inne obiekty znajdują się w tym samym regionie świata co inny, co ogranicza liczbę kolizji, z którymi musisz się zmierzyć.
Oprócz partycjonowania przestrzennego zaleciłbym utworzenie AABB ( obwiedni wyrównanej do osi ) dla każdego z twoich obiektów fizycznych. Umożliwia to sprawdzenie AABB jednego obiektu względem drugiego, co jest znacznie szybsze niż szczegółowe sprawdzenie poszczególnych obiektów między obiektami.
Można to zrobić o krok dalej w przypadku skomplikowanych lub dużych obiektów fizycznych, w których można podzielić siatkę fizyki, dając każdemu pod-kształtowi własny AABB, który można sprawdzić tylko wtedy, gdy AABB dwóch obiektów się nakładają.
Większość silników fizykalnych dezaktywuje aktywną symulację fizyki na ciałach fizycznych po ich zatrzymaniu. Kiedy ciało fizyki jest dezaktywowane, musi tylko sprawdzić kolizję z jego AABB w każdej ramce, a jeśli coś zderzy się z AABB, wówczas ponownie się aktywuje i przeprowadzi bardziej szczegółową kontrolę kolizji. To skraca czas symulacji.
Ponadto wiele silników fizyki korzysta z „wysp symulacyjnych”, w których grupa ciał fizycznych znajdujących się blisko siebie jest zgrupowana. Jeśli wszystko na wyspie symulacji jest w spoczynku, sama wyspa symulacji dezaktywuje się. Zaletą wyspy symulacyjnej jest to, że wszystkie znajdujące się w niej ciała mogą przestać sprawdzać kolizje, gdy wyspa jest nieaktywna, a jedynym sprawdzeniem w każdej ramce jest sprawdzenie, czy coś dostało się do AABB wyspy. Tylko wtedy, gdy coś wejdzie do AABB wyspy, każde ciało na wyspie będzie musiało sprawdzić, czy nie ma kolizji. Wyspa symulacyjna reaktywuje się również, jeśli którekolwiek z jej ciał zacznie się ponownie poruszać. Jeśli ciało porusza się wystarczająco daleko od centrum grupy, jest usuwane z wyspy.
Na koniec masz coś takiego (w pseudokodzie):
Poleciłbym również, aby nie mieć tak wielu pętli w takich pętlach, powyższa próbka była po prostu, abyś wpadł na pomysł, podzielę ją na wiele funkcji, które zapewniają taką samą funkcjonalność jak coś takiego, jak pokazano powyżej.
Pamiętaj również, aby nie zmieniać kontenera AABBNodes podczas jego przechodzenia, ponieważ może to oznaczać brak kontroli kolizji. Może to zabrzmieć jak zdrowy rozsądek, ale byłbyś zaskoczony, jak łatwo jest reagować na kolizje, powodując zmiany, których nie spodziewałbyś się. Na przykład jeśli kolizja spowodowała, że jeden z kolidujących obiektów zmienił pozycję na tyle, aby usunąć je z AABB sprawdzanego węzła Octree, może to zmienić ten kontener. Aby rozwiązać ten problem, zalecam prowadzenie listy wszystkich zdarzeń kolizji, które mają miejsce podczas kontroli, a następnie po zakończeniu wszystkich kontroli przejrzyj listę i wyślij wszystkie zdarzenia kolizji.
źródło
Twój przykład testuje każdą parę obiektów wiele razy.
Weźmy bardzo prosty przykład z tablicą zawierającą 0,1,2,3
Za pomocą kodu otrzymujesz:
Teraz zobaczmy następujący kod:
Jeśli ponownie użyjemy tablicy zawierającej 0,1,2,3, mamy następujące zachowanie:
W drugim algorytmie mamy 6 testów kolizji, podczas gdy poprzedni poprosił o 12 testów kolizji.
źródło
N(N-1)/2
porównań, które wciąż są wydajnością O (N ^ 2).Zaprojektuj algorytm zgodnie z potrzebami, ale zachowaj szczegółowość danych dotyczących implementacji. Nawet w języku Javascript obowiązują podstawowe koncepcje OOP.
Dla
N =~ 30, O(N*N)
nie jest problemem, a przeszukiwanie liniowe może być tak samo szybko, jak tam jakiejkolwiek alternatywy. Ale nie chcesz zapisywać w kodzie założeń. W pseudokodzie miałbyś interfejsTo opisuje, co może zrobić twoja lista przedmiotów. Następnie możesz napisać klasę ArrayContainer, która implementuje ten interfejs. W Javascript kod wygląda następująco:
A oto przykładowy kod, który tworzy 300 obwiedni i pobiera wszystkie skrzyżowania. Jeśli poprawnie zaimplementowałeś ArrayContainer i QuadTreeContainer, jedyną rzeczą, którą musisz zmienić w kodzie, jest zmiana
var allMyObjects = new ArrayContainer()
navar allMyObjects = QuadTreeContainer()
.Poszedłem dalej i ulepszyłem implementację standardowego ArrayContainer tutaj:
http://jsfiddle.net/SKkN5/1/
źródło
Należy również wziąć pod uwagę rodzaje obiektów, które mogą rozsądnie kolidować.
Na przykład gracz prawdopodobnie musi zostać sprawdzony pod kątem kolizji ze wszystkim oprócz własnych kul. Jednak wrogowie mogą wymagać tylko sprawdzenia przeciwko kulom gracza. Kule prawie na pewno nie muszą się ze sobą zderzać.
Aby skutecznie to zaimplementować, prawdopodobnie chcesz przechowywać osobne listy obiektów, po jednym dla każdego typu obiektu.
źródło