【问题标题】:Find point of collision for two sprites找到两个精灵的碰撞点
【发布时间】:2011-08-21 08:15:43
【问题描述】:

我正在使用 C# 和 XNA 开发一款 2D 游戏,并且我已经成功地通过 sprite 工作的逐像素碰撞检测。现在让我困惑的挑战是如何计算两个精灵碰撞的位置。

我需要这些信息的原因是,这款游戏的本质要求对象具有旋转性,并且根据物理原理做出一定程度的准确反应。我目前的测试环境涉及两个方块。任何关于如何找到联络点的建议都会很受欢迎。

另外,如果有人对如何计算碰撞时移动精灵的距离有任何建议,这样它们就不会重叠,那也很好。

我用于碰撞检测的代码来自这里:

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

谢谢大家!

【问题讨论】:

    标签: c# xna 2d sprite collision


    【解决方案1】:

    关于获取接触点的第一部分,您链接到的代码已经完成了艰苦的工作,而不是返回 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
    }
    

    至于第二部分,常用的方法是加一个与碰撞方向相反的小力将它们击退。这个关于游戏物理的article 是一本很好的入门书,很少有现成的物理引擎像Farseer 这样强大。

    代码示例用于转换后的精灵,如果您不需要此功能,您可以简化代码。如果您在移动一个时不使用物理引擎来防止它们重叠,则可能需要移动另一个,依此类推,物理引擎会为您解决这个问题。

    编辑: 这是对 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);
    

    在 spriteBatch.End() 之前添加到Draw()

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

    【讨论】:

    • 感谢您的回复,但是我很难应用此更改。首先,我不熟悉“yield”以及为什么会在这种情况下使用它。 (已经查过了,但在 MSDN 上但我很难理解它)另外,在这种情况下究竟会返回什么以及我将如何处理它。在我将该函数称为 if 语句的“参数”之前。但我不认为在这种情况下会起作用。
    • 我使用yield是因为可能有不止一个接触点。结果是一个 IEnumerable,因此您可以在结果上使用 foreach 或使用 Linq 的 ToArray、ToList 扩展方法。您可以将这些点添加到列表中并返回,但根据需要使用 yield fetch,因此对于 Linq,您可以使用 IntersectPixels(...).Any() 作为返回 bool 的原始方法的替代品。检查编辑以获取示例。
    • 先生,您是我的英雄。我相信我欠你一个火鸡三明治。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多