Есть ли хороший способ получить пиксельное обнаружение столкновений в XNA?

12

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

Я предполагаю, что это также использовало бы полигоны (прямоугольники / треугольники / круги) для первого прохода, быстрого теста на столкновения, и если этот тест показал столкновение, то тогда он будет искать столкновение на пиксель.

Это может быть сложно, потому что мы должны учитывать масштаб, вращение и прозрачность.

ВНИМАНИЕ: Если вы используете пример кода по ссылке из ответа ниже, помните, что масштабирование матрицы закомментировано по уважительной причине. Вам не нужно раскомментировать его, чтобы масштабирование работало.

ashes999
источник
2
Полигоны или спрайты?
двойник
Я не уверен. Xna поддерживает оба? Мое редактирование упоминает комбинацию обоих, так как тесты ограничивающего прямоугольника - быстрый первый проход.
ashes999
1
Обнаружение столкновений будет отличаться в зависимости от того, используете ли вы 3D / 2D модели или спрайты. У одного есть пиксели. Другой имеет вершины и ребра.
двойник
Хорошо, я вижу, что вы получаете сейчас. Я использую спрайты. Хотя я считаю, что 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
Да, я забыл об этом! Код не учитывает преобразование. Сейчас у меня нет времени на редактирование, но вы можете взглянуть на образец Collision Transformed, пока я не сделаю: create.msdn.com/en-US/education/catalog/tutorial/…
pek
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

Я также нашел подход Riemer Grootjans интересным в его игре 2D-шутеры. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(Ему требуется некоторое время, чтобы добраться до него ... http://www.riemers.net/rus/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... но вы можете проследить, чтобы увидеть проблему он решает)

Но я предупреждаю вас, что образец Riemers не является XNA 4.0, и вам, возможно, придется немного поработать, чтобы запустить его. Однако это не сложная или таинственная работа.

Крис Гомес
источник
Большие ссылки, но старые ссылки; Я уже использовал их для своего решения.
ashes999
1
Потрясающие. Я просто думаю, что когда кто-то ищет, он может найти ваш вопрос, и у него будет больше ресурсов.
Крис Гомес
0

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

Это очень похоже на использование матрицы столкновений, но более продвинутый, и вы можете преобразовать его! подумайте о создании инструмента генератора карт столкновений, если он вам нужен. Если вы сделаете это, пожалуйста, поделитесь кодом с другими!

Дэвид Маркарян
источник