Plattformer bauen - Wie kann man feststellen, ob ein Spieler springen darf?


16

Ich baue ein einfaches Plattformer Jump n 'Run Style Spiel. Ich verwende keine Kacheln - stattdessen habe ich geometrische Formen für meine Level-Objekte (und der Player ist auch eine). Ich habe meinen Kollisionserkennungscode beendet und alles funktioniert soweit.

Als nächstes wollte ich das Springen implementieren. Überprüfen Sie einfach, ob der Spieler die entsprechende Taste drückt, und erhöhen Sie die Aufwärtsgeschwindigkeit. Funktioniert gut. Aber es funktioniert auch, wenn der Spieler in der Luft ist, was ich nicht will. ;-)

Ich muss also prüfen, ob der Spieler auf etwas steht. Meine erste Idee war, zu überprüfen, ob es im letzten Frame eine Kollision gab, und den Spieler als "springfähig" zu markieren, aber dies würde sogar ausgelöst, wenn der Spieler eine Wand in der Luft trifft. Da meine mathematischen Fähigkeiten nicht so gut sind, bitte ich um Hilfe - auch Hinweise, wie man das umsetzt.

Vielen Dank!

Antworten:


14

Zwei Optionen sind denkbar:

  • Der erste Gedanke ist, die Geometrie mit einer ID zu kennzeichnen und dann zu prüfen, ob die Kollision mit der als Boden gekennzeichneten Geometrie vorliegt. Dies bietet die größtmögliche Kontrolle über springbare Oberflächen, kostet jedoch Zeit bei der Levelerstellung.
  • Zweitens, um die Normalität der Kollision zu überprüfen. Wenn sie nach oben zeigt, dann erlaube einen Sprung. Oder innerhalb einer gewissen Spanne von bis, hängt davon ab, ob Sie schräge Böden haben. Dies ist flexibel und erfordert keine Kennzeichnung, aber wenn Sie geneigte Böden und Wände haben, können Sie springen, wo Sie es nicht wollen.

Die normale Prüfung scheint mir eine großartige Idee zu sein. Danke, dass du das gepostet hast.
Christopher Horenstein

+1 für normale Prüfung. Dies stellt sicher, dass ein Boden nahtlos in eine Wand übergehen kann, ohne Probleme zu verursachen.
Soviut

1
+1 Auf jeden Fall die Kollisionsnormale überprüfen. Verschiedene Arten von Weltgeometrie zu haben ist in Ordnung und ermöglicht ein flexibleres Level-Design, löst aber nicht das Hauptproblem. Eine "Wand" kann auch vom Typ "Boden" sein, je nachdem, ob Sie darauf stoßen oder darauf stehen. Hier hilft die Kollisionsnormale.
Mistzack

Ich finde die normale Lösung sehr schön und würde mein Problem lösen. Auch die andere hoch bewertete Antwort ist nett und so - aber das beantwortet meine Frage besser. Vielen Dank, dass Sie uns besucht haben!
Malax

8

Sie sollten sicherlich eine Art Oberflächentyp implementieren. Denken Sie darüber nach, wie werden Sie es schaffen, wenn Sie eine Leiter hochklettern können, wenn Sie nicht wissen, ob Ihr Charakter gerade gegen eine Wand oder eine Leiter gestoßen ist? Sie könnten einfach OOP verwenden, um eine Typhierarchie mithilfe des Erbes zu verwalten. Ich empfehle jedoch, "Kategorien" zu verwenden, die mit einem Aufzählungstyp implementiert wurden:

Hier ist die Idee: Eine Aufzählung "Kollisionen" hat ein Flag für jede Kategorie. Beispielsweise:

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
    };
}

Mit dieser Methode können Sie testen, ob der Spieler irgendetwas kollidiert hat, das Sie verwalten sollten, sodass Ihre Engine eine "Kollidierte" Methode der Entität aufrufen kann:

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
   }
}

