Game Engine Math

KWEngine, Teil 22: Raytracing-Kollisionen

Die Abschnitte 1) und 2) zeigen zunächst die generischen Methoden für die Strahlenmessung. Das bedeutet, dass die Strahlen immer für alle Objekte der Welt durchgeführt werden. In Abschnitt 3) (weiter unten) wird anschließend noch eine Methode vorgestellt, die speziell für Spielerfiguren verwendet werden kann, weil dort nur Elemente geprüft werden, die sich in unmittelbarer Nähe zur Spielerfigur befinden.

1) Überprüfung, ob Instanzen bestimmter Klassen von einem Teststrahl getroffen werden

Wenn Sie z.B. überprüfen möchten, ob Instanzen bestimmter Klassen im Blickfeld eines anderen Objekts liegen, können Sie wie folgt vorgehen:

public class Player : GameObject
{
    public override void Act()
    {
        Vector3 rayStart     = this.Center;
        Vector3 rayDirection = this.LookAtVector; // muss normalisiert sein!
        List<RayIntersectionExt> results = HelperIntersection.RayTraceObjectsForViewVector(
            rayStart,                        // Strahl-Startposition
            rayDirection,                    // Strahlrichtung (normalisiert)
            0,                               // Strahllänge (0 = unendlich)
            true,                            // Ergebnisliste sortieren?
            this,                            // Verweis auf das Objekt, was prüft
            typeof(Enemy), typeof(Wall), ... // Kommaseparierte Aufzählung der zu überprüfenden Klassen
        );

        if(results.Count > 0)
        {
            // Erstes Objekt der Liste angucken, weil es
            // vom Strahl als erstes getroffen wurde:
            RayIntersectionExt raycollision = results[0];

            // Objekt, das getroffen wurde:
            GameObject objectHitByRay = raycollision.Object;

            // Distanz zwischen Strahl-Startposition und dem Treffer:
            float distanceToObject = raycollision.Distance;

            // Genaue Trefferposition:
            Vector3 target = raycollision.IntersectionPoint;

            // Ebenenvektor der Oberfläche, die vom Strahl getroffen wurde:
            Vector3 normal = raycollision.SurfaceNormal;
        }
    }
}

2) Schnellere (aber ungenaue) Überprüfung, ob Instanzen bestimmter Klassen getroffen werden

Wenn Sie keine punktgenaue Analyse benötigen, können Sie sich viele unnötige Berechnungen sparen, indem Sie folgende Variante verwenden:

public class Player : GameObject
{
    public override void Act()
    {
        Vector3 rayStart     = this.Center;
        Vector3 rayDirection = this.LookAtVector; // muss normalisiert sein!
        List<RayIntersection> results = HelperIntersection.RayTraceObjectsForViewVectorFast(
            rayStart,                        // Strahl-Startposition
            rayDirection,                    // Strahlrichtung (normalisiert)
            this,                            // Verweis auf das Objekt, was prüft
            0,                               // Strahllänge (0 = unendlich)
            true,                            // Ergebnisliste sortieren?
            typeof(Enemy), typeof(Wall), ... // Kommaseparierte Aufzählung der zu überprüfenden Klassen
        );

        if(results.Count > 0)
        {
            // Erstes Objekt der Liste angucken, weil es
            // vom Strahl als erstes getroffen wurde:
            RayIntersection raycollision = results[0];

            // Objekt, das getroffen wurde:
            GameObject objectHitByRay = raycollision.Object;

            // Ungefähre Distanz zwischen Strahl-Startposition und dem Treffer:
            float distanceToObject = raycollision.Distance;
        }
    }
}

3) Strahlenmessung für springende Spielerfiguren

Jede GameObject-Instanz kann die Methode RaytraceObjectsBelowPosition() aufrufen, um einen (oder mehrere) Strahl(en) nach unten zu „schießen“. Das Ergebnis dieser Messung gibt die Methode in Form eines Objekts des Typs RayIntersectionExtSet zurück.

