Найти точку столкновения для двух спрайтов - PullRequest
1 голос
/ 09 мая 2011

Я работаю над 2D-игрой на C # и XNA, и мне удалось добиться обнаружения столкновений на пиксель с работающими спрайтами. Задача, которая меня сейчас озадачивает, заключается в том, как рассчитать место, где сталкиваются два спрайта.

Причина, по которой мне нужна эта информация, заключается в том, что природа этой игры требует, чтобы объекты вращались и реагировали достаточно точно в соответствии с физикой. Моя текущая среда тестирования включает в себя два квадрата. Любые предложения о том, как найти контактную точку, будут хорошо приняты.

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

Код, который я использую для обнаружения столкновений, происходит отсюда:

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

Спасибо всем!

1 Ответ

3 голосов
/ 09 мая 2011

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

Вот немного измененный код из http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed

public static IEnumerable<Vector2> IntersectPixels(
                    Matrix transformA, int widthA, int heightA, Color[] dataA,
                    Matrix transformB, int widthB, int heightB, Color[] dataB)
{
    // Calculate a matrix which transforms from A's local space into
    // world space and then into B's local space
    Matrix transformAToB = transformA * Matrix.Invert(transformB);

    // When a point moves in A's local space, it moves in B's local space with a
    // fixed direction and distance proportional to the movement in A.
    // This algorithm steps through A one pixel at a time along A's X and Y axes
    // Calculate the analogous steps in B:
    Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
    Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);

    // Calculate the top left corner of A in B's local space
    // This variable will be reused to keep track of the start of each row
    Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);

    // For each row of pixels in A
    for(int yA = 0; yA < heightA; yA++)
    {
        // Start at the beginning of the row
        Vector2 posInB = yPosInB;

        // For each pixel in this row
        for(int xA = 0; xA < widthA; xA++)
        {
            // Round to the nearest pixel
            int xB = (int)Math.Round(posInB.X);
            int yB = (int)Math.Round(posInB.Y);

            // If the pixel lies within the bounds of B
            if(0 <= xB && xB < widthB &&
                0 <= yB && yB < heightB)
            {
                // Get the colors of the overlapping pixels
                Color colorA = dataA[xA + yA * widthA];
                Color colorB = dataB[xB + yB * widthB];

                // If both pixels are not completely transparent,
                if(colorA.A != 0 && colorB.A != 0)
                {
                    // then an intersection has been found
                    yield return Vector2.Transform(new Vector2(xA, yA),transformA);
                }
            }

            // Move to the next pixel in the row
            posInB += stepX;
        }

        // Move to the next row
        yPosInB += stepY;
    }

    // No intersection found
}

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

Пример кода для преобразованных спрайтов. Если вам не нужна эта функция, вы, вероятно, могли бы упростить код. Если вы не используете физический движок, чтобы не допустить их перекрытия при перемещении, может потребоваться перемещение другого и т. Д., Физический движок позаботится об этом за вас.

Edit: Вот несколько небольших изменений в образце MSDN, поэтому каждая точка контакта отображается зеленым пикселем.

Добавить эти поля

//Contact points are cleared and re-added each update
List<Vector2> contactPoints = new List<Vector2>();
//Texture for contact display
Texture2D pixelTex;

Добавить к LoadContent() где-то

pixelTex = new Texture2D(GraphicsDevice, 1, 1);
pixelTex.SetData<Color>(new[] { Color.White });

Заменить конец Update() на этот

// Update each block
personHit = false;
contactPoints.Clear();
for(int i = 0; i < blocks.Count; i++)
{
    // Animate this block falling
    blocks[i].Position += new Vector2(0.0f, BlockFallSpeed);
    blocks[i].Rotation += BlockRotateSpeed;

    // Build the block's transform
    Matrix blockTransform =
        Matrix.CreateTranslation(new Vector3(-blockOrigin, 0.0f)) *
        // Matrix.CreateScale(block.Scale) *  would go here
        Matrix.CreateRotationZ(blocks[i].Rotation) *
        Matrix.CreateTranslation(new Vector3(blocks[i].Position, 0.0f));

    // Calculate the bounding rectangle of this block in world space
    Rectangle blockRectangle = CalculateBoundingRectangle(
                new Rectangle(0, 0, blockTexture.Width, blockTexture.Height),
                blockTransform);

    // The per-pixel check is expensive, so check the bounding rectangles
    // first to prevent testing pixels when collisions are impossible.
    if(personRectangle.Intersects(blockRectangle))
    {
        contactPoints.AddRange(IntersectPixels(personTransform, personTexture.Width,
                            personTexture.Height, personTextureData,
                            blockTransform, blockTexture.Width,
                            blockTexture.Height, blockTextureData));
        // Check collision with person
        if(contactPoints.Count != 0)
        {
            personHit = true;
        }
    }

    // Remove this block if it have fallen off the screen
    if(blocks[i].Position.Y >
        Window.ClientBounds.Height + blockOrigin.Length())
    {
        blocks.RemoveAt(i);

        // When removing a block, the next block will have the same index
        // as the current block. Decrement i to prevent skipping a block.
        i--;
    }
}

base.Update(gameTime);

Добавить к Draw() перед spriteBatch.End ()

foreach(Vector2 p in contactPoints)
{
    spriteBatch.Draw(pixelTex, new Rectangle((int)p.X, (int)p.Y, 1, 1), Color.FromNonPremultiplied(120, 255, 100, 255));
}
...