Gibt es eine gute Möglichkeit, in XNA eine pixelgenaue Kollisionserkennung zu erzielen?

12

Gibt es einen bekannten Weg (oder ein wiederverwendbares Codebit) für die pixelgenaue Kollisionserkennung in XNA?

Ich gehe davon aus, dass dies auch Polygone (Kästchen / Dreiecke / Kreise) für einen ersten Durchgang, einen Schnelltest für Kollisionen, verwenden würde. Wenn dieser Test eine Kollision anzeigt, würde nach einer Kollision pro Pixel gesucht.

Dies kann kompliziert sein, da wir Skalierung, Rotation und Transparenz berücksichtigen müssen.

WARNUNG: Wenn Sie den Beispielcode aus dem Link der folgenden Antwort verwenden, beachten Sie, dass die Skalierung der Matrix aus gutem Grund auskommentiert ist. Sie müssen es nicht auskommentieren, damit die Skalierung funktioniert.

ashes999
quelle
2
Polygone oder Sprites?
Doppelgreener
Ich bin mir nicht sicher. Xna unterstützt beide? Meine Ausgabe erwähnt eine Kombination von beiden, da Bounding-Box-Tests ein schneller erster Durchgang sind.
Asche999
1
Die Kollisionserkennung hängt davon ab, ob Sie 3D / 2D-Modelle oder Sprites verwenden. Man hat Pixel. Der andere hat Ecken und Kanten.
Doppelgreener
Okay, ich verstehe, worauf du jetzt hinaus willst. Ich benutze Sprites. Obwohl ich glaube, dass XNA sie als strukturierte Polygone implementiert.
Asche999

Antworten:

22

Ich sehe, dass Sie die Frage als 2d markiert haben, also werde ich fortfahren und meinen Code ausgeben:

class Sprite {

    [...]

    public bool CollidesWith(Sprite other)
    {
        // Default behavior uses per-pixel collision detection
        return CollidesWith(other, true);
    }

    public bool CollidesWith(Sprite other, bool calcPerPixel)
    {
        // Get dimensions of texture
        int widthOther = other.Texture.Width;
        int heightOther = other.Texture.Height;
        int widthMe = Texture.Width;
        int heightMe = Texture.Height;

        if ( calcPerPixel &&                                // if we need per pixel
            (( Math.Min(widthOther, heightOther) > 100) ||  // at least avoid doing it
            ( Math.Min(widthMe, heightMe) > 100)))          // for small sizes (nobody will notice :P)
        {
            return Bounds.Intersects(other.Bounds) // If simple intersection fails, don't even bother with per-pixel
                && PerPixelCollision(this, other);
        }

        return Bounds.Intersects(other.Bounds);
    }

    static bool PerPixelCollision(Sprite a, Sprite b)
    {
        // Get Color data of each Texture
        Color[] bitsA = new Color[a.Texture.Width * a.Texture.Height];
        a.Texture.GetData(bitsA);
        Color[] bitsB = new Color[b.Texture.Width * b.Texture.Height];
        b.Texture.GetData(bitsB);

        // Calculate the intersecting rectangle
        int x1 = Math.Max(a.Bounds.X, b.Bounds.X);
        int x2 = Math.Min(a.Bounds.X + a.Bounds.Width, b.Bounds.X + b.Bounds.Width);

        int y1 = Math.Max(a.Bounds.Y, b.Bounds.Y);
        int y2 = Math.Min(a.Bounds.Y + a.Bounds.Height, b.Bounds.Y + b.Bounds.Height);

         // For each single pixel in the intersecting rectangle
         for (int y = y1; y < y2; ++y)
         {
             for (int x = x1; x < x2; ++x)
             {
                 // Get the color from each texture
                 Color a = bitsA[(x - a.Bounds.X) + (y - a.Bounds.Y)*a.Texture.Width];
                 Color b = bitsB[(x - b.Bounds.X) + (y - b.Bounds.Y)*b.Texture.Width];

                 if (a.A != 0 && b.A != 0) // If both colors are not transparent (the alpha channel is not 0), then there is a collision
                 {
                     return true;
                 }
             }
         }
        // If no collision occurred by now, we're clear.
        return false;
    }

    private Rectangle bounds = Rectangle.Empty;
    public virtual Rectangle Bounds
    {
        get
        {
            return new Rectangle(
                (int)Position.X - Texture.Width,
                (int)Position.Y - Texture.Height,
                Texture.Width,
                Texture.Height);
        }

    }

Bearbeiten : Während dieser Code fast selbsterklärend ist, habe ich mich schlecht gefühlt, weil ich keine Kommentare hatte, also habe ich einige hinzugefügt;) Ich habe auch die bitweisen Operationen entfernt, da sie im Grunde das tun, was die Color.A-Eigenschaft auf kompliziertere Weise tut ;)

pek
quelle
Stimmen Sie für einen Code-Dump ab, ohne Kommentare oder Erklärungen.
AttackingHobo
12
Jede Erklärung wäre nur, den Code neu zu formulieren, und der Code ist ziemlich einfach.
2
Ich weiß, dass ich das in meiner Frage erwähnt habe, aber ich muss es noch einmal sagen: Handelt es sich um Skalierung und Rotation? Können sich zwei skalierte, gedrehte Sprites korrekt schneiden? (Ich kann zwar einen schnellen Bounding-Box-First-Pass hinzufügen.) Oder ist dies mit Anrufen von abgedeckt Bounds?
Asche999
1
Ja, das habe ich vergessen! Der Code berücksichtigt keine Transformation. Im Moment habe ich nicht die Zeit zu bearbeiten, aber Sie können einen Blick auf die Kollision Transformed Probe haben , bis ich tun: create.msdn.com/en-US/education/catalog/tutorial/...
pek
1
Nur umständlich sein, aber Sie brauchen nur: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell
4

Auf dem App Hub gibt es ein sehr altes Beispiel, das Sie durch die 2D-Kollisionserkennung von einfachen Begrenzungsrahmen bis hin zu pixelgetesteten Sprites mit gedrehten und skalierten Sprites führt. Es wurde vollständig auf 4.0 aktualisiert. Die ganze Serie ist eine Lektüre wert, wenn Sie mit dem Thema noch nicht vertraut sind.

http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed

Ich fand Riemer Grootjans Ansatz auch in seinem 2D-Shooter-Spiel interessant. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Es dauert eine Weile, bis er dahin kommt ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... aber vielleicht möchten Sie mitmachen, um das zu sehen Problem, das er löst)

Ich warne Sie jedoch davor, dass Riemers Sample nicht XNA 4.0 ist und Sie möglicherweise ein wenig arbeiten müssen, um es zum Laufen zu bringen. Es ist jedoch keine schwierige oder mysteriöse Arbeit.

Chris Gomez
quelle
Tolle Links, aber alte Links; Ich habe diese bereits für meine Lösung verwendet.
Asche999
1
Genial. Ich denke nur, wenn jemand sucht, kann er Ihre Frage finden und hat mehr Ressourcen.
Chris Gomez
0

Ich empfehle, eine Schwarz-Weiß-Kollisionskarte zu erstellen. Programmieren Sie es so, dass die schwarzen Pixel Kollisionspunkte sind. Geben Sie dem Charakter auch eine Kollisionskarte. Verwenden Sie bei der Verarbeitung von Karten einen Algorithmus, der große Quadrate schwarzer Pixel in Kollisionsrechtecke umwandelt. Speichern Sie diese Rechteckdaten in einem Array. Sie können die Funktion Rechteck-Schnittpunkte verwenden, um nach Kollisionen zu suchen. Sie können die Kollisionskarte auch mit der Textur transformieren.

Dies ähnelt der Verwendung einer Kollisionsmatrix, ist jedoch fortgeschrittener und Sie können sie transformieren! Überlegen Sie, ob Sie ein Tool zur Erstellung von Kollisionskarten erstellen möchten, wenn Sie es benötigen. Wenn Sie es schaffen, teilen Sie den Code bitte mit anderen!

David Markarian
quelle