Jak zapobiegać przycinaniu postaci mojej platformówki na kafelkach?

9

Obecnie mam platformówkę z kafelkami terenu (grafika zapożyczona z Cave Story). Gra została napisana od podstaw za pomocą XNA, więc nie używam istniejącego silnika ani silnika fizyki.

Zderzenia kafelków są opisane dokładnie tak, jak opisano w tej odpowiedzi (z prostym SAT dla prostokątów i kół) i wszystko działa dobrze.

Z wyjątkiem sytuacji, gdy gracz wpada na ścianę podczas upadku / skoku. W takim przypadku złapią się płytki i zaczną myśleć, że uderzyli o podłogę lub sufit, których tak naprawdę nie ma.

Obraz łapania postaci

Na tym zrzucie ekranu gracz porusza się w prawo i spada w dół. Więc po ruchu kolizje są sprawdzane - i po pierwsze, okazuje się, że postać gracza zderza się z płytką 3 od podłogi i pcha w górę. Po drugie, koliduje z kafelkiem obok niego i pcha się na boki - końcowym rezultatem jest to, że postać gracza myśli, że leży na ziemi i nie spada, i „łapie” kafelek tak długo, jak długo na niego wpadnie. .

Mógłbym to rozwiązać, definiując zamiast tego płytki od góry do dołu, co sprawia, że ​​spada on płynnie, ale wtedy zdarza się odwrotna sprawa i uderzy w sufit, którego nie ma, skacząc w górę o ścianę.

Jak podejść do rozwiązania tego problemu, aby postać gracza mogła po prostu spaść wzdłuż ściany, tak jak powinna?

doppelgreener
źródło
Podobne do tego pytania? gamedev.stackexchange.com/questions/14486/…
MichaelHouse
@ Byte56 Pytanie nie jest takie samo, ale jego odpowiedź może pomóc.
doppelgreener
Uwaga: w ciągu ostatnich kilku tygodni miałem bardzo mało czasu na eksperymentowanie z odpowiedziami tutaj. Wdrożyłem światowe rozwiązanie kolizji, na które odpowiedziano w pytaniu @ Byte56, które ma osobne błędy przy dużych prędkościach i jeszcze nie byłem w stanie wypróbować odpowiedzi na to pytanie. Spróbuję tych, kiedy będę w stanie, i albo zaakceptuję odpowiedź, kiedy będę mógł, albo opublikuję własną, jeśli rozwiążę ją samodzielnie.
doppelgreener

Odpowiedzi:

8

Najprostszym, bardziej odpornym na awarie podejście jest po prostu nie sprawdzanie kolizji na ukrytych krawędziach. Jeśli masz dwie płytki ścienne, jedną bezpośrednio nad drugą, to nie należy sprawdzać, czy dolna krawędź górnej płytki i górna krawędź dolnej płytki nie kolidują z graczem.

Możesz określić, które krawędzie są prawidłowe podczas prostego przejścia przez mapę kafelków, przechowując krawędzie zewnętrzne jako flagi w każdej lokalizacji kafelka. Możesz to również zrobić w czasie wykonywania, sprawdzając sąsiadujące płytki podczas wykrywania kolizji.

Istnieją bardziej zaawansowane wersje tej samej techniki, które ułatwiają i przyspieszają również obsługę płytek innych niż kwadratowe.

Zalecam przeczytanie poniższych artykułów. Są przyzwoitymi, prostymi wprowadzeniami do wykonywania podstawowej fizyki i wykrywania kolizji we współczesnych platformówkach (takich jak Cave Story). Jeśli szukasz bardziej oldschoolowego stylu Mario, musisz martwić się kilkoma sztuczkami, ale większość z nich polega na skakaniu i animacjach, a nie na kolizji.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html

Sean Middleditch
źródło
Czy możesz wyjaśnić, jak zignorować niektóre krawędzie podczas kolizji? Zazwyczaj widzę kolizje jako skrzyżowanie dwóch prostokątów (granice gracza i granice kafelków). Kiedy mówisz, aby nie sprawdzać kolizji z poszczególnymi krawędziami, czy oznacza to, że algorytm kolizji nie jest przecięciem prostokąta-prostokąta, ale czymś innym? Twój opis ma sens, ale nie jestem pewien, jak wyglądałoby wdrożenie.
David Gouveia,
Tak, po prostu zrób kolizję z prostokątem. Uproszczone, ponieważ linia jest zawsze wyrównana do osi. Możesz po prostu rozłożyć pole wyboru.
Sean Middleditch,
5

Tutaj rozkaz bym zrobił rzeczy ....

1) Zapisz bieżące X, Y znaku

2) Przesuń kierunek X.

3) Sprawdź cały narożnik znaku, pobierając dane kafelka i sprawdź, czy jest ono solidne, wykonując następujące czynności: (X i Y to pozycja znaku)

  • w lewym górnym rogu: podłoga (X / tileWidth), podłoga (Y / tileHeight);
  • w prawym górnym rogu: floor ((X + characterWidth) / tileWidth), floor (Y / tileHeight);
  • dla lewego dolnego rogu: floor (X + / tileWidth), floor ((Y + characterHeight) / tileHeight);
  • dla prawego dolnego rogu: floor ((X + characterWidth) / tileWidth), floor ((Y + characterHeight) / tileHeight);

... aby uzyskać prawidłowe dane kafelkowe z danych mapy

4) Jeśli którykolwiek z kafelków jest solidny, musisz zmusić X do najlepszej możliwej pozycji, przywracając zapisane X i ...

  • jeśli poruszasz się w lewo: X = floor (X / tileWidth) * tileWidth;
  • jeśli poruszasz się w prawo: X = ((floor ((X + characterWidth) / tileWidth) + 1) * tileWidth) - characterWidth;

5) Powtórz 2-4, używając Y

Jeśli twoja postać jest wyższa niż płytka, musisz sprawdzić więcej płytek. Aby to zrobić, musisz zapętlić i dodać tileHeight do pozycji postaci Y, aby uzyskać kafelek

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

Jeśli znak jest szerszy niż 1 blok, zrób to samo, ale zamień characterY, characterHeight, tileHeight itp. Na characterX, characterWidth itp.

Jan
źródło
2

Co się stanie, jeśli zaznaczysz możliwość kolizji w ramach metody ruchu gracza i odrzucisz ruch, który spowodował, że gracz wślizgnął się w inny obiekt? Uniemożliwiłoby to graczowi „wejście” do bloku, a zatem nie mógł znajdować się „na szczycie” bloku pod nim.

Jakiś facet
źródło
1

W grze, nad którą obecnie pracuję, napotkałem prawie ten sam problem. Sposób, w jaki to rozwiązałem, polegał na zapisaniu obszaru nakładającej się części podczas testowania kolizji, a następnie obsługiwaniu tych kolizji na podstawie ich priorytetu (najpierw największy obszar), a następnie podwójnym sprawdzaniu każdej kolizji, aby sprawdzić, czy została już rozwiązana wcześniej obsługa.

W twoim przykładzie obszar kolizji na osi X byłby większy niż oś Y, więc byłby obsłużony jako pierwszy. Następnie, zanim spróbuje poradzić sobie z kolizją na osi y, sprawdzi i zobaczy, że nie koliduje już po przesunięciu na osi x. :)

Nick Badal
źródło
1

Prostym, ale nie idealnym rozwiązaniem jest zmiana kształtu pola kolizji gracza, aby nie był kwadratem. Odetnij rogi, aby był podobny do ośmiokąta lub czegoś podobnego. W ten sposób, gdy zderzy się z płytką podobną do tej w ścianie, ześlizgnie się z niej i nie zostanie złapany. Nie jest to idealne, ponieważ nadal można zauważyć, że trochę łapie na zakrętach, ale nie utknie i jest bardzo łatwy do wdrożenia.

Amplify91
źródło
1

Bardzo podobny problem omówiono w tym samouczku: Wprowadzenie do tworzenia gier . Odpowiednia część filmu jest o 1:44 , wyjaśniając za pomocą diagramów i fragmentów kodu.

Uwaga 14-tilemap.py wykazuje problem z przycinaniem, ale został naprawiony w 15-blocker-sides.py

Nie potrafię dokładnie wyjaśnić, dlaczego ostatni przykład samouczka nie jest przycinany, gdy robi to Twój kod, ale myślę, że ma to coś wspólnego z:

  • Warunkowa reakcja na kolizję w zależności od rodzaju płytki (które krawędzie są faktycznie aktywne).
  • Gracz zawsze „spada”. Chociaż wydaje się, że gracz spoczywa na kafelku, faktycznie kilkakrotnie spada przez kafelek, a następnie zostaje zepchnięty z powrotem na szczyt. (Element self.restingw kodzie służy tylko do sprawdzenia, czy gracz może skoczyć. Mimo to nie mogę zmusić gracza do wykonania „podwójnego” skoku ze ściany. Teoretycznie może to być możliwe)
Leftium
źródło
0

Inną metodą (do której odpowiedziałem na pytanie powiązane z Byte56) byłoby sprawdzenie, czy rozwiązanie kolizji stawia znak w pustym miejscu, czy nie. Tak więc w twoim problemie dochodzi do kolizji z sufitu wewnętrznego prostokąta, aby przesunąć go w górę, co byłoby nielegalne (ponieważ nadal kolidujesz z inną płytką). Zamiast tego przenosisz go tylko wtedy, gdy zostaniesz przeniesiony na wolne miejsce (na przykład, jak kolizja z górnej płytki przesunęłaby cię w lewo), a kiedy znajdziesz tę kolizję, to koniec.

Odpowiedź, którą podałem, miała kod, ale stało się to zbyt skomplikowane. Śledzę wszystkie kolizje w tym czasie i biorę tylko te, które prowadzą do wolnego miejsca. Jednak gdyby ich nie było, myślę, że uciekłem się do zresetowania pozycji postaci do jej ostatniej pozycji, jednak jest to naprawdę brzydkie i może wolisz zaimplementować coś takiego jak oryginalny Mario, w którym porusza się tylko w jednym kierunku lub coś, gdy nie ma rozdzielczości wolnego miejsca jest możliwe. Możesz także posortować tę listę rozdzielczości kolizji i przejść do wolnej przestrzeni tylko w najkrótszej odległości (myślę, że to rozwiązanie byłoby najkorzystniejsze, chociaż nie kodowałem tego).

Jeff
źródło
0

Zrobiłem tylko 3 SAT w 3D, więc może nie rozumiem tego dobrze. Jeśli rozumiem twój problem, może to wynikać z nawiązania kontaktów z osią kontaktu, co nie powinno być możliwe, w wyniku czego gracz zachowuje się, jakby istniała podłoga. Jednym z obejść może być użycie zamiast tego kształtu koła dla twojego caracter, wtedy osie kontaktowe uzyskasz tylko w kierunku osi x po uderzeniu w ścianę.

Mikael Högström
źródło