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