Existe uma boa maneira de obter uma detecção de colisão perfeita em pixels no XNA?

12

Existe uma maneira bem conhecida (ou talvez reutilizável de código) para a detecção de colisão perfeita em pixels no XNA?

Suponho que isso também usaria polígonos (caixas / triângulos / círculos) para um teste rápido de primeira passagem para colisões e, se esse teste indicasse uma colisão, procuraria uma colisão por pixel.

Isso pode ser complicado, porque temos que levar em conta escala, rotação e transparência.

AVISO: Se você estiver usando o código de amostra do link da resposta abaixo, saiba que o dimensionamento da matriz está comentado por um bom motivo. Você não precisa remover o comentário para que o dimensionamento funcione.

ashes999
fonte
2
Polígonos ou sprites?
22411 doppelgreener
Não tenho certeza. Xna suporta ambos? Minha edição menciona uma combinação de ambos, já que os testes da caixa delimitadora são rápidos na primeira passagem.
ashes999
1
A detecção de colisão será diferente dependendo de você estar usando modelos 3D / 2D ou sprites. Um tem pixels. O outro possui vértices e arestas.
22411 doppelgreener
Ok, vejo o que você está recebendo agora. Estou usando sprites. Embora eu acredite que o XNA os implemente como polígonos texturizados.
ashes999

Respostas:

22

Vejo que você marcou a pergunta como 2D, então vou adiante e despejo meu 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);
        }

    }

Edit : Embora esse código seja quase auto-explicativo, eu me senti mal por não ter comentários, então adicionei alguns;) Eu também me livrei das operações bit a bit, pois fazia basicamente o que a propriedade Color.A faz de uma maneira mais complicada. ;)

pek
fonte
Faça o voto negativo para um despejo de código sem comentários ou explicações.
AttackingHobo
12
Qualquer explicação seria apenas reafirmar o código, e o código é bastante direto.
2
Sei que mencionei isso na minha pergunta, mas preciso declarar novamente: isso lida com o dimensionamento e a rotação? Dois sprites em escala e rotacionados podem se cruzar corretamente? (Eu posso lidar com a adição de uma primeira passagem rápida na caixa delimitadora.) Ou isso é coberto com chamadas para Bounds?
ashes999
1
Sim, eu esqueci disso! O código não leva em consideração a transformação. No momento não tenho tempo para editar, mas você pode dar uma olhada no exemplo Collision Transformed até que eu faça: create.msdn.com/en-US/education/catalog/tutorial/…
pek
1
Basta ser pedante, mas você só precisa: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell
4

No App Hub, há uma amostra muito antiga que mostra a detecção de colisão 2D, desde caixas delimitadoras simples até testes de pixel em sprites rotacionados e em escala. Foi totalmente atualizado para 4.0. A série inteira vale a pena ser lida se você é novo no tópico.

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

Também achei a abordagem de Riemer Grootjans interessante em seu jogo de tiro em 2D. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Ele demora um pouco para chegar a ele ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... mas você pode acompanhar o vídeo para ver problema que ele está resolvendo)

Mas aviso que a amostra do Riemers não é o XNA 4.0 e talvez você precise fazer um pouco de trabalho para executá-la. No entanto, não é um trabalho difícil ou misterioso.

Chris Gomez
fonte
Ótimos links, mas links antigos; Eu já os usei para a minha solução.
ashes999
1
Impressionante. Imagino que quando alguém pesquisar, eles poderão encontrar sua pergunta e terão mais recursos.
Chris Gomez
0

Eu recomendo criar um mapa de colisão em preto e branco. Programe para que os pixels pretos sejam pontos de colisão. Dê ao personagem um mapa de colisão também; Ao processar mapas, use um algoritmo que transformará grandes quadrados de pixels pretos em retângulos de colisão. Salve esses dados do retângulo em uma matriz. Você pode usar a função de interseção de retângulo para procurar colisões. Você também pode transformar o mapa de colisão com a textura.

É muito parecido com o uso de uma matriz de colisão, mas é mais avançado e você pode transformá-la! considere criar uma ferramenta geradora de mapa de colisão, se necessário. Se você fizer isso funcionar, compartilhe o código com outras pessoas!

David Markarian
fonte