W dzisiejszym poście opiszę kolejną technikę przydatną podczas tworzenia gier 2d. Chodzi o blokowanie ruchu jednych obiektów innymi obiektami.
Jest to tak naprawdę wariacja kolizji z paroma dodatkowymi haczykami. Tym razem jako przykładu, znów użyję mojego psa 🙂 .
Po kliknięciu w obrazek otworzy się strona z przykładową sceną z gry. Zbyt wiele jednak się tam nie dzieje. Za pomocą strzałek można poruszać psem po ekranie. Jednak jeśli wejdzie na hydrant, zatrzymuje się. Hydrant blokuje ruch, niezależnie od której strony się do niego podejdzie.
obiekty blokujące ruch – metodablockRectangle
Całość kodu, można obejrzeć, podpatrując źródło strony. Nie mam tam nic nadzwyczajnego. To co dziś jest ważne to funkcja blockRectangle. Jest ona wywoływana w każdej iteracji pętli gry w metodzie update, dokładnie tak jak metody wykrywające kolizje. Bo tak naprawdę, jak napisałem wcześniej, i tutaj mam do czynienia z wykrywaniem kolizji. Treść blockRectangle wygląda tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
this.blockRectangle = function(r1, r2) { var vx = r1.centerX() - r2.centerX(); var vy = r1.centerY() - r2.centerY(); var combinedHalfWidths = r1.width/2 + r2.width/2; var combinedHalfHeights = r1.height/2 + r2.height/2; if(Math.abs(vx) < combinedHalfWidths){ if(Math.abs(vy) < combinedHalfHeights){ var overlapX = combinedHalfWidths - Math.abs(vx); var overlapY = combinedHalfHeights - Math.abs(vy); if(overlapX >= overlapY) { if(vy > 0) { r1.y = r1.y + overlapY; } else { r1.y = r1.y - overlapY; } } else { if(vx > 0) { r1.x = r1.x + overlapX; } else { r1.x = r1.x - overlapX; } } } } } |
W tej funkcji jest trochę główkowania, ale nie ma się czego bać. Żadne bardzo abstrakcyjne idee się nie pojawiają. Część algorytmu jest bardzo podobna do wykrywania kolizji w okrągłych obiektach.
Funkcja przyjmuje dwa argumenty, są to dwa obiekty, które nie mogę na siebie wejść. Pierwszy obiekt r1 to obiekt, który się porusza. r2 może być (i bardzo często będzie) nieruchomy.
Pierwsze dwie linijki to ustalenie długości odcinków vx oraz vy. Określają one poziomą i pionową odległość pomiędzy środkami obiektów. Wygląda to tak:
Długość tych odcinków uzyskuję dzięki metodom centerX oraz centerY obietków. Zwracają one współrzędne środka prostokąta opisywanego przez obiekt. Znajduję różnice pomiędzy wartościami centerX oraz CenterY obu obiektów. Tak wygląda schemat:
Kolejne dwa kroki, to pobranie wartości dla zmiennych combinedHalfWidths oraz combinedHalfHeights, czyli sumy długości połowy wysokości obiektów i połowy szerokości obiektów.
W tym miejscu muszę powiedzieć o metodzie wbudowanego obiektu Math – abs. Nie chodzi o tu o układ w motoryzacji a o matematyczną wartość bezwzględną (od angielskiego absolute). Metoda abs zwraca wartość bezwzględną podanej liczby, czyli liczbę dodatnią jeśli jest ujemna i liczbę dodatnią jeśli liczba jest dodatnia (taką samą liczbę).
Metoda abs potrzebna mi jest, aby sprawdzić, czy prostokąty nachodzą na siebie. Jeżeli na przykład pierwszy prostokąt zbliża się do drugiego od lewej strony, vx będzie ujemne. Dla mnie liczy długość odcinka, więc znak nie ma znaczenia. Jak zaraz się okaże, minus by tylko przeszkadzał. Metoda abs idealnie tu pasuje.
Ale co dokładnie się dzieje? Mam tu dwa wyrażenia warunkowe. W pierwszym sprawdzam czy odcinek vx (odległość w poziomie pomiędzy środkami obiektów), nie jest krótszy od combinedHalfWidths (długość połączonych połówek szerokości obiektów). Jeżeli tak jest oznacza to, że obiekty mogą nachodzić na siebie na osi X. Wiadomo, nie oznacza to jeszcze kolizji. Dlatego w drugim wyrażeniu warunkowym sprawdzam czy długość odcinka vy nie jest mniejsza od combinedHalfHeights. Oba spełnione warunki, oznaczają, że miedzy obiektami zachodzi kolizja czyli mówiąc dosłownie, nachodzą na siebie:
poziomy czerwony odcinek jest na pewno krótszy od połączonych zielonych, a pionowy odcinek czerwony, na pewno jest krótszy od połączonych odcinków niebieskich. Tak naprawdę jest to technika bardzo podobna wykrywania kolizji w przypadku kół. Tym razem jednak biorę pod uwagę różna szerokość i wysokość figury.
Do tej pory wszystko powinno być w miarę jasne. Udało mi się wykryć kolizję pomiędzy obiektami, nadszedł czas na zasymulowanie, efektu blokady pierwszego obiektu przed samym drugim obiektem. Jak chcę to osiągnąć? Sprawdzę z której strony nastąpiła kolizja, następnie policzę o ile obiekty się pokryły i o tę wartość cofnę pierwszy obiekt w odpowiednim kierunku.
Brzmi jak sporo obliczeń, ale prawda jest taka, że mam już prawie wszystko co potrzebuje aby policzyć te wartości. Jedyne czego potrzebuje, to dowiedzieć się o ile nachodzą na siebie obiekty na osi X i o ile na osi Y. Mogę to osiągnąć w bardzo prosty sposób, po prostu od długości połączonych połówek, odejmuje odpowiedni odcinek. Od combinedHalfWidths odejmuję vx a od combinedHalfHeights odejmuję vy. Wynik tego odejmowania to wartość, która jest równa pokryciu obiektów na osiach X i Y. Zapisuje te dane w zmiennych overlapX oraz overlapY
Kolejny krok, to sprawdzenie czy mniejsze jest overlapX czy overlapY. To, które jest mniejsze, wskazuje oś, na której obiekty się naszły. Teraz muszę jeszcze ustalić na od której strony. Mogę to łatwo wydedukować z wartość vx oraz vy.
Jeżeli overlapX jest wieksze od overlapY a vy jest dodatnie oznacza że obiekt pierwszy naszedł na drugi od dołu. Wygląda to tak jak na obrazku (pierwszym obiektem jest w tej sytuacji duży prostokąt):
Teraz wystarczy, że zmienię y obiektu pierwszego tak aby znajdował sie bezpośrednio pod obiektem drugim. Aby to zrobić, muszę do y dodać wartość overlapY, czyli tyle o ile obiekty się pokrywają. Gdyby vy było ujemne, oznaczałoby to, że obiekt pierwszy nachodzi na drugi z góry, wtedy od jego yodjąłbym overlapY.
Analogicznie postępuję w sytuacji, w której obiekt nachodzi na drugi na osi X (overlapX jest mniejsze). Używam po prostu odpowiednich wartości.
Mam nadzieję, że udało mi się wytłumaczyć w miarę jasno działanie tego algorytmu. Jest to już trochę bardziej zaawansowana technika. Ale jeśli zrozumiało się już podstawowe zagadnienia, które opisywałem wcześniej, zrozumienie tego nie powinno być trudno. Czasem po prostu trzeba poświęcić trochę czasu na zrozumienie tego co tu się dzieje.
A warto jest zrozumieć co się dzieje, ponieważ ta technika daje programiście sporo możliwości. Jest to pierwszy krok w kierunku tworzenia gier platformowych 🙂
Jeżeli masz jakieś pytania, coś jest nie jasne, lub zauważyłeś błąd w moim kodzie, daj znać w komentarzach. Na każdy komentarz na pewno odpowiem. Zachęcam też do polubienia mojej strony na facebooku. Zawsze umieszczam tam informacje o wszystkich nowościach. Polubienie jej gwarantuje, że zawsze będziesz na bieżąco z materiałami pojawiającymi się na blogu.
Nooo, chyba rozwiąże mój problem z kolizją ze ścianą i ślizganie się. To podejście ma jakąś nazwę? Ulepszone AABB?