cHitChecker - 解决游戏开发中的命中测试问题






4.50/5 (5投票s)
2002年1月17日
4分钟阅读

90918

2045
这篇文章介绍了如何创建一个简单的 HitChecking 类,用于检查平面中的对象是否被击中。它使用了 GDI API,实现了部分区域函数。
引言
碰撞检测测试包括发现屏幕上的一个预定义区域是否被一个对象或多边形“击中”。这种类型的测试经常用于游戏开发中,例如测试鱼雷是否击中目标,或者外星人是否被击中,或者陨石是否击中了宇宙飞船。创建这种碰撞检测代码最糟糕的问题是,几乎每个精灵都不是矩形的,所以你不能仅仅使用平面上的 X 和 Y 位置来检查你的飞船是否被击中。
为了解决这个问题,我创建了一个名为 cHitChecker
的简单类。该类通过创建一个围绕 GDI 库的区域函数的包装器来执行碰撞检测。
该类有两个创建函数,一个用于创建矩形区域,另一个用于创建矩形区域。这些创建函数只是内部创建 GDI 区域对象的包装器。
类中最重要的函数是 HaveHitted
函数,该函数将另一个 cHitChecker
对象和两个对象的位置作为参数。我完全重写了这个函数以提高其性能。我将向您展示新代码的工作原理和旧代码的工作原理。如果您想检查性能,可以在项目 .ZIP 中注释掉旧代码,只需取消注释旧代码块并注释新代码块即可。
新代码
从旧代码到新代码的主要区别之一是我首先检查代表屏幕对象的两个区域的边界矩形。由于我们只检查边界矩形,因此此操作比整个区域结构更快。因此,这是检查边界矩形的第一部分代码
// First check the bounding rectangle RECT rcObj; GetRgnBox(pHitCheck->hBoundingPoly,&rcObj); rcObj.top += nY; rcObj.bottom += nY; rcObj.left += nX; rcObj.right += nX; if(nSrcX!=0 && nSrcY!=0) { OffsetRgn(hBoundingPoly, nSrcX, nSrcY); if(RectInRegion(hBoundingPoly, &rcObj) == 0) { OffsetRgn(hBoundingPoly, -nSrcX, -nSrcY); return FALSE; } OffsetRgn(hBoundingPoly, -nSrcX, -nSrcY); } else { if(RectInRegion(hBoundingPoly, &rcObj) == 0) return FALSE; }
请注意,这里有两个测试,一个用于源与原点(点 0,0)不同的区域,一个用于位于原点的区域。由于我们的区域始终存储在原点,因此我们必须先将其偏移到正确的位置,然后才能进行测试。显然这会影响性能,但在某些情况下可能需要。
如果边界矩形“击中”,我们需要检查区域的多边形结构,以便我们知道只有对象的正确部分被击中,而不是边界矩形内但区域多边形内的东西。我使用了与第一个示例不同的技术来检查碰撞区域。我首先从其中一个区域获取区域数据(RGNDATA)结构,然后获取用于创建该区域的 RECT 数组。由于 RectInRegion 函数比 CombineRgn 和 EqualRgn 快得多,我创建了一个循环,测试属于其中一个区域的每个矩形是否与正在测试的区域相交。即使该区域非常复杂(由 200 多个矩形组成),此代码也比第一个版本快得多(我在我的新游戏中使用此类,并且它大大提高了性能)。以下是用于检查区域的代码
// First we need to create a copy of the source region, so that // we don´t screw up the original region data dwSize = GetRegionData(pHitCheck->hBoundingPoly, sizeof(RGNDATA), NULL); rgnData = (RGNDATA*) malloc(dwSize); GetRegionData(pHitCheck->hBoundingPoly, dwSize, rgnData); hSrcObjectRgn = ExtCreateRegion(NULL, dwSize, rgnData); // Now get the offeseted version of the region OffsetRgn(hSrcObjectRgn, nX, nY); dwSize = GetRegionData(hSrcObjectRgn, sizeof(RGNDATA), NULL); rgnData = (RGNDATA*) realloc(rgnData, dwSize); GetRegionData(hSrcObjectRgn, dwSize, rgnData); if(nSrcX !=0 && nSrcY !=0) { RGNDATA* rgnData2; // Same copy for the region being tested dwSize = GetRegionData(hBoundingPoly, sizeof(RGNDATA), NULL); rgnData2 = (RGNDATA*) malloc(dwSize); GetRegionData(hBoundingPoly, dwSize, rgnData2); hCompObjectRgn = ExtCreateRegion(NULL, dwSize, rgnData2); OffsetRgn(hCompObjectRgn, nSrcX, nSrcY); bResult = TRUE; for(i=0;i<rgnData->rdh.nCount;i++) { memcpy(&rcObj, &rgnData->Buffer[i*sizeof(RECT)], sizeof(RECT)); if(RectInRegion(hCompObjectRgn, &rcObj) != 0) { bResult = FALSE; break; } } free(rgnData2); DeleteObject(hCompObjectRgn); } else { bResult = TRUE; for(i=0;i<rgnData->rdh.nCount;i++) { memcpy(&rcObj, &rgnData->Buffer[i*sizeof(RECT)], sizeof(RECT)); if(RectInRegion(hBoundingPoly, &rcObj) != 0) { bResult = FALSE; break; } } }
旧代码
我将解释旧代码的工作原理,以便您了解这两个版本之间的区别。我们需要做的第一件事是创建这两个对象的区域的副本。
HRGN hSrcObjectRgn; HRGN hTmpObjectRgn; HRGN hCompObjectRgn; BOOL bResult = FALSE; DWORD dwSize; RGNDATA* rgnData; // First we need to create a copy of the source region, so that // we don´t screw up the original region data dwSize = GetRegionData(pHitCheck->hBoundingPoly, sizeof(RGNDATA), NULL); rgnData = (RGNDATA*) malloc(dwSize); GetRegionData(pHitCheck->hBoundingPoly, dwSize, rgnData); hSrcObjectRgn = ExtCreateRegion(NULL, dwSize, rgnData); free((void*)rgnData); OffsetRgn(hSrcObjectRgn, nX, nY); // Same copy for the region being tested dwSize = GetRegionData(hBoundingPoly, sizeof(RGNDATA), NULL); rgnData = (RGNDATA*) malloc(dwSize); GetRegionData(hBoundingPoly, dwSize, rgnData); hCompObjectRgn = ExtCreateRegion(NULL, dwSize, rgnData); free((void*)rgnData); // Same copy for the region being tested dwSize = GetRegionData(hBoundingPoly, sizeof(RGNDATA), NULL); rgnData = (RGNDATA*) malloc(dwSize); GetRegionData(hBoundingPoly, dwSize, rgnData); hTmpObjectRgn = ExtCreateRegion(NULL, dwSize, rgnData); free((void*)rgnData);
我们创建了正在测试的区域的第二个副本,以调整最终区域的大小。为此,我们需要组合这两个区域(以便我们有一个足够大的最终区域,然后再次添加原始区域,以便我们拥有所需的参考)。我们还将参考和临时区域移动到参数中指示的位置。
OffsetRgn(hTmpObjectRgn, nSrcX, nSrcY); OffsetRgn(hCompObjectRgn, nSrcX, nSrcY); CombineRgn(hCompObjectRgn, hCompObjectRgn, hSrcObjectRgn, RGN_DIFF); CombineRgn(hCompObjectRgn, hCompObjectRgn, hTmpObjectRgn, RGN_OR);
在设置好参考和临时区域后,我们需要将临时区域与源对象的区域结合起来
CombineRgn(hTmpObjectRgn, hTmpObjectRgn, hSrcObjectRgn, RGN_DIFF);
如果源对象与临时区域(即原始对象区域)的组合产生的区域不等于比较区域,则表示对象被击中。