All games, at some point, must deal with the situation in which one sprite touches another. For some games, this may be when a bullet strikes an alien or when an animated mouth collides with a glowing white pellet to eat it. A game application must determine when these situations happen, and this is where collision detection comes in.
There exist as many different methods to perform collision detection as there do methods to draw a sprite. Many articles have been written on this subject, exploring the nuances of checking large amounts of sprites against each other and the optimizations required to perform such techniques in a timely fashion. In most cases, however, sprite collision can be detected using one of two techniques: bounding boxes and pixel overlap comparisons.
Bounding Boxes
The bounding box method of collision detection works by defining a rectangular region around the sprite, its “bounding box.” To determine if a collision with another sprite has occurred, the application simply determines if their bounding box rectangles intersect. If indeed the rectangles intersect, a collision has occurred and the game application can respond accordingly, as shown in the following illustration.
Figure 6-7: Sprite bounding boxes
Notice that in Figure 6-7b, the intersected rectangles would indicate that a collision has occurred, although clearly the sprites are not touching. This is the inherent drawback to using bounding boxes, in that many times during close calls a collision may be flagged when in fact the sprites never touched. Shrinking the size of the bounding boxes to where they do not encapsulate the entire sprite diminishes this error, as shown in Figure 6-7c. However, the reverse effect can occur, where two sprites have collided although no collision was detected. For the most part, it is better to err on the side of the player and allow the infrequent true collisions to go unresolved. However, for some games where precise collision detection is required, it is necessary to actually check the pixels of one image with the pixels of another to see if they overlap, as we examine shortly.
Tip: It is possible to use two or more bounding boxes for a sprite, thereby providing a more accurate approximation of the sprite’s surface. The GDI also contains many functions to create regions of different shapes and sizes, including circular and polygonal, which can then be used to detect collisions more accurately.
Windows has highly optimized routines that make manipulating rectangles a breeze. In particular, the IntersectRect function determines if two rectangles overlap and returns a rectangle that defines the area of intersection. The IntersectRect function is defined as:
IntersectRect(
var lprcDst: TRect; // the rectangle receiving the intersection coordinates const lprcSrc1: TRect; // the first rectangle
const lprcSrc2: TRect // the second rectangle : BOOL; // returns TRUE or FALSE
The first parameter is a rectangle that will receive the coordinates of the intersection between the rectangles specified in the last two parameters. If the rectangles do not intersect, the first parameter will be set to all zeroes and the function returns FALSE.
The following example demonstrates how to use bounding boxes to determine if two sprites have collided.
Listing 6-5: Bounding box collision detection
procedure TfrmDXAppMain.DrawSurfaces;
var
Intersection, Area: TRect;
iCount: Integer;
{this function moves and draws a sprite}
procedure MoveSprite(var Sprite: TSprite);
var
SrfcDC: HDC;
TempCanvas: TCanvas;
begin
{move the sprite, checking for horizontal and vertical screen boundaries}
Sprite.FXPos := Sprite.FXPos + Sprite.FXVel;
. . .
{update the sprite’s bounding box based on its new position}
Sprite.Boundary := Rect(Sprite.FXPos, Sprite.FYPos, Sprite.FXPos+Sprite.Width, Sprite.FYPos+Sprite.Height);
{draw the sprite transparently}
Area := Rect (0, 0, Sprite.Width, Sprite.Heigh);
FBackBuffer.BltFast(Sprite.FXPos, Sprite.FYPos, Sprite.FImages, Area, DDBLTFAST_SRCCOLORKEY or DDBLTFAST_WAIT);
{draw a red border around the sprite to display its bounding box}
. . . end;
begin
{erase the previous animation frame}
Area := Rect (0, 0, DXWIDTH, DXHEIGHT);
FBackBuffer.BltFast(0, 0, FBackground, Area,
DDBLTFAST_NOCOLORKEY or DDBLTFAST_WAIT);
{move and draw the sprites}
for iCount := 0 to 1 do
MoveSprite(Sprites[iCount]);
{if any of the sprite’s bounding boxes intersect, signal a collision. real world examples would generally require checking many rectangles against each other}
if IntersectRect(Intersection, Sprites[0].Boundary, Sprites[1].Boundary) then Messagebeep(0);
end;
Previous Table of Contents Next [an error occurred while processing this directive]
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction whole or in part in any form or medium without express written permission of
EarthWeb is prohibited. Read EarthWeb's privacy statement.
Search Tips
Advanced Search
Delphi Graphics and Game Programming Exposed with DirectX 7.0 by John Ayres
Wordware Publishing, Inc.
ISBN: 1556226373 Pub Date: 12/01/99 Search this book:
Previous Table of Contents Next
Pixel Overlap Comparison
The bounding box method of collision detection can easily be extended to provide very accurate pixel level collision detection. Basically, a standard bounding box collision detection is performed. It is necessary that the bounding boxes of the sprites be the same dimensions as the sprite images (actually, they could be different, but the resulting offset math makes the pixel offset determinations cumbersome). Once a collision has occurred, it is necessary to determine the exact rectangular intersection area, which is conveniently reported by the IntersectRect function. The coordinates of the upper left corner of the rectangle are subtracted from the coordinates of the upper left corner of each of the sprite image positions. This will give a starting offset of the intersection rectangle relative to the sprites. From this starting offset within the sprite images, a simple embedded loop is used to walk through the pixels in each sprite image, comparing the colors of the pixels in the relative position of each sprite as determined by the intersection rectangle. If any non-transparent pixels match up, then the sprites overlap and a collision has occurred, as illustrated below.
Figure 6-8: Pixel level collision detection
This algorithm provides very accurate collision detection. Due to its nature, we must directly access the surface memory as we covered above. This is a slow collision detection technique, but it could possibly be optimized using assembly language. However, for small numbers of sprites, the performance penalty is minimal. The following example demonstrates pixel-accurate collision detection.
Listing 6-6: Collision detection at the pixel level
procedure TfrmDXAppMain.DrawSurfaces;
var
Intersection, Area: TRect;
iCount: Integer;
Sprite1SurfaceDesc,
Sprite2SurfaceDesc: TDDSurfaceDesc2;
Sprite1Mem, Sprite2Mem: PBytePtr;
iRow, iCol: Integer;
SrfcDC: HDC;
Hit: Boolean;
Title
{this function moves and draws a sprite}
procedure MoveSprite(var Sprite: TSprite);
var
{erase the previous animation frame}
FBackBuffer.BltFast(0, 0, FBackground, Rect(0, 0, DXWIDTH, DXHEIGHT), DDBLTFAST_NOCOLORKEY or DDBLTFAST_WAIT);
{move and draw the sprites}
for iCount := 0 to 1 do
MoveSprite(Sprites[iCount]);
{retrieve the intersection of the sprite’s bounding boxes. if there is indeed an intersection, compare the pixels in intersecting areas of each sprite. if the AND of any two pixels is greater than 0 (our transparent pixel value is 0, so two overlapped image pixels will return a value greater than 0), then there was indeed a pixel level collision.}
if IntersectRect(Intersection, Sprites[0].Boundary, Sprites[1].Boundary) then begin
{at this point, the bounding boxes have collided, so we need to prepare to access the sprite surfaces. we will lock the entire surface of each
sprite}
Sprite1SurfaceDesc.dwSize := SizeOf(TDDSurfaceDesc2);
DXCheck(FShip1.Lock(nil, Sprite1SurfaceDesc,
DDLOCK_WAIT or DDLOCK_SURFACEMEMORYPTR, 0));
{initialize the byte array to the intersected rectangular area of the sprite}
Sprite1Mem := PBytePtr(Sprite1SurfaceDesc.lpSurface);
Sprite1Mem := Pointer(Longint(Sprite1Mem)+
((Intersection.Top-Sprites[0].FYPos)*
Sprite1SurfaceDesc.lPitch+
(Intersection.Left-Sprites[0].FXPos)));
{lock the other sprite’s surface}
Sprite2SurfaceDesc.dwSize := SizeOf(TDDSurfaceDesc2);
DXCheck(FShip2.Lock(nil, Sprite2SurfaceDesc,
DDLOCK_WAIT or DDLOCK_SURFACEMEMORYPTR, 0));
{and initialize the other byte array to the intersected rectangular area of the sprite}
Sprite2Mem := PBytePtr(Sprite2SurfaceDesc.lpSurface);
Sprite2Mem := Pointer(Longint(Sprite2Mem)+
((Intersection.Top-Sprites[1].FYPos)*
Sprite2SurfaceDesc.lPitch+
(Intersection.Left-Sprites[1].FXPos)));
{begin determining if a collision has taken place.}
Hit := FALSE;
for iRow := 0 to (Intersection.Bottom-Intersection.Top)-1 do for iCol := 0 to (Intersection.Right-Intersection.Left)-1 do {if the pixels in the intersected rectangular areas of
both sprites is something other than a transparent pixel, a collision has occurred}
if Sprite1Mem^[iRow*Sprite1SurfaceDesc.lPitch+iCol] and Sprite2Mem^[iRow*Sprite2SurfaceDesc.lPitch+iCol] > 0 then Hit := TRUE;
{visually indicate any collisions}
FBackBuffer.GetDC(SrfcDC);
try
if Hit then
TextOut(SrfcDC, 10, 460, ‘Collision!’, Length(‘Collision!’));
finally
FBackBuffer.ReleaseDC(SrfcDC);
end;
{we no longer need access to surfaces, so unlock them}
FShip1.Unlock(nil);
FShip2.Unlock(nil);
end;
end;