Budowanie platformy - jak ustalić, czy gracz może skakać?

16

Buduję prostą grę Jump n 'Run w stylu Plattformer. Nie używam kafelków - zamiast tego mam geometryczne kształty dla moich jednostek poziomu (a gracz też jest jednym). Skończyłem kod wykrywania kolizji i do tej pory wszystko działa dobrze.

Następnie chciałem zastosować skakanie. Tylko sprawdzam, czy gracz naciska odpowiedni klawisz i dodaje trochę prędkości w górę. Działa w porządku. Ale działa, nawet jeśli odtwarzacz jest w powietrzu, czego nie chcę. ;-)

Muszę więc sprawdzić, czy gracz na czymś stoi. Moim pierwszym pomysłem było sprawdzenie, czy w ostatniej klatce doszło do kolizji i oznaczenie gracza jako „zdolnego do skoku”, ale zadziałałoby to nawet, gdyby gracz uderzył w ścianę w powietrzu. Ponieważ moje umiejętności matematyczne nie są tak dobre, proszę o pomoc - nawet wskazówki, jak to zrobić.

Dzięki!

Malax
źródło

Odpowiedzi:

14

Przychodzą mi na myśl dwie opcje:

  • Najpierw należy oznaczyć geometrię identyfikatorem, a następnie sprawdzić, czy kolizja dotyczy geometrii oznaczonej jako podłoga. Zapewnia to największą kontrolę nad przeskakiwanymi powierzchniami, ale kosztem czasu tworzenia poziomu.
  • Drugim jest sprawdzenie normalności kolizji, jeśli jest skierowana do góry, pozwól skoczyć. Lub w pewnym marginesie wzrostu, zależy od tego, czy masz pochyłe podłogi. Jest to elastyczne i nie wymaga tagowania, ale jeśli masz pochyłe podłogi i ściany, możesz skakać tam, gdzie nie chcesz.
wkerslake
źródło
Normalna kontrola wydaje mi się świetnym pomysłem. Dzięki za opublikowanie tego.
Christopher Horenstein
+1 za normalne sprawdzanie. To gwarantuje, że podłoga może płynnie przejść w ścianę bez powodowania problemów.
Soviut
1
+1 Zdecydowanie sprawdź normalną kolizję. Posiadanie różnych rodzajów geometrii świata jest w porządku i pozwala na bardziej elastyczny projekt poziomów, ale nie rozwiązuje to głównego problemu. „Ściana” może być również typu „ziemia”, w zależności od tego, czy na nią wpadniesz, czy stoisz na niej. Tam pomoże normalna kolizja.
bummzack
Uważam, że normalne rozwiązanie jest bardzo miłe i rozwiązałoby mój problem. Nawet inna wysoko oceniana odpowiedź jest miła i taka - ale to lepiej odpowiada na moje pytanie. Dziękujemy swojemu wkerslake!
Malax
8

Z pewnością powinieneś wdrożyć jakiś rodzaj powierzchni. Pomyśl o tym, jak sobie poradzisz, jeśli będziesz mógł wspiąć się po drabinie, jeśli nie wiesz, czy twoja postać po prostu zderzyła się ze ścianą lub drabiną? Możesz po prostu użyć OOP do zarządzania hierarchią typów przy użyciu dziedzictwa, ale sugerowałbym użycie „kategorii” zaimplementowanych przy użyciu typu wyliczonego:

Oto pomysł: Wyliczenie „Kolizje” ma flagę dla każdej kategorii. Na przykład:

namespace Collisions
{
    enum Type
    {
        None   = 0,
        Floor  = 1 << 0,
        Ladder = 1 << 1,
        Enemy  = 1 << 2,
        ... // And whatever else you need.

        // Then, you can construct named groups of flags.
        Player = Floor | Ladder | Enemy
    };
}

Dzięki tej metodzie będziesz mógł sprawdzić, czy gracz po prostu zderzył się z czymś, czym powinieneś zarządzać, aby twój silnik mógł nazwać metodę „zderzoną” encji:

void Player::Collided( Collisions::Type group )
{
   if ( group & Collisions::Ladder )
   {
      // Manage Ladder Collision
   }
   if ( group & Collisions::Floor )
   {
      // Manage Floor Collision
   }
   if ( group & Collisions::Enemy )
   {
      // Manage Enemy Collision
   }
}

