C'è un buon modo per ottenere un rilevamento delle collisioni perfetto per i pixel in XNA?


12

Esiste un modo ben noto (o forse un po 'di codice riutilizzabile) per il rilevamento delle collisioni perfetto per i pixel in XNA?

Suppongo che questo userebbe anche poligoni (scatole / triangoli / cerchi) per un primo passaggio, test rapido per le collisioni, e se quel test indicava una collisione, avrebbe quindi cercato una collisione per pixel.

Questo può essere complicato, perché dobbiamo tenere conto di scala, rotazione e trasparenza.

ATTENZIONE: se si utilizza il codice di esempio dal collegamento dalla risposta seguente, tenere presente che il ridimensionamento della matrice è commentato per una buona ragione. Non è necessario decomprimerlo per far funzionare il ridimensionamento.


2
Poligoni o sprite?
doppelgreener,

Non ne sono sicuro. Xna supporta entrambi? La mia modifica menziona una combinazione di entrambi, poiché i test del riquadro di selezione sono un primo passaggio rapido.
ashes999,

1
Il rilevamento delle collisioni varierà a seconda che si stiano utilizzando modelli 3D / 2D o sprite. Uno ha pixel. L'altro ha vertici e bordi.
doppelgreener,

Ok, vedo cosa stai ricevendo ora. Sto usando gli sprite. Anche se credo che XNA li implementi come poligoni strutturati.
ashes999,

Risposte:


22

Vedo che hai taggato la domanda come 2d, quindi andrò avanti e scaricherò il mio codice:

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

    }

Modifica : Sebbene questo codice sia quasi autoesplicativo, mi sono sentito male per non avere commenti, quindi ne ho aggiunti alcuni;) Mi sono anche liberato delle operazioni bit per bit dato che stava praticamente facendo quello che la proprietà Color.A fa in un modo più complicato ;)


Votazione negativa per un dump del codice senza commenti o spiegazioni.
Attaccando

12
Qualsiasi spiegazione sarebbe semplicemente riaffermare il codice, e il codice è abbastanza semplice.

2
So di averlo menzionato nella mia domanda, ma devo dichiararlo di nuovo: questo gestisce il ridimensionamento e la rotazione? Due sprite in scala e ruotati possono intersecarsi correttamente? (Riesco a gestire l'aggiunta di un primo passaggio rapido del riquadro di selezione.) O è coperto da chiamate a Bounds?
ashes999,

1
Sì, me ne sono dimenticato! Il codice non prende in considerazione la trasformazione. In questo momento non ho il tempo di modificare, ma puoi dare un'occhiata all'esempio
pek,

1
Basta essere pedanti, ma hai solo bisogno di: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell

4

Sull'hub app, c'è un esempio molto vecchio che ti guida attraverso il rilevamento di collisioni 2D da semplici box di delimitazione a pixel testati su sprite ruotati e ridimensionati. È stato completamente aggiornato alla 4.0. L'intera serie merita una lettura se sei nuovo sull'argomento.

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

Ho anche trovato interessante l'approccio di Riemer Grootjans nel suo gioco sparatutto in 2D. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Gli ci vuole un po 'di tempo per arrivarci ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... ma potresti voler seguire per vedere il problema che sta risolvendo)

Ma ti avverto che il campione di Riemers non è XNA 4.0 e potresti dover fare un po 'di lavoro per farlo funzionare. Non è un lavoro difficile o misterioso, tuttavia.


Grandi collegamenti, ma vecchi collegamenti; Li ho già usati per la mia soluzione.
ashes999,

1
Eccezionale. Immagino solo quando qualcuno cerca di trovare la tua domanda e avranno più risorse.
Chris Gomez,

0

Consiglio di creare una mappa di collisione in bianco e nero. Programmalo in modo che i pixel neri siano punti di collisione. Dai anche al personaggio una mappa delle collisioni; Durante l'elaborazione delle mappe, utilizzare un algoritmo che trasformerà grandi quadrati di pixel neri in rettangoli di collisione. Salva i dati di questo rettangolo in un array. È possibile utilizzare la funzione Intersezioni rettangolo per cercare collisioni. Puoi anche trasformare la mappa delle collisioni con la trama.

È un po 'come usare una matrice di collisione ma più avanzata e puoi trasformarla! prendere in considerazione la creazione di uno strumento generatore di mappe di collisione se necessario Se lo fai funzionare, ti preghiamo di condividere il codice con gli altri!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.