Die Methode verwendet bitweise Flags und den bitweisen "Oder" -Operator, um sicherzustellen, dass jede Gruppe basierend auf dem Binärwert der Kategorie einen anderen Wert hat. Diese Methode funktioniert einwandfrei und ist leicht skalierbar, sodass Sie Zollkollisionsgruppen erstellen können. Jede Entität (Spieler, Feind usw.) in Ihrem Spiel verfügt über einige Bits, die als "Filter" bezeichnet werden und die bestimmen, womit sie kollidieren kann. Ihr Kollisionscode sollte prüfen, ob die Bits übereinstimmen, und entsprechend reagieren, mit einem Code, der wie folgt aussehen könnte:

void PhysicEngine::OnCollision(...)
{
    mPhysics.AddContact( body1, body1.GetFilter(), body2, body2.GetFilter() );
}

Gibt es einen Grund, statt einer Aufzählung const ints zu verwenden? Im entarteten Fall eines einzelnen Flags oder einer benannten Gruppe ist der Aufzählungstyp besser lesbar, und Sie erhalten in den meisten Compilern zusätzliche Möglichkeiten zur Typprüfung.

Ja, die Aufzählung kann (glaube ich) keine binären Operatoren verwenden, um neue benutzerdefinierte Gruppen zu erstellen. Der Grund, warum ich diese Konstante erstellt habe, ist, dass ich aus Gründen der Lesbarkeit eine Config-Klasse mit öffentlichen statischen Elementen verwende, um die Sicherheit der Konfigurationsparameter zu gewährleisten.
Frédérick Imbeault

Es kann. Aufzählungswerte können bitweise Operationen mit anderen konstanten Werten sein (ich denke, es kann tatsächlich jeder konstante Ausdruck sein, aber ich bin zu faul, um den Standard zu überprüfen). Nicht statische Werte werden genauso wie in Ihrem Beispiel erzielt, sie werden jedoch genauer eingegeben. Ich habe keine Ahnung, was Sie unter "Sicherheit" verstehen, aber mit einem Namespace kann genau dieselbe Syntax ohne den mit einer Klasse verbundenen Overhead erreicht werden.

Ich habe Ihre Codebeispiele aktualisiert, um einen Aufzählungstyp zu verwenden.

Vielleicht macht es die Gedanken leichter zu lesen. Ich stimme den von Ihnen vorgenommenen Änderungen zu, die von mir verwendete Methode war korrekt, aber ich hatte den Code für die Erklärung nicht angepasst. Vielen Dank für die Korrekturen. Für den Namespace haben Sie vollkommen recht, dies gilt jedoch nicht für alle Sprachen (z. B. Skriptsprachen). Die "Sicherheit" ist, dass meine Konfigurationsoptionen statisch sind, so dass sie nicht geändert werden können. Die Verwendung einer Enumeration verhindert dies jedoch ebenfalls.
Frédérick Imbeault

1

Wenn Sie davon ausgehen, dass sich der Fuß Ihres Charakters um eine gewisse Distanz unter dem Charakter befindet und Sie sich nicht von der Oberfläche entfernen, befindet sich Ihr Charakter auf dem Boden.

In grobem Pseudocode:

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; }


Dies funktioniert nicht, wenn Sie Funktionen wie Doppelsprünge oder Mauersprünge hinzufügen möchten, denke ich?
Frédérick Imbeault

Ich glaube nicht, dass der OP Doppelsprünge oder Mauersprünge erwähnt hat, oder?
Jpaver

Ich habe den Code überarbeitet, um zu zeigen, wie der Vektor von der Zeichenmitte zu Ihrem Fuß abstrahiert wird. Wenn Sie dies animieren und die Füße seitwärts stehen und gegen eine Wand stoßen, können Sie den Wandsprung unterstützen.
Jpaver

Dies ist im Grunde eine weniger flexible Möglichkeit, die Kollisionsnormalität zu überprüfen. (Weniger flexibel, da Sie einen

2
Niemals eine Variable aufrufen char, auch nicht im Pseudocode.
rechts
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.