Metoda wykorzystuje flagi bitowe i bitowy operator „Lub”, aby upewnić się, że każda grupa ma inną wartość na podstawie wartości binarnej kategorii. Ta metoda działa dobrze i jest łatwo skalowalna, dzięki czemu można tworzyć celne grupy kolizyjne. Każda jednostka (gracz, wróg itp.) W twojej grze ma pewne bity zwane „filtrem”, które służą do ustalenia, z czym może się zderzyć. Twój kod kolizyjny powinien sprawdzić, czy bity są zgodne i odpowiednio zareagować, z pewnym kodem, który może wyglądać następująco:

void PhysicEngine::OnCollision(...)
{
    mPhysics.AddContact( body1, body1.GetFilter(), body2, body2.GetFilter() );
}
Frédérick Imbeault
źródło
Czy istnieje powód, aby używać const ints zamiast enum? W zdegenerowanym przypadku pojedynczej flagi lub nazwanej grupy wyliczony typ jest bardziej czytelny, aw większości kompilatorów zyskujesz dodatkowe możliwości sprawdzania typu.
Cóż, tak, wyliczanie nie może (myślę) używać operatorów binarnych do tworzenia nowych grup niestandardowych. Powodem, dla którego stworzyłem tezy const jest to, że używam klasy Config z publicznymi elementami statycznymi ze względu na czytelność, zachowując bezpieczeństwo parametrów konfiguracji.
Frédérick Imbeault
To może. Wyliczenia „wartości” mogą być bitowymi operacjami na innych stałych wartościach (myślę, że może to być dowolne stałe wyrażenie, ale jestem zbyt leniwy, aby sprawdzić standard). Wartości niestatyczne są osiągane dokładnie w taki sam sposób jak twój przykład, ale są dokładniejsze. Nie mam pojęcia, co rozumiesz przez „bezpieczeństwo”, ale dokładnie taką samą składnię bez narzutu związanego z klasą można uzyskać za pomocą przestrzeni nazw.
Zaktualizowałem twoje przykłady kodu, aby używały typu wyliczonego.
Może to ułatwia myślenie. Popieram dokonane przez ciebie zmiany, zastosowana metoda była poprawna, ale nie dostosowałem kodu do wyjaśnienia, dziękuję za poprawki. Jeśli chodzi o przestrzeń nazw, masz całkowitą rację, ale nie dotyczy to wszystkich języków (na przykład języków skryptowych). „Bezpieczeństwo” polega na tym, że moje opcje konfiguracji są statyczne, więc nie można ich modyfikować, ale użycie enum również temu zapobiega.
Frédérick Imbeault
1

Jeśli weźmiesz pod uwagę stopę swojej postaci jak zawsze pod postacią o określoną odległość, a jeśli nie oddalasz się od powierzchni, twoja postać leży na ziemi.

W przybliżeniu pseudokod:

bool isOnGround(Character& chr)
{
   // down vector is opposite from your characters current up vector.
   // if you want to support wall jumps, animate the vecToFoot as appropriate.
   vec vecToFoot = -chr.up * chr.footDistanceFromCentre;

// if feet are moving away from any surface, can't be on the ground if (dot(chr.velocity, down) < 0.0f) return false;

// generate line from character centre to the foot position vec linePos0 = chr.position; vec linePos1 = chr.position + vecToFoot;

// test line against world. If it returns a hit surface, we're on the ground if (testLineAgainstWorld(line0, line1, &surface) return true;

return false; }

jpaver
źródło
To nie zadziała, jeśli chcesz dodać funkcje, takie jak podwójne skoki lub skoki ścienne, myślę?
Frédérick Imbeault
Nie wierzę, że OP wspominał o podwójnych skokach lub skokach do ściany, prawda?
jpaver
Zmodyfikowałem kod, aby pokazać, jak wyodrębnić wektor do stopy z centrum znaków. Jeśli to zrobisz, a stopy wylądują bokiem i uderzą w ścianę, będziesz w stanie wesprzeć skok po ścianie.
jpaver
Jest to w zasadzie mniej elastyczny sposób sprawdzania normalności kolizji. (Mniej elastyczny, ponieważ można w prosty sposób odróżnić skok ze ściany od skoku z podłogi od skoku z sufitu, po prostu sprawdzając kierunek normalności.)
2
Nigdy nie wywołuj zmiennej char, nawet w pseudokodzie.
prawej