Пиксельно-совершенные столкновения в Monogame с плавающими позициями - PullRequest
0 голосов
/ 16 января 2019

Я хочу обнаружить идеальные по пикселям коллизии между двумя спрайтами.

Я использую следующую функцию, которую я нашел в Интернете, но для меня это имеет смысл.

    static bool PerPixelCollision(Sprite a, Sprite b)
    {
        // Get Color data of each Texture
        Color[] bitsA = new Color[a.Width * a.Height];
        a.Texture.GetData(0, a.CurrentFrameRectangle, bitsA, 0, a.Width * a.Height);
        Color[] bitsB = new Color[b.Width * b.Height];
        b.Texture.GetData(0, b.CurrentFrameRectangle, bitsB, 0, b.Width * b.Height);

        // Calculate the intersecting rectangle
        int x1 = (int)Math.Floor(Math.Max(a.Bounds.X, b.Bounds.X));
        int x2 = (int)Math.Floor(Math.Min(a.Bounds.X + a.Bounds.Width, b.Bounds.X + b.Bounds.Width));

        int y1 = (int)Math.Floor(Math.Max(a.Bounds.Y, b.Bounds.Y));
        int y2 = (int)Math.Floor(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 colorA = bitsA[(x - (int)Math.Floor(a.Bounds.X)) + (y - (int)Math.Floor(a.Bounds.Y)) * a.Texture.Width];
                Color colorB = bitsB[(x - (int)Math.Floor(b.Bounds.X)) + (y - (int)Math.Floor(b.Bounds.Y)) * b.Texture.Width];

                if (colorA.A != 0 && colorB.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;
    }

(все Math.floor бесполезны, я скопировал эту функцию из моего текущего кода, где я пытаюсь заставить ее работать с плавающей точкой).

Он считывает цвет спрайтов в прямоугольной части, которая является общей для обоих спрайтов.

Это на самом деле работает нормально, когда я отображаю спрайты в координатах x / y, где x и y являются целыми числами (.Bounds.X и .Bounds.Y):

Посмотреть пример

Проблема с отображением спрайтов в координатах int заключается в том, что это приводит к очень неровному движению по диагонали:

Посмотреть пример

Так что, в конечном счете, я бы не хотел приводить позицию спрайта к int при рисовании, что приводит к плавному (er) движению:

Посмотреть пример

Проблема в том, что PerPixelCollision работает с целыми числами, а не с плавающей точкой, поэтому я добавил все эти объекты Math.Floor. Как и раньше, он работает в большинстве случаев, но ему не хватает одной строки и одного ряда проверки снизу и справа (я думаю) общего прямоугольника из-за округления, вызванного Math.Floor:

Посмотреть пример

Когда я думаю об этом, я думаю, что это имеет смысл. Если x1 равен 80, а x2 фактически равен 81,5, но равен 81 из-за преобразования, то цикл будет работать только для x = 80 и, следовательно, пропустит последний столбец (в примере gif у фиксированного спрайта есть прозрачный столбец на слева от видимых пикселей).

Проблема в том, что, как бы я ни думал об этом или что бы я ни пытался (я много чего пробовал) - я не могу заставить это работать должным образом. Я почти убежден, что x2 и y2 должны иметь Math.Ceiling вместо Math.Floor, чтобы «включить» последний пиксель, который в противном случае был бы пропущен, но тогда он всегда выводит мне индекс из массивов bitA или bitsB.

Кто-нибудь сможет настроить эту функцию так, чтобы она работала, когда Bounds.X и Bounds.Y являются числами с плавающей запятой?

PS - возможно, проблема возникла в BoxingViewportAdapter? Я использую это (из MonoExtended), чтобы «улучшить» мою игру, которая на самом деле 144p.

Ответы [ 2 ]

0 голосов
/ 19 января 2019

вы можете обернуть ваш спрайт прямоугольником и использовать его функцию Intersect, которая предотвращает столкновения.

Пересечение - XNA

0 голосов
/ 19 января 2019

Помните, что нет такой вещи, как дробный пиксель. Для целей перемещения имеет смысл использовать плавающие значения и приводить их к целочисленным пикселям при рисовании. Проблема не в дробных значениях, а в том, как они нарисованы.

Основная причина, по которой коллизии не работают должным образом, - это масштабирование. Цвета для новых пикселей между диагоналями получают свои цвета путем усреднения * окружающих пикселей. Эффект заставляет изображение выглядеть больше, чем оригинал, особенно на диагонали.

* Есть несколько методов, которые могут использоваться для масштабирования, бикубические и линейные являются наиболее распространенными.

Единственное прямое (идеальное по пикселям) решение - сравнить фактический результат после масштабирования. Это требует рендеринга всего экрана дважды, а масштабный коэффициент требует большего количества вычислений. (не рекомендуется)

Поскольку вы сравниваете немасштабированные изображения, ваши коллизии кажутся отключенными.

Другая проблема - скорость движения. Если вы перемещаетесь быстрее, чем один пиксель на Update (), обнаружения столкновений на пиксель недостаточно, если движение должно быть ограничено препятствием. Вы должны разрешить столкновение.

Для врагов или опасностей для окружающей среды достаточно оригинального кода и разрешение столкновений не требуется. Это даст игроку незначительное преимущество.

Простой алгоритм разрешения (см. Математическое решение ниже) состоит в том, чтобы разматывать движение пополам, проверять на столкновение. Если он все еще сталкивается, размотайте движение на четверть, иначе продвиньте его на четверть и проверьте на столкновение. Повторяйте, пока движение не станет менее 1 пикселя. Это запускает журнал скорости раз.

Что касается верхней стены, которая не сталкивается идеально: если начальное значение Y не кратно скорости вертикального движения, вы не попадете идеально на ноль. Я предпочитаю решить эту проблему, установив Y = 0, когда Y отрицательно. То же самое для X, а также когда X и Y> границы экрана - начало координат, для нижней и правой частей экрана.

Я предпочитаю использовать математические решения для разрешения столкновений. На ваших примерах изображений вы видите коробку, сталкивающуюся с ромбом, форма ромба математически представляется как расстояние Манхэттена (Math.Abs(x1-x2) + Math.Abs(y1-y2)). Из этого факта легко напрямую рассчитать разрешение до столкновения.

По оптимизации:

Убедитесь, что ограничивающие прямоугольники перекрываются перед вызовом этого метода.

Как вы заявили, удалите все Math.Floor с, так как приведение достаточно. Сократите все вычисления внутри циклов, не зависящих от переменной цикла вне цикла.

(int)a.Bounds.Y * a.Texture.Width и (int)b.Bounds.Y * b.Texture.Width не зависят от переменных x или y и должны быть вычислены и сохранены перед циклами. Вычитания 'y- [вышеуказанная переменная] `должны храниться в цикле" y ".

Я бы рекомендовал использовать битборд (1 бит на 8 на 8 квадратов) для столкновений. Это уменьшает широкие (8x8) проверки столкновений до O (1). Для разрешения 144x144 все пространство поиска становится 18x18.

...