¿Hay una buena manera de obtener una detección de colisión con píxeles perfectos en XNA?

12

¿Existe una forma conocida (o quizás un bit de código reutilizable) para la detección de colisión con píxeles perfectos en XNA?

Supongo que esto también usaría polígonos (cajas / triángulos / círculos) para una prueba rápida de colisión de primer paso, y si esa prueba indica una colisión, entonces buscaría una colisión por píxel.

Esto puede ser complicado, porque tenemos que tener en cuenta la escala, la rotación y la transparencia.

ADVERTENCIA: Si está utilizando el código de muestra del enlace de la respuesta a continuación, tenga en cuenta que la escala de la matriz se comenta por una buena razón. No necesita descomentarlo para que la escala funcione.

cenizas999
fuente
2
Polígonos o sprites?
doppelgreener
No estoy seguro. Xna es compatible con ambos? Mi edición menciona una combinación de ambos, ya que las pruebas de cuadro delimitador son un primer paso rápido.
cenizas999
1
La detección de colisión diferirá dependiendo de si está utilizando modelos 3D / 2D o sprites. Uno tiene píxeles. El otro tiene vértices y aristas.
doppelgreener
De acuerdo, veo a qué te refieres ahora. Estoy usando sprites. Aunque creo que XNA los implementa como polígonos texturizados.
cenizas999

Respuestas:

22

Veo que etiquetaste la pregunta como 2d, así que continuaré y volcaré mi código:

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

    }

Editar : Si bien este código se explica casi por sí mismo, me sentí mal por no tener comentarios, así que agregué algunos;) También me deshice de las operaciones bit a bit ya que estaba haciendo básicamente lo que hace la propiedad Color.A de una manera más complicada ;)

pek
fuente
Voto negativo para un volcado de código sin comentarios ni explicación.
AttackingHobo
12
Cualquier explicación sería simplemente restablecer el código, y el código es bastante sencillo.
2
Sé que mencioné esto en mi pregunta, pero necesito decirlo nuevamente: ¿esto maneja la escala y la rotación? ¿Pueden dos sprites rotados y escalados cortarse correctamente? (Sin embargo, puedo manejar la adición de un primer paso rápido del cuadro delimitador). ¿O está cubierto con llamadas a Bounds?
cenizas999
1
¡Sí, me olvidé de eso! El código no tiene en cuenta la transformación. En este momento no tengo tiempo para editar, pero puedes echar un vistazo a la muestra Transformada por colisión hasta que lo haga: create.msdn.com/en-US/education/catalog/tutorial/…
pek
1
Solo siendo pedante, pero solo necesitas: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell
4

En App Hub, hay una muestra muy antigua que lo guía a través de la detección de colisión en 2D, desde simples cuadros delimitadores hasta pruebas de píxeles en sprites rotados y escalados. Se ha actualizado completamente a 4.0. Vale la pena leer toda la serie si eres nuevo en el tema.

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

También encontré interesante el enfoque de Riemer Grootjans en su juego de disparos en 2D. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Le lleva un poco de tiempo llegar a él ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... pero es posible que desee seguirlo para ver el problema que está resolviendo)

Pero le advierto que la muestra de Riemers no es XNA 4.0 y puede que tenga que hacer un pequeño trabajo para que funcione. Sin embargo, no es un trabajo difícil o misterioso.

Chris Gomez
fuente
Grandes enlaces, pero viejos enlaces; Ya los he usado para mi solución.
cenizas999
1
Increíble. Me imagino que cuando alguien busca puede encontrar su pregunta y tendrá más recursos.
Chris Gomez
0

Recomiendo crear un mapa de colisión en blanco y negro. Prográmelo para que los píxeles negros sean puntos de colisión. Dale al personaje un mapa de colisión también; Al procesar mapas, use un algoritmo que convierta grandes cuadrados de píxeles negros en rectángulos de colisión. Guarde los datos de este rectángulo en una matriz. Puede usar la función Rectángulo intersecta para buscar colisiones. También puede transformar el mapa de colisión con la textura.

¡Esto es muy parecido a usar una matriz de colisión pero es más avanzado y puedes transformarlo! considere construir una herramienta generadora de mapas de colisión si la necesita. Si lo hace funcionar, ¡comparta el código con otros!

David Markarian
fuente