65.9K
CodeProject 正在变化。 阅读更多。
Home

碰撞 - 一个 C# 游戏,第 3 部分:像素完美碰撞检测

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (15投票s)

2002年4月17日

3分钟阅读

viewsIcon

123019

downloadIcon

2221

完成我用C#编写简单游戏的尝试。

Sample Image - Collision3.jpg

目标

从我最初的宏伟计划降低了不少,我仍然决心这个游戏中的碰撞检测应该是完美的,而不是近似的。这只能通过直接访问位图数据来实现。由于到目前为止我发现所有东西都比我希望的要慢,所以我的第一步是优化我所拥有的东西。

JPEG 与 Bitmap

您会记得我最初的资源都是 jpeg 以节省空间,但我注意到这意味着我在绘制时必须指定一个颜色范围才能屏蔽背景。 为了速度,我使用了位图资源,这意味着手动将背景绘制成硬黑色。 这意味着您可能会发现游戏看起来并不像素完美,因为一些旧的蓝色背景可能仍然存在,但在游戏中不可见。 我向您保证我向您展示的技术没有问题,问题在于我编辑位图的工作。 代码有望更好地为我们的碰撞检测成本做好准备,但您会注意到 EXE 和项目的大小有了显着的跃升。 没有添加新的位图,这仅仅是位图相对于 jpeg 的成本。

MakeTransparent

完成此操作后,我可以使用 BitmapMakeTransparent 方法,传入 Color.Black 作为参数,并且我不再需要担心屏蔽 - 图像现在将自动绘制,黑色区域被屏蔽。

逐像素碰撞

这让我可以使用碰撞代码。第一步很简单,即通过检查行星的 Rectangle 与飞船的 Rectangle 是否发生碰撞来检查是否发生碰撞。这与移动行星和计分的循环发生在同一个循环中,但我为了清晰起见删除了该代码(您上次已经看到了)。

 for (int i = 0; i < m_arAsteroids.Count; ++i)
 {
     if (arTemp.rcAsteroid.IntersectsWith(m_rcShipPos))
    {
        // We've hit
    }

但问题是,正如您所记得的,我们有一个巨大的行星,所以其他行星都有相当多的透明度。即使不是这样,也没有游戏玩家会很高兴地发现,如果他们进入定义他们正在躲避的行星的矩形范围内,他们就会死亡。我们需要做得更好。解决方案是找出定义两个对象共享区域的矩形,为两个位图规范化该矩形,然后一起逐步遍历它们,看看相交矩形中的任何像素位置是否包含在两个位图中都没有被屏蔽的像素值。主要的技巧是尽可能限制我们逐步遍历的区域,因为它不是一个廉价的操作。

 private bool HitTest(Asteroid a) 
 { 
    Rectangle rcIntersect = a.rcAsteroid; rcIntersect.Intersect(m_rcShipPos);
    BitmapData bmData = m_bmShip.LockBits(
                               new Rectangle(rcIntersect.X - m_rcShipPos.X, 
                                             rcIntersect.Y - m_rcShipPos.Y, 
                                             rcIntersect.Width, rcIntersect.Height), 
                               ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    BitmapData bmData2 = m_bmPlanets.LockBits(
                      new Rectangle(87 * a.nBitmap + rcIntersect.X - a.rcAsteroid.X, 
                                   rcIntersect.Y - a.rcAsteroid.Y, 
                                   rcIntersect.Width, rcIntersect.Height), 
                                   ImageLockMode.ReadOnly, 
                                   PixelFormat.Format24bppRgb);

    int stride = bmData.Stride;
    System.IntPtr Scan0 = bmData.Scan0;
    System.IntPtr Scan1 = bmData2.Scan0;

    unsafe
    {
        byte * p = (byte *)Scan0;
        byte * p1 = (byte *)Scan1;

        int nOffset = stride - rcIntersect.Width*3;

        for(int y=0;y<rcIntersect.Height;++y)
        {
            for(int x=0; x < rcIntersect.Width; ++x )
            {
                if (p[0] != 0 &&
                    p[1] != 0 &&
                    p[2] != 0 &&
                    p1[0] != 0 &&
                    p1[1] != 0 &&
                    p1[2] != 0)
                {
                    m_bmShip.UnlockBits(bmData);
                    m_bmPlanets.UnlockBits(bmData2);
                    return true;
                }

                p += 3;
                p1 += 3;;
            }
            p += nOffset;
            p1 += nOffset;
        }
    }

    m_bmShip.UnlockBits(bmData);
    m_bmPlanets.UnlockBits(bmData2);

    return false;            
}

所以现在我们首先进行矩形测试,然后测试这个函数,如果我们都得到 true,我们就结束游戏。一个消息框会通知我们我们的分数,然后当我们关闭它时,游戏会重新开始,并在一个新的模式中重新生成星空。

这不是很令人兴奋(目标受众是我 5 岁的女儿),但它实现了我个人的目标,即给我一个编写更多 C# 代码的借口,并且希望我一路涵盖的主题对一些 CPians 感兴趣。我的硬盘驱动器上的某个地方有一个使用 DirectX 的小行星游戏,我会把它挖出来并写一些关于它的东西,以便可以进行比较。考虑到 C# 并不是为此而设计的,而且我不知道没有 DirectX 的 C++ 会好多少,所以这根本不是一个公平的比较。我想说 C# 通过集成 GDI+ 使得编写起来更容易,GDI+ 为我们做了很多工作。

历史

  • 2002 年 4 月 17 日:初始版本

许可证

本文没有附加明确的许可,但可能在文章文本或下载文件中包含使用条款。如有疑问,请通过下面的讨论区联系作者。可以在此处找到作者可能使用的许可证列表。

© . All rights reserved.