我想检测2个精灵之间完美的像素碰撞。
我使用以下在网上找到的功能,但对我来说完全有意义。
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是int的(.Bounds.X
和.Bounds.Y
),这实际上工作得很好:
在int的坐标上显示子图形的问题是,它会导致对角线非常锯齿运动:
所以最终我不想在绘制精灵时将精灵位置转换为int的位置,这会导致运动更顺畅:
问题在于PerPixelCollision使用int而不是float,因此这就是为什么我添加所有Math.Floor的原因。照原样,它在大多数情况下都可以使用,但是由于Math.Floor引起的舍入,在通用矩形的底部和右侧(我认为)缺少一行和一行检查。
考虑到这一点,我认为这是有道理的。如果x1是80而x2实际上是81.5但由于强制转换而为81,则该循环仅适用于x = 80,因此错过了最后一列(在示例gif中,固定的sprite在其上具有透明列)可见像素的左侧)。
问题在于,无论我如何努力思考或尝试(我尝试了很多事情),我都无法正常工作。我几乎确信,x2和y2应该具有Math.Ceiling而不是Math.Floor,以便“包括”否则被遗漏的最后一个像素,但是这样一来,我总能从bitsA或bitsB数组中获取索引。
任何人都可以调整此功能,以便在Bounds.X
和Bounds.Y
浮点时起作用吗?
PS-问题可能来自BoxingViewportAdapter吗?我正在使用它(来自MonoExtended)来“升级”我的游戏,实际上是144p。
答案 0 :(得分:0)
请记住,没有小数像素之类的东西。出于移动目的,将浮点值用作值并在绘制时将其强制转换为整数像素是完全有意义的。问题不在于分数,而在于绘制它们的方式。
冲突似乎无法正常工作的主要原因是缩放。对角线之间的新像素的颜色通过平均周围像素获得其颜色。该效果使图像看起来比原始图像大,尤其是在对角线上。
*有几种方法可用于缩放,双三次和线性是最常见的。
唯一的直接(像素完美)解决方案是在缩放后比较实际输出。这需要整个屏幕渲染两次,并且需要更多比例因子计算。(不建议)
由于您正在比较未缩放的图像,因此您的碰撞似乎已消失。
另一个问题是移动速度。如果每个Update()的移动速度快于一个像素,则如果移动要受到障碍物的限制,则检测每个像素的碰撞是不够的。您必须解决冲突。
对于敌人或环境危害,您的原始代码就足够了,不需要解决冲突。这将给玩家带来次要的优势。
一种简单的分辨率算法(请参见下面的数学解决方案)是将机芯展开一半,检查是否有碰撞。如果仍在碰撞,则将机芯展开四分之一圈,否则将其前进四分之一圈并检查是否有碰撞。重复直到运动小于1像素。这将记录“速度”时间。
对于顶壁碰撞不佳:如果起始Y值不是垂直移动速度的倍数,则您将不会完美地落在零上。我更喜欢通过在Y为负数时将Y设置为0来解决此问题。对于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”循环中。
我建议使用一个位板(每8乘8平方1位)进行碰撞。它将Broad(8x8)冲突检查减少为O(1)。对于144x144的分辨率,整个搜索空间变为18x18。
答案 1 :(得分:0)
您可以用一个矩形包裹精灵,并使用名为Intersect的函数,该函数可以确定共谋。