Die Idee hinter dem Code ist:

  • Es wird entweder ein einzelner Strahl (RayMode.SingleY) oder mehrere Strahlen (z.B. RayMode.FourRaysY) von den Füßen der Spielfigur nach unten geschossen.
  • Ein Skalierungsfaktor (Standardwert: 1f) kann optional verändert werden, um die Strahlenpositionen weiter in die Hitbox oder aus der Hitbox der Spielfigur heraus zu bewegen. Diese Einstellung sollte normalerweise bei 1f verbleiben.
  • Als nächstes wird ein minimaler Grenzwert angegeben. Sollte sich die Spielerfigur z.B. bereits teilweise in einem Bodenobjekt befinden, würden die Strahlen die Bodenfläche eigentlich nicht treffen, weil sie erst unterhalb des Bodens starten. Die Engine kompensiert diesen Umstand und gibt in diesem Fall negative Distanzwerte zurück. Eine Grenze von -1f bedeutet hier also, dass die Figur bereits eine ganze Einheit im Boden versinken darf und die Strahlenmessung dennoch als „gültig“ (wenn auch mit negativem Distanzwert) anerkannt wird.
  • Der letzte numerische Parameter gibt die Ferndistanz an. Trifft ein Strahl beispielsweise erst nach 2 Einheiten auf ein Bodenobjekt, kann dies ignoriert werden, wenn die Einstellgrenze <2f gesetzt wurde.
  • Als letzten Parameter kann eine kommaseparierte Auflistung der zu betrachtenden Klassen eingegeben werden. Objekte nichtgelisteter Klassen werden automatisch nicht berücksichtigt.

Ist die Strahlenmessung erfolgt, bekommt man einen Satz an Ergebnissen zurück (vereint in einer Instanz der Klasse RayIntersectionExtSet). Die Instanz enthält z.B.:

  • Die Eigenschaft, ob die Messung gültig ist (weil mindestens ein Objekt gefunden wurde) oder nicht (falls kein Objekt in der Nähe getroffen wurde).
  • Den Schnittpunkt des Strahls mit dem nächstgelegenen Objekt (IntersectionPointNearest).
  • Das nächstgelegene Objekt, das vom Strahl getroffen wurde (ObjectNearest).
  • Der Oberflächenvektor (als Vector3-Instanz) des Objekts, das vom Strahl getroffen wurde (SurfaceNormalNearest).

Es gibt noch weitere Eigenschaften dieses Ergebnis-Sets, die aber nur in seltenen Fällen benötigt werden.

class Player : GameObject
{
    public override void Act()
    {
        RayIntersectionExtSet resultSet = RaytraceObjectsBelowPosition(
            RayMode.SingleY,                 // Schieße einen Teststrahl entlang der negativen Y-Achse
            1f,                              // Skalierungsfaktor (falls mehrere Strahlen verwendet werden)
            -1f,                             // Strahlen mit Distanzen < -1f werden ignoriert
            0.1f,                            // Strahlen mit Distanzen > 0.1f werden ignoriert
            typeof(Floor), typeof(Obstacle)  // Kommaseparierte Aufzählung der zu betrachtenden GameObject-Klassen
        );
        
        // Prüfen, ob die Messungen verwendbar sind (d.h., wenn mindestens ein Objekt getroffen wurde):
        if(resultSet.IsValid == true)
        {
            Vector3 ipn = resultSet.IntersectionPointNearest; // Kollisionsmesspunkt, der der Spielerfigur am nächsten ist
            GameObject obj = resultSet.ObjectNearest;         // Objekt, das den nächstgelegenen Kollisionsmesspunkt erzeugt hat
            Vector3 snn = resultSet.SurfaceNormalNearest;     // Oberflächenvektor, der zum nächstgelegenen Kollisionsmesspunkt gehört

            // Beispiel: Prüfe, ob eines der getroffenen Objekte näher als 0.1f Einheiten am Player liegt:
            if (resultSet.DistanceMin < 0.1f)
            {
                // Setze den Boden der Spieler-Hitbox auf die Höhe des nächstgelegenen Schnittpunkts (ipn):
                SetPositionY(ipn.Y, PositionMode.BottomOfAABBHitbox); 
            }
        }
    }
}

Beitrag veröffentlicht

in

von

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.