Чи є хороший спосіб отримати ідеальне піксельне виявлення зіткнень у XNA?


12

Чи існує добре відомий спосіб (або, можливо, багаторазовий біт коду) для виявлення зіткнень, ідеальних для пікселів у XNA?

Я припускаю, що це також використовує багатокутники (коробки / трикутники / кола) для першого проходу, швидкого тесту на зіткнення, і якщо цей тест вказував на зіткнення, то він би шукав зіткнення за пікселем.

Це може бути складним, оскільки ми повинні враховувати масштаб, обертання та прозорість.

ПОПЕРЕДЖЕННЯ. Якщо ви використовуєте зразок коду за посиланням з наведеної нижче відповіді, майте на увазі, що масштабування матриці коментується з поважної причини. Вам не потрібно коментувати це, щоб розпочати масштабування.


2
Полігони чи спрати?
doppelgreener

Я не впевнений. Xna підтримує обох? У моїй редакції згадується комбінація обох, оскільки тести з обмеженою коробкою - це швидкий перший пропуск.
ashes999

1
Виявлення зіткнення буде відрізнятися залежно від того, використовуєте ви 3D / 2D моделі або спрайти. Один має пікселі. Інший має вершини та ребра.
doppelgreener

Гаразд, я бачу, що ти зараз отримуєш. Я використовую спрайт. Хоча я вважаю, що XNA реалізує їх як текстуровані багатокутники.
ashes999

Відповіді:


22

Я бачу, що ви позначили це питання як 2d, тому я продовжую і скидаю код:

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

    }

Редагувати : Хоча цей код майже не пояснює себе, я почував себе погано за те, що не мав коментарів, тому я додав кілька;) Я також позбувся від побітних операцій, оскільки він в основному робив те, що властивість Color.A робить більш складний спосіб ;)


Проголосуйте за демпфірування коду без коментарів чи пояснень.
AttackingHobo

12
Будь-яке пояснення було б просто перезавантаженням коду, і код є досить простим.

2
Я знаю, що я згадував про це у своєму запитанні, але мені потрібно це ще раз зазначити: чи це ручка масштабування та обертання? Чи можуть два масштабовані обертові спрати правильно перетинатися? (Я можу впоратися із додаванням першого швидкого обмежувального вікна першого пропуску.) Або це покрито дзвінками Bounds?
ashes999

1
Так, я про це забув! Код не враховує перетворення. Зараз у мене немає часу для редагування, але ви можете подивитися на зіткнення трансформованого зразка , поки я не зроблю: create.msdn.com/en-US/education/catalog/tutorial / ...
Пекін

1
Просто бути педантичним, але вам потрібно лише: CollidesWith(Sprite other, bool calcPerPixel = true);:)
Джонатан Коннелл

4

На App Hub є дуже старий зразок, який проводить вас через 2D виявлення зіткнень від простих обмежувальних коробок до тестування пікселів на обертових та масштабованих спрайтах. Він повністю оновлений до 4.0. Цілу серію варто прочитати, якщо ви новачок у цій темі.

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

Я також виявив, що підхід Рімера Гротджанса цікавий у його грі 2D шутери. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Потрібно йому трохи часу, щоб дістатися до нього ... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... але ви, можливо, захочете пройти далі, щоб побачити проблему, яку він вирішує)

Але я застерігаю, що зразок Riemers не є XNA 4.0, і вам, можливо, доведеться зробити трохи роботи, щоб запустити його. Однак це не складна чи загадкова робота.


Чудові посилання, але старі посилання; Я вже використовував їх для свого рішення.
ashes999

1
Дивовижно. Я просто рахую, коли хтось шукає, він може знайти ваше запитання, і у них буде більше ресурсів.
Кріс Гомес

0

Я рекомендую створити чорно-білу карту зіткнень. Програмуйте його так, щоб чорні пікселі були точками зіткнення. Надайте персонажу карту зіткнення також; Обробляючи карти, використовуйте алгоритм, який перетворить великі квадрати чорних пікселів у прямокутники зіткнення. Збережіть ці дані прямокутника в масиві. Для пошуку зіткнень можна використовувати функцію Rectangle intersects. Ви також можете перетворити карту зіткнення з текстурою.

Це дуже схоже на використання матриці зіткнення, але більш досконалою, і ви можете її перетворити! розглянути можливість створення інструменту генератора карт зіткнення, якщо він вам потрібен. Якщо ви працюєте, будь ласка, поділіться кодом з іншими!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.