Apakah ada cara yang baik untuk mendapatkan deteksi tabrakan pixel-sempurna di XNA?

12

Apakah ada cara yang terkenal (atau mungkin sedikit kode yang dapat digunakan kembali) untuk deteksi tabrakan pixel-sempurna di XNA?

Saya berasumsi ini juga akan menggunakan poligon (kotak / segitiga / lingkaran) untuk lulus pertama, tes cepat untuk tabrakan, dan jika tes itu menunjukkan tabrakan, maka akan mencari tabrakan per-pixel.

Ini bisa rumit, karena kita harus memperhitungkan skala, rotasi, dan transparansi.

PERINGATAN: Jika Anda menggunakan kode sampel dari tautan dari jawaban di bawah ini, ketahuilah bahwa penskalaan matriks dikomentari untuk alasan yang baik. Anda tidak perlu menghapus tanda komentar untuk mendapatkan skala untuk bekerja.

ashes999
sumber
2
Poligon atau sprite?
doppelgreener
Saya tidak yakin. Xna mendukung keduanya? Hasil edit saya menyebutkan kombinasi keduanya, karena tes kotak pembatas adalah langkah cepat pertama.
ashes999
1
Deteksi tabrakan akan berbeda tergantung pada apakah Anda menggunakan model 3D / 2D atau sprite. Seseorang memiliki piksel. Yang lain memiliki simpul dan tepi.
doppelgreener
Oke, saya mengerti apa yang Anda dapatkan sekarang. Saya menggunakan sprite. Meskipun saya percaya XNA mengimplementasikannya sebagai poligon bertekstur.
ashes999

Jawaban:

22

Saya melihat bahwa Anda menandai pertanyaan sebagai 2d, jadi saya akan melanjutkan dan membuang kode saya:

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

    }

Sunting : Walaupun kode ini hampir jelas, saya merasa tidak enak karena tidak memiliki komentar, jadi saya menambahkan beberapa;) Saya juga menyingkirkan operasi bitwise karena pada dasarnya melakukan apa yang properti Color.A lakukan dengan cara yang lebih rumit ;)

pek
sumber
Turunkan suara untuk dump kode tanpa komentar atau penjelasan.
AttackingHobo
12
Penjelasan apa pun hanya akan menyatakan ulang kode, dan kode ini cukup mudah.
2
Saya tahu saya menyebutkan ini dalam pertanyaan saya, tetapi saya perlu menyatakannya lagi: apakah ini menangani penskalaan dan rotasi? Dapatkah dua sprite berskala dan diputar dengan benar berpotongan? (Aku bisa menangani menambahkan lulus cepat-kotak pertama cepat.) Atau apakah ini ditutupi dengan panggilan Bounds?
ashes999
1
Ya, saya lupa tentang itu! Kode tidak mempertimbangkan transformasi. Saat ini saya tidak punya waktu untuk mengedit, tetapi Anda dapat melihat sampel Collision Transformed sampai saya melakukannya: create.msdn.com/en-US/education/catalog/tutorial/…
pek
1
Hanya menjadi sombong, tetapi Anda hanya perlu: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell
4

Di App Hub, ada sampel sangat lama yang memandu Anda melalui deteksi tabrakan 2D dari kotak ikatan sederhana hingga pengujian piksel pada sprite yang diputar dan diskalakan. Telah sepenuhnya diperbarui ke 4.0. Seluruh seri ini layak dibaca jika Anda baru dalam topik ini.

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

Saya juga menemukan pendekatan Riemer Grootjans menarik dalam game penembak 2D-nya. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Butuh waktu agak lama baginya untuk sampai ke sana ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... tetapi Anda mungkin ingin mengikuti untuk melihat masalah dia memecahkan)

Tapi saya memperingatkan Anda bahwa sampel Riemers bukan XNA 4.0 dan Anda mungkin harus melakukan sedikit pekerjaan untuk membuatnya berjalan. Namun itu bukan pekerjaan yang sulit atau misterius.

Chris Gomez
sumber
Tautan bagus, tetapi tautan lama; Saya sudah menggunakan ini untuk solusi saya.
ashes999
1
Luar biasa. Saya baru tahu ketika seseorang mencari mereka dapat menemukan pertanyaan Anda dan mereka akan memiliki lebih banyak sumber daya.
Chris Gomez
0

Saya merekomendasikan membuat peta tabrakan hitam dan putih. Memprogramnya sehingga piksel hitam adalah titik tabrakan. Berikan karakter tabrakan peta juga; Saat memproses peta, gunakan algoritma yang akan mengubah kotak besar piksel hitam menjadi persegi panjang tabrakan. Simpan data persegi panjang ini dalam sebuah array. Anda dapat menggunakan fungsi berpotongan Rectangle untuk mencari tabrakan. Anda juga dapat mengubah peta tabrakan dengan tekstur.

Ini sangat mirip dengan menggunakan tabrakan matriks tetapi lebih maju dan Anda dapat mengubahnya! pertimbangkan membangun alat penghasil peta tabrakan jika Anda membutuhkannya. Jika Anda membuatnya berfungsi, silakan bagikan kodenya dengan yang lain!

David Markarian
sumber