Existe-t-il un bon moyen d'obtenir une détection de collision au pixel près dans XNA?

12

Existe-t-il un moyen bien connu (ou peut-être un morceau de code réutilisable) pour la détection de collision au pixel près dans XNA?

Je suppose que cela utiliserait également des polygones (boîtes / triangles / cercles) pour un premier test rapide pour les collisions, et si ce test indiquait une collision, il rechercherait alors une collision par pixel.

Cela peut être compliqué, car nous devons tenir compte de l'échelle, de la rotation et de la transparence.

AVERTISSEMENT: si vous utilisez l'exemple de code du lien de la réponse ci-dessous, sachez que la mise à l'échelle de la matrice est commentée pour une bonne raison. Vous n'avez pas besoin de le décommenter pour que la mise à l'échelle fonctionne.

cendres999
la source
2
Des polygones ou des sprites?
doppelgreener
Je ne suis pas sûr. Xna prend en charge les deux? Ma modification mentionne une combinaison des deux, car les tests de la boîte englobante sont un premier passage rapide.
ashes999
1
La détection de collision diffère selon que vous utilisez des modèles 3D / 2D ou des sprites. L'un a des pixels. L'autre a des sommets et des arêtes.
doppelgreener
D'accord, je vois où tu veux en venir. J'utilise des sprites. Bien que je crois que XNA les implémente comme des polygones texturés.
ashes999

Réponses:

22

Je vois que vous avez marqué la question comme 2d, donc je vais continuer et vider mon code:

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

    }

Edit : Bien que ce code soit presque explicite, je me sentais mal de ne pas avoir de commentaires, alors j'en ai ajouté;) Je me suis également débarrassé des opérations au niveau du bit car il faisait essentiellement ce que la propriété Color.A fait de manière plus compliquée ;)

pek
la source
Votez pour un vidage de code sans commentaire ni explication.
AttackingHobo
12
Toute explication serait simplement de reformuler le code, et le code est assez simple.
2
Je sais que je l'ai mentionné dans ma question, mais je dois le répéter: cela gère-t-il la mise à l'échelle et la rotation? Deux sprites à l'échelle et en rotation peuvent-ils se croiser correctement? (Je peux gérer l'ajout d'une première passe de boîte de délimitation rapide cependant.) Ou est-ce couvert par des appels à Bounds?
ashes999
1
Oui, j'ai oublié ça! Le code ne prend pas en compte la transformation. Pour le moment, je n'ai pas le temps de modifier, mais vous pouvez jeter un œil à l'exemple Collision Transformed jusqu'à ce que je le fasse: create.msdn.com/en-US/education/catalog/tutorial/…
pek
1
Être pédant, mais il vous suffit: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell
4

Sur l'App Hub, il existe un très ancien exemple qui vous guide à travers la détection de collision 2D, des simples boîtes englobantes aux pixels testés sur les sprites tournés et mis à l'échelle. Il a été entièrement mis à jour vers 4.0. Toute la série mérite d'être lue si vous êtes nouveau sur le sujet.

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

J'ai aussi trouvé l'approche de Riemer Grootjans intéressante dans son jeu de tir 2D. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Il lui faut un peu de temps pour y arriver ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... mais vous voudrez peut-être suivre pour voir le problème qu'il résout)

Mais je vous préviens que l'exemple de Riemers n'est pas XNA 4.0 et vous devrez peut-être faire un peu de travail pour le faire fonctionner. Ce n'est cependant pas un travail difficile ou mystérieux.

Chris Gomez
la source
Grands liens, mais anciens liens; Je les ai déjà utilisés pour ma solution.
ashes999
1
Impressionnant. Je pense que quand quelqu'un cherche, il peut trouver votre question et il aura plus de ressources.
Chris Gomez
0

Je recommande de créer une carte de collision en noir et blanc. Programmez-le de sorte que les pixels noirs soient des points de collision. Donnez également au personnage une carte de collision; Lors du traitement des cartes, utilisez un algorithme qui transformera de grands carrés de pixels noirs en rectangles de collision. Enregistrez ces données rectangulaires dans un tableau. Vous pouvez utiliser la fonction Rectangle intersecte pour rechercher les collisions. Vous pouvez également transformer la carte de collision avec la texture.

C'est un peu comme utiliser une matrice de collision mais plus avancée et vous pouvez la transformer! envisagez de créer un outil générateur de carte de collision si vous en avez besoin. Si vous le faites fonctionner, veuillez partager le code avec d'autres!

David Markarian
la source