Czy istnieje dobry sposób na uzyskanie detekcji kolizji w pikselach w XNA?

12

Czy istnieje dobrze znany sposób (lub być może fragment kodu wielokrotnego użytku) do wykrywania kolizji w pikselach w XNA?

Zakładam, że użyłoby to również wielokątów (pól / trójkątów / kół) do pierwszego przejścia, szybkiego testu na kolizje, a jeśli test ten wskazywałby na kolizję, wówczas szukałby kolizji na piksel.

Może to być skomplikowane, ponieważ musimy uwzględnić skalę, rotację i przejrzystość.

OSTRZEŻENIE: Jeśli używasz przykładowego kodu z linku z poniższej odpowiedzi, pamiętaj, że skalowanie macierzy zostało skomentowane z uzasadnionego powodu. Nie musisz odkomentować, aby skalowanie działało.

ashes999
źródło
2
Wieloboki czy duszki?
doppelgreener
Nie jestem pewny. Xna obsługuje oba? Moja edycja wymienia kombinację obu, ponieważ testy ograniczające są szybkim pierwszym przejściem.
ashes999
1
Wykrywanie kolizji będzie się różnić w zależności od tego, czy używasz modeli 3D / 2D, czy duszków. Jeden ma piksele. Drugi ma wierzchołki i krawędzie.
doppelgreener
Dobra, rozumiem o co teraz chodzi. Używam duszków. Chociaż uważam, że XNA implementuje je jako teksturowane wielokąty.
ashes999

Odpowiedzi:

22

Widzę, że otagowałeś pytanie jako 2d, więc zacznę i zrzucę mój kod:

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

    }

Edycja : Chociaż ten kod jest prawie oczywisty, nie czułem się dobrze z powodu braku komentarzy, więc dodałem trochę;) Pozbyłem się również operacji bitowych, ponieważ robiło to w zasadzie to, co robi właściwość Color.A w bardziej skomplikowany sposób ;)

pek
źródło
Głosuj w dół na zrzut kodu bez komentarzy i wyjaśnień.
AttackingHobo
12
Każde wyjaśnienie byłoby po prostu przekształceniem kodu, a kod jest dość prosty.
2
Wiem, że wspomniałem o tym w moim pytaniu, ale muszę to powtórzyć: czy to obsługuje skalowanie i rotację? Czy dwa skalowane, obrócone duszki mogą poprawnie się przecinać? (Mogę jednak poradzić sobie z dodaniem pierwszego przejścia szybkiego obwiedni). Czy może to obejmować połączenia z Bounds?
ashes999
1
Tak, zapomniałem o tym! Kod nie uwzględnia transformacji. W tej chwili nie mam czasu na edycję, ale możesz rzucić
pek
1
Po prostu pedantyczny, ale potrzebujesz tylko: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Jonathan Connell,
4

W App Hub znajduje się bardzo stara próbka, która prowadzi Cię przez wykrywanie kolizji 2D od prostych ramek ograniczających do testowania pikseli na obróconych i skalowanych duszkach. Został w pełni zaktualizowany do wersji 4.0. Cała seria jest warta przeczytania, jeśli jesteś nowy w temacie.

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

Zauważyłem też, że Riemer Grootjans jest interesujący w swojej strzelance 2D. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Zajęło mu to trochę czasu, aby się do niego dostać ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... ale możesz śledzić, aby zobaczyć problem, który rozwiązuje)

Ale ostrzegam cię, że próbka Riemers nie jest XNA 4.0 i być może będziesz musiał trochę popracować, aby ją uruchomić. Nie jest to jednak praca trudna ani tajemnicza.

Chris Gomez
źródło
Świetne linki, ale stare linki; Użyłem ich już do mojego rozwiązania.
ashes999
1
Niesamowite. Po prostu myślę, że kiedy ktoś szuka, może znaleźć twoje pytanie i będzie miał więcej zasobów.
Chris Gomez
0

Polecam utworzenie czarno-białej mapy kolizji. Zaprogramuj go tak, aby czarne piksele były punktami kolizji. Nadaj postaci również mapę kolizji; Podczas przetwarzania map użyj algorytmu, który zamieni duże kwadraty czarnych pikseli w prostokąty kolizyjne. Zapisz dane prostokąta w tablicy. Możesz użyć funkcji Przecięcia prostokąta, aby wyszukać kolizje. Możesz także przekształcić mapę kolizji za pomocą tekstury.

Jest to bardzo podobne do korzystania z macierzy kolizji, ale jest bardziej zaawansowane i można ją przekształcić! rozważ zbudowanie narzędzia do generowania mapy kolizji, jeśli go potrzebujesz. Jeśli sprawisz, że zadziała, udostępnij kod innym osobom!

David Markarian
źródło