Problemy z kolizją AABB 2D Platformer

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

Mam problem z rozdzielczością kolizji AABB.


Rozwiązuję przecięcie AABB, rozwiązując najpierw oś X, a następnie oś Y. Ma to na celu uniknięcie tego błędu: http://i.stack.imgur.com/NLg4j.png


Obecna metoda działa dobrze, gdy obiekt porusza się w odtwarzaczu, a gracz musi zostać popchnięty poziomo. Jak widać na .gif, poziome kolce popychają gracza poprawnie.


Jednak gdy pionowe kolce przemieszczają się do gracza, oś X jest nadal rozpatrywana jako pierwsza. To uniemożliwia „wykorzystanie kolców jako podnośnika”.

Kiedy gracz porusza się do pionowych kolców (pod wpływem grawitacji, wpada w nie), pcha się na oś Y, ponieważ na początku na osi X nie zachodziło na siebie nakładanie się.


Coś, co wypróbowałem, to metoda opisana w pierwszej odpowiedzi tego linku: wykrywanie kolizji obiektów prostokątnych 2D

Jednak kolce i poruszające się obiekty poruszają się, zmieniając ich położenie, a nie prędkość, i nie obliczam ich następnej przewidywanej pozycji, dopóki nie zostanie wywołana metoda Update (). Nie trzeba dodawać, że to rozwiązanie również nie działało. :(


Muszę rozwiązać kolizję AABB w taki sposób, aby oba opisane powyżej przypadki działały zgodnie z przeznaczeniem.

To jest mój obecny kod źródłowy kolizji: http://pastebin.com/MiCi3nA1

Byłbym naprawdę wdzięczny, gdyby ktoś mógł się temu przyjrzeć, ponieważ ten błąd występował w silniku od samego początku, a ja starałem się znaleźć dobre rozwiązanie, bez żadnego sukcesu. To poważnie zmusza mnie do spędzania nocy na przeglądaniu kodu kolizji i uniemożliwia mi przejście do „zabawy” i kodowanie logiki gry :(


Próbowałem wdrożyć ten sam system kolizji, co w wersji demonstracyjnej platformówki XNA AppHub (kopiując i wklejając większość rzeczy). Jednak błąd „skakania” występuje w mojej grze, podczas gdy nie występuje w wersji demo AppHub. [bug bug: http://i.stack.imgur.com/NLg4j.png ]

Aby skoczyć, sprawdzam, czy gracz jest „na ziemi”, a następnie dodaj -5 do Velocity.Y.

Ponieważ Velocity.X gracza jest wyższy niż Velocity.Y (patrz czwarty panel na schemacie), onGround jest ustawiany na true, kiedy nie powinien, i pozwala graczowi skakać w powietrzu.

Wydaje mi się, że tak się nie dzieje w wersji demonstracyjnej AppHub, ponieważ prędkość gracza nigdy nie będzie wyższa niż Velocity.Y, ale mogę się mylić.

Rozwiązałem to wcześniej, rozwiązując najpierw na osi X, a następnie na osi Y. Ale to zepsuło kolizję z kolcami, jak powiedziałem powyżej.

Vittorio Romeo
źródło
5
W rzeczywistości nic się nie dzieje. Metoda, której używam, najpierw naciska na oś X, a następnie na oś Y. Dlatego gracz jest popychany poziomo, nawet na pionowych kolcach. Muszę znaleźć rozwiązanie, które pozwoli uniknąć „problemu skakania” i popchnie gracza na płytką oś (oś o najmniejszej penetracji), ale nie mogę jej znaleźć.
Vittorio Romeo
1
Możesz wykryć, której powierzchni przeszkody dotyka gracz, i rozwiązać tę
Ioachim
4
@Johnathan Hobbs, przeczytaj pytanie. Vee dokładnie wie, co robi jego kod, ale nie wie, jak rozwiązać określony problem. Przejście przez kod nie pomoże mu w tej sytuacji.
AttackingHobo
4
@Maik @Jathanathan: Nic nie idzie źle z programem - dokładnie rozumie, gdzie i dlaczego jego algorytm nie robi tego, co chce. Po prostu nie wie, jak zmienić algorytm, aby robić to, co chce. Debuger nie jest w tym przypadku przydatny.
BlueRaja - Danny Pflughoeft
1
@AttackingHobo @BlueRaja Zgadzam się i usunąłem swój komentarz. To właśnie wyciągałem wnioski na temat tego, co się działo. Naprawdę przepraszam za zmuszanie cię do wyjaśnienia tego i za wprowadzenie w błąd co najmniej jednej osoby. Szczerze mówiąc, możesz liczyć na to, że naprawdę odpowiednio wchłonę pytanie, zanim odpowiem następnym razem.
doppelgreener

Odpowiedzi:

9

OK, zorientowałem się, dlaczego demo platformówki XNA AppHub nie ma błędu „przeskakiwania”: demo testuje płytki kolizji od góry do dołu . Gracz stojący naprzeciw „ściany” może nakładać się na wiele płytek. Kolejność rozwiązywania jest ważna, ponieważ rozwiązanie jednej kolizji może również rozwiązać inne kolizje (ale w innym kierunku). onGroundWłaściwość ma wartość tylko wtedy, gdy kolizja jest rozwiązany poprzez wciśnięcie player up na osi y. Ta rozdzielczość nie nastąpi, jeśli poprzednie rozdzielczości pchały odtwarzacz w dół i / lub w poziomie.

Byłem w stanie odtworzyć błąd „skakania” w demie XNA, zmieniając ten wiersz:

for (int y = topTile; y <= bottomTile; ++y)

do tego:

for (int y = bottomTile; y >= topTile; --y)

(Poprawiłem także niektóre stałe związane z fizyką, ale to nie powinno mieć znaczenia).

Być może sortowanie bodiesToCheckna osi Y przed rozwiązaniem kolizji w grze naprawi błąd „skakania”. Sugeruję rozwiązanie kolizji na „płytkiej” osi penetracji, jak robi to demo XNA i sugeruje Trevor . Zauważ również, że odtwarzacz demonstracyjny XNA jest dwa razy wyższy niż kolidujące płytki, co zwiększa prawdopodobieństwo wielokrotnego zderzenia.

Leftium
źródło
Brzmi interesująco ... Czy uważasz, że uporządkowanie wszystkiego według współrzędnej Y przed wykryciem kolizji może rozwiązać wszystkie moje problemy? Czy jest jakiś haczyk?
Vittorio Romeo,
Aaa i znalazłem „haczyk”. Za pomocą tej metody poprawiony jest błąd skoku, ale jeśli ustawię Velocity.Y na 0 po uderzeniu w sufit, pojawi się ponownie.
Vittorio Romeo,
@Vee: ustaw Position = nextPositionbezpośrednio w forpętli, w przeciwnym razie onGroundnadal występują niepożądane rozdzielczości (ustawienia ) kolizji . Gracz powinien być popychany pionowo w dół (i nigdy w górę) podczas uderzania w sufit, dlatego onGroundnigdy nie powinien być ustawiony. W ten sposób robi to demo XNA i nie mogę tam naprawić błędu „pułapu”.
Leftium
pastebin.com/TLrQPeBU - to jest mój kod: faktycznie pracuję nad samą pozycją, bez użycia zmiennej „nextPosition”. Powinno działać tak, jak w demie XNA, ale jeśli nie zmienię linii, która ustawia Velocity.Y na 0 (linia 57), nie pojawia się komentarz, pojawia się błąd skoku. Jeśli go usunę, gracz będzie unosił się po skoku do sufitu. Czy można to naprawić?
Vittorio Romeo,
@Vee: Twój kod nie pokazuje, jak onGroundjest ustawiony, więc nie mogę ustalić, dlaczego skakanie jest niepoprawnie dozwolone. Demo XNA aktualizuje swoją analogiczną isOnGroundwłaściwość wewnątrz HandleCollisions(). Ponadto, po ustawieniu Velocity.Yna 0, dlaczego grawitacja nie zaczyna już przesuwać odtwarzacza w dół? Domyślam się, że onGroundwłaściwość jest niepoprawnie ustawiona. Zobacz, jak aktualizuje się wersja demo XNA previousBottomi isOnGround( IsOnGround).
Leftium
9

Najprostszym rozwiązaniem będzie sprawdzenie obu kierunków kolizji z każdym obiektem na świecie przed rozwiązaniem kolizji i po prostu rozwiązanie mniejszej z dwóch wynikowych „kolizji złożonych”. Oznacza to, że rozpatrujesz w najmniejszej możliwej ilości, zamiast zawsze najpierw rozstrzygać x lub zawsze najpierw y.

Twój kod wyglądałby mniej więcej tak:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

duża rewizja : po przeczytaniu komentarza do innych odpowiedzi, wydaje mi się, że w końcu zauważyłem niepotwierdzone założenie, które spowoduje, że to podejście nie zadziała (i które wyjaśnia, dlaczego nie mogłem zrozumieć problemów, że niektóre - ale nie wszystkie - ludzie widzieli z takim podejściem). Aby to rozwinąć, oto trochę więcej pseudokodów, pokazujących bardziej wyraźnie, jakie funkcje, do których się wcześniej odwoływałem, powinny faktycznie działać:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

To nie jest test „pary obiektów”; nie działa, testując i rozwiązując poruszający się obiekt indywidualnie dla każdego kafelka mapy świata (takie podejście nigdy nie zadziała niezawodnie i zawodzi w coraz bardziej katastrofalny sposób, gdy zmniejszają się rozmiary kafelków). Zamiast tego testuje jednocześnie poruszający się obiekt względem każdego obiektu na mapie świata, a następnie rozwiązuje go na podstawie kolizji z całą mapą świata.

W ten sposób upewniasz się, że (na przykład) pojedyncze kafelki w ścianie nigdy nie odbijają gracza w górę iw dół między dwoma sąsiadującymi kafelkami, w wyniku czego gracz zostaje uwięziony w jakiejś nieistniejącej przestrzeni „między” nimi; odległości rozwiązywania kolizji są zawsze obliczane aż do pustej przestrzeni na świecie, a nie tylko do granicy pojedynczej płytki, na której może znajdować się kolejna solidna płytka.

Trevor Powell
źródło
1
i.stack.imgur.com/NLg4j.png - dzieje się tak, jeśli użyję powyższej metody.
Vittorio Romeo,
1
Być może nie zaniżam poprawnie kodu, ale pozwól mi wyjaśnić problem na schemacie: ponieważ odtwarzacz jest szybszy na osi X, penetracja X jest większa niż penetracja Y. Kolizja wybiera oś Y (ponieważ penetracja jest mniejsza) i popycha gracza w pionie. Nie dzieje się tak, jeśli gracz jest wystarczająco wolny, aby penetracja Y była zawsze większa niż penetracja X.
Vittorio Romeo
1
@Vee Do którego panelu diagramu mówisz tutaj, gdy podajesz nieprawidłowe zachowanie przy użyciu tego podejścia? Zakładam panel 5, w którym gracz wyraźnie penetruje mniej w X niż w Y, co spowodowałoby wypchnięcie wzdłuż osi X - co jest prawidłowym zachowaniem. Złe zachowanie występuje tylko wtedy, gdy wybierasz oś do rozdzielczości w oparciu o prędkość (jak na zdjęciu), a nie w oparciu o rzeczywistą penetrację (jak sugerowałem).
Trevor Powell,
1
@ Trevor: Ach, rozumiem co mówisz. W takim przypadku możesz po prostu to zrobićif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft,
1
@BlueRaja Świetny punkt. Zredagowałem moją odpowiedź, aby użyć mniejszej liczby instrukcji warunkowych, jak sugerujesz. Dzięki!
Trevor Powell,
6

Edytować

Mam poprawiła go

Wydaje się, że kluczową rzeczą, którą zmieniłem, jest klasyfikacja skrzyżowań.

Skrzyżowania są albo:

  • Uderzenia sufitu
  • Trafienia w ziemię (wypchnięcie z podłogi)
  • Zderzenia ścian w powietrzu
  • Zderzenia uziemionych ścian

i rozwiązuję je w tej kolejności

Zdefiniuj kolizję z ziemią jako gracz znajdujący się co najmniej 1/4 drogi na płytce

To jest zderzenie z ziemią, a gracz ( niebieski ) usiądzie na płytce ( czarny ) zderzenie z ziemią

Ale to NIE jest zderzenie z ziemią, a gracz „poślizgnie się” po prawej stronie płytki, kiedy na nią wyląduje gracz nie chce być naziemnym pojazdem kolizyjnym

Dzięki tej metodzie gracz nie będzie już przyłapany na ścianach

Bobobobo
źródło
Wypróbowałem twoją metodę (zobacz moją implementację: pastebin.com/HarnNnnp ), ale nie wiem, gdzie ustawić prędkość gracza na 0. Próbowałem ustawić ją na 0, jeśli encrY <= 0, ale to pozwala graczowi zatrzymać się w powietrzu przesuwając się wzdłuż ściany, a także pozwala mu wielokrotnie skakać po ścianie.
Vittorio Romeo,
Działa to jednak poprawnie, gdy gracz wchodzi w interakcje z kolcami (jak pokazano w .gif). Byłbym bardzo wdzięczny, gdybyś mógł mi pomóc w rozwiązaniu problemu skakania przez ściany (również utknięcia w ścianach). Pamiętaj, że moje ściany są wykonane z kilku płytek AABB.
Vittorio Romeo,
Przepraszamy za opublikowanie kolejnego komentarza, ale po wklejeniu kodu w nowym projekcie, zmianie sp na 5 i spowodowaniu, że płytki odradzają się w kształcie ściany, pojawia się błąd skoku (patrz ten schemat: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo,
Ok, spróbuj teraz.
bobobobo
+1 za działający przykładowy kod (brakuje jednak bloków przesuwających się poziomo :)
Leftium
3

Uwaga:
Mój silnik wykorzystuje klasę wykrywania kolizji, która sprawdza kolizje ze wszystkimi obiektami w czasie rzeczywistym, tylko gdy obiekt się porusza i tylko na kwadratach siatki, które aktualnie zajmuje.

Moje rozwiązanie:

Kiedy natrafiłem na takie problemy w platformówce 2D, zrobiłem coś takiego:

- (silnik wykrywa możliwe kolizje, szybko)
- (gra informuje silnik, aby sprawdził dokładne kolizje dla konkretnego obiektu) [zwraca wektor obiektu *]
- (obiekt patrzy na głębokość penetracji, a także poprzednią pozycję względem poprzedniej pozycji innego obiektu, aby ustalić, z której strony się wysunąć)
- (obiekt się porusza i uśrednia jego prędkość z prędkością obiektu (jeśli obiekt się porusza))

ultifinitus
źródło
„(obiekt patrzy na głębokość penetracji, a także poprzednią pozycję w stosunku do poprzedniej pozycji innego obiektu, aby ustalić, z której strony należy się wysunąć)”. To jest część, która mnie interesuje. Czy możesz to rozwinąć? Czy to się udaje w sytuacji pokazanej przez .gif i diagram?
Vittorio Romeo
Jeszcze nie znalazłem sytuacji (innej niż duże prędkości), w której ta metoda nie działa.
ultifinitus
Brzmi dobrze, ale czy potrafisz wyjaśnić, w jaki sposób rozwiązujesz penetrację? Czy rozwiązujesz zawsze na najmniejszej osi? Czy sprawdzasz bok kolizji?
Vittorio Romeo,
Nie, w rzeczywistości mniejsza oś nie jest dobrą (podstawową) drogą. MOIM ZDANIEM. Zwykle rozwiązuję na podstawie tego, która strona była wcześniej poza stroną innego obiektu, jest to najczęściej. Kiedy to się nie powiedzie, sprawdzam na podstawie prędkości i głębokości.
ultifinitus
2

W grach wideo, które zaprogramowałem, podejście polegało na tym, aby mieć funkcję informującą, czy twoje stanowisko jest prawidłowe, tj. bool ValidPos (int x, int y, int wi, int he);

Funkcja sprawdza ramkę ograniczającą (x, y, wi, he) względem całej geometrii w grze i zwraca false, jeśli występuje jakieś skrzyżowanie.

Jeśli chcesz się poruszyć, powiedz w prawo, zajmij pozycję gracza, dodaj 4 do x i sprawdź, czy pozycja jest prawidłowa, jeśli nie, sprawdź za pomocą + 3, + 2 itd.

Jeśli potrzebujesz również grawitacji, potrzebujesz zmiennej, która rośnie, dopóki nie uderzysz w ziemię (uderzanie w ziemię: ValidPos (x, y + 1, wi, he) == prawda, y jest tutaj dodatnie w dół). jeśli możesz przesunąć tę odległość (np. ValidPos (x, y + grawitacja, wi, on) zwraca wartość true) upadasz, przydatne czasem, gdy nie powinieneś być w stanie kontrolować swojej postaci podczas upadku.

Teraz twoim problemem jest to, że masz obiekty w swoim świecie, które się poruszają, więc przede wszystkim musisz sprawdzić, czy twoja stara pozycja jest nadal aktualna!

Jeśli tak nie jest, musisz znaleźć odpowiednią pozycję. Jeśli obiekty w grze nie mogą poruszać się szybciej niż powiedzmy 2 piksele na obrót gry, musisz sprawdzić, czy pozycja (x, y-1) jest poprawna, a następnie (x, y-2), a następnie (x + 1, y) itd. itd. należy sprawdzić całą przestrzeń między (x-2, y-2) do (x + 2, y + 2). Jeśli nie ma prawidłowej pozycji, oznacza to, że zostałeś „zmiażdżony”.

HTH

Valmond

Valmond
źródło
Kiedyś korzystałem z tego podejścia w czasach Game Maker. Mogę wymyślić sposoby na przyspieszenie / ulepszenie go za pomocą lepszych wyszukiwań (powiedz binarne wyszukiwanie przejechanej przestrzeni, chociaż w zależności od przebytej odległości i geometrii może być wolniejsze), ale główną wadą tego podejścia jest że zamiast robić powiedz jeden czek w stosunku do wszystkich możliwych kolizji, robisz wszystko, by zmienić pozycję.
Jeff
„robisz wszystko, by zmienić pozycję - czeki” Przykro mi, ale co dokładnie znaczy, niż znaczy? BTW oczywiście użyjesz technik partycjonowania przestrzeni (BSP, Octree, quadtree, Tilemap itp.), Aby przyspieszyć grę, ale i tak zawsze musisz to zrobić (jeśli mapa jest duża), pytanie nie dotyczy tego, ale o algorytmie używanym do (poprawnego) przemieszczania odtwarzacza.
Valmond
@Valmond Myślę, że @Jeff oznacza, że ​​jeśli gracz przesuwa się o 10 pikseli w lewo, możesz mieć do 10 różnych detekcji kolizji.
Jonathan Connell,
Jeśli to właśnie ma na myśli, to ma rację i tak powinno być (kto inaczej może stwierdzić, czy zatrzymamy się na +6, a nie na +7?). Komputery są szybkie, użyłem tego na S40 (~ 200 MHz i niezbyt szybki KVM) i działało to jak urok. Zawsze możesz spróbować zoptymalizować początkowe algo, ale to zawsze da ci narożne przypadki, jak ten, który ma OP.
Valmond
Dokładnie o to mi chodziło
Jeff
2

Mam kilka pytań, zanim zacznę na nie odpowiadać. Po pierwsze, w oryginalnym błędzie, w którym utknąłeś w ścianach, czy te płytki po lewej stronie były pojedynczymi kafelkami, a nie jedną dużą płytką? A jeśli tak, to czy gracz utknął między nimi? Jeśli tak, to upewnij się, że Twoje nowe stanowisko jest prawidłowe . Oznacza to, że musisz sprawdzić, czy występuje kolizja w miejscu, w którym każesz graczowi się ruszyć. Rozwiąż więc minimalne przemieszczenie, jak opisano poniżej, a następnie przenieś gracza na podstawie tego tylko wtedy, gdy możerusz się tam. Niemal też pod nosem: P To faktycznie wprowadza kolejny błąd, który nazywam „przypadkami narożnymi”. Zasadniczo w kategoriach narożników (jak lewy dolny róg, w którym poziome kolce wychodzą w twoim .gif, ale jeśli nie byłoby kolców) nie rozwiązałoby kolizji, ponieważ wydawałoby się, że żadna z generowanych przez ciebie rozdzielczości nie prowadzi do prawidłowej pozycji . Aby rozwiązać ten problem, po prostu sprawdź, czy kolizja została rozwiązana, a także listę wszystkich minimalnych rozdzielczości penetracji. Następnie, jeśli kolizja nie została rozwiązana, zapętlaj każdą wygenerowaną rozdzielczość i śledź maksymalne rozdzielczości X i maksymalne Y (maksima nie muszą pochodzić z tej samej rozdzielczości). Następnie rozstrzygnij kolizję na tych maksimach. Wydaje się, że to rozwiązuje wszystkie problemy, jak również te, które napotkałem.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

Kolejne pytanie, czy kolce pokazują jedną płytkę, czy pojedyncze płytki? Jeśli są to pojedyncze cienkie płytki, być może będziesz musiał zastosować inne podejście do tych poziomych i pionowych niż te, które opisuję poniżej. Ale jeśli są to całe kafelki, powinno to działać.


W porządku, więc w zasadzie to właśnie opisywał @Trevor Powell. Ponieważ używasz tylko AABB, wszystko, co musisz zrobić, to dowiedzieć się, ile jednego prostokąta przenika drugi. To da ci ilość na osi X i Y. Wybierz minimum z dwóch i przesuń kolidujący obiekt wzdłuż tej osi o tę kwotę. To wszystko, czego potrzebujesz, aby rozwiązać kolizję AABB. NIGDY nie będziesz musiał poruszać się wzdłuż więcej niż jednej osi w takiej kolizji, więc nigdy nie powinieneś być zdezorientowany co do tego, który ruch wykonać jako pierwszy, ponieważ będziesz poruszał się tylko o minimum.

Oprogramowanie Metanet ma klasyczny tutorial podejścia tutaj . Zmienia się również w inne kształty.

Oto funkcja XNA, którą utworzyłem, aby znaleźć wektor nakładania się dwóch prostokątów:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(Mam nadzieję, że jest to łatwe do naśladowania, ponieważ jestem pewien, że istnieją lepsze sposoby na jego wdrożenie ...)

isCollision (Rectangle) to dosłownie tylko połączenie z XNA's Rectangle.Intersects (Rectangle).

Przetestowałem to na ruchomych platformach i wydaje się, że działa dobrze. Zrobię jeszcze kilka testów bardziej podobnych do twojego .gif, aby się upewnić i zgłoś się, jeśli to nie zadziała.


Jeff
źródło
Jestem trochę sceptyczny wobec tego komentarza, który napisałem na ten temat, że nie działa z cienkimi platformami. Nie mogę wymyślić powodu, dla którego by tego nie zrobił. Również jeśli chcesz źródła, mogę załadować dla ciebie projekt XNA
Jeff
Tak, testowałem to jeszcze trochę i wraca do tego samego problemu utknięcia w ścianie. Dzieje się tak, ponieważ tam, gdzie dwa kafelki ustawiają się w linii, mówi ci, abyś przesunął się w górę z powodu niższego, a następnie wysunął się (w twoim przypadku) do górnego, skutecznie negując ruch grawitacyjny, jeśli twoja prędkość y jest wystarczająco wolna . Będę musiał znaleźć rozwiązanie tego problemu. Wydaje mi się, że wcześniej miałem ten problem.
Jeff
Okej, wymyśliłem bardzo hackerski sposób na obejście twojego pierwszego problemu. Rozwiązanie polega na znalezieniu minimalnej penetracji dla każdego AABB, rozpatrywaniu tylko na tym z największym X, a następnie na drugim przejściu rozstrzygającym tylko na największym Y. Wygląda źle, kiedy jesteś miażdżony, ale podnosi pracę i możesz zostać popchnięty poziomo, a Twój pierwotny problem z klejeniem zniknął. To jest hackey i nie mam pojęcia, jak przełożyłoby się to na inne kształty. Inną możliwością może być spawanie dotykających się geometrii, ale nawet nie próbowałem tego
Jeff
Ściany wykonane są z płytek 16x16. Kolce to płytka 16 x 16. Jeśli rozstrzygnę na osi minimalnej, pojawi się błąd skoku (spójrz na schemat). Czy Twoje „wyjątkowo hackujące” rozwiązanie działałoby w sytuacji pokazanej na .gif?
Vittorio Romeo
Tak, działa w moim. Jaki jest rozmiar twojej postaci?
Jeff
1

To proste.

Przepisz kolce. To wina kolców.

Twoje kolizje powinny mieć miejsce w dyskretnych, namacalnych jednostkach. Problem, o ile widzę, to:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

Problem dotyczy kroku 2, a nie 3 !!

Jeśli starasz się, aby rzeczy stały się solidne, nie powinieneś pozwalać, aby przedmioty wsuwały się w siebie w ten sposób. Gdy znajdziesz się na skrzyżowaniu, jeśli stracisz miejsce, problem staje się trudniejszy do rozwiązania.

Kolce powinny idealnie sprawdzić istnienie gracza, a kiedy się poruszają, powinny go popchnąć w razie potrzeby.

Łatwym sposobem na osiągnięcie tego jest posiadanie przez gracza moveXi moveYfunkcje, które rozumieją krajobraz i będą pchały gracza o określoną deltę lub tak daleko, jak to możliwe bez uderzania w przeszkodę .

Zwykle będą wywoływane przez pętlę zdarzeń. Mogą być jednak również wywoływane przez obiekty w celu popchnięcia gracza.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Oczywiście gracz nadal musi reagować na kolce jeśli on idzie do nich .

Nadrzędną zasadą jest, że po każdym ruchu obiektu powinien kończyć się w pozycji bez przecięcia. W ten sposób niemożliwe jest uzyskanie usterki, którą widzisz.

Chris Burt-Brown
źródło
w skrajnym przypadku, gdy moveYnie uda się usunąć skrzyżowania (ponieważ przeszkadza mu inna ściana), możesz ponownie sprawdzić skrzyżowanie i spróbować moveX lub po prostu go zabić.
Chris Burt-Brown