Runtime Revolution
 
Articles Other News

Sprite Animation: Part 1 - Collision Detection

by John Craig

 

If you are writing games with Revolution, you may need to know when your sprites collide with each other or other objects.  Although there’s no built in function to do this, we can easily write our own.

Intersection

To kick off, Revolution has a built in function “intersect”.  Intersect will tell us if the bounding boxes of our sprites overlap, but pays no attention to other detail like transparency.  As a first test in our collision detection, we can use this intersect function.  If it returns true, then we know there is a chance of a collision and can investigate further – if false is returned then we already know that no collision has occurred.

If the intersect function returns true, then the next step is to find out the common, overlapping area of the two sprites – the intersection rectangle.  Figure 1 shows the intersection rectangle of two overlapping sprites bounded by a black box.

Figure 1

It didn’t seem obvious to me how to work out the co-ordinates of this intersection rectangle, so I started with a piece of paper and a pencil and drew a few sketches of boxes overlapping in different ways.  After a while, I realised that it was actually pretty straight forward.  I would recommend trying to work it out for yourself before you read on – it’s more rewarding!

Top left corner of the rocket = x1A, y1A
Bottom right corner of the rocket = x2A, y2A

Top left corner of the car = x1B, y1B
Bottom right corner of the car = x2B, y2B

Top left corner of the intersection rectangle = x1I, y1I
Bottom right corner of the intersection rectangle = ix2, iy2

Take the greatest of x1A and x1B – this will be equal to x1br/p> Take the greatest of y1A and y1B – this will be equal to y1br/p> Take the smallest of x2A and x2B – this will be equal to x2br/p> Take the smallest of y2A and y2B – this will be equal to y2I

In Revolution (if we have been passed two images, pObjA and pObjB);

function molCollision pObjA, pCacheA, pObjB, pCacheB, pAlpha

  if not intersect(pObjA, pObjB) then return false

  put the rect of pObjA into tRect

  put item 1 of tRect into x1A

  put item 2 of tRect into y1A

  put item 3 of tRect into x2A

  put item 4 of tRect into y2A

  put the rect of pObjB into tRect

  put item 1 of tRect into x1B

  put item 2 of tRect into y1B

  put item 3 of tRect into x2B

  put item 4 of tRect into y2B

  if x1A > x1B then put x1A into x1I else put x1B into x1I

  if y1A > y1B then put y1A into y1I else put y1B into y1I

  if x2A < x2B then put x2A into x2I else put x2B into x2I

  if y2A < y2B then put y2A into y2I else put y2B into y2I

  . . . . .

  . . . . .

end molCollision

The rectangle x1I,y1I,x2I,y2I is the intersection rectangle of our two images.

Alpha data

You will notice from the function snippet above that there are other parameters as well as the two sprite images that we are testing for a collision.  To decide whether a collision has taken place or not, we are going to scan the pixels of the intersection rectangle for opaque (non-transparent) pixels in each sprite that overlap.  Images have an alphaData property which contains one byte per pixel.  A value of zero means a pixel is totally tranparent and opacity increases up to a value of 255 which means a pixel is totally opaque.  The pAlpha parameter passed to the function is the level of opacity required to consider a pixel for a collision.  If a pixel’s alphaData falls below this value, then it is ignored.  This allows shadows, etc. to exist without causing collisions.

In Revolution, we can grab this alphaData;

put the alphaData of pObjA into tAlphaA

put the alphaData of pObjB into tAlphaB

It is pretty slow to access this alphaData property, so we copy it to a temporary variable to work with it which speeds things up.  It also takes a fair amount of time to copy this alphaData for large objects, which slows things down, but we can use a cached version of the alphaData for speed if the object has not been transformed in any way since we last cached it’s data – the pCacheA and pCacheB parameters can be used to tell the function to use cached alphaData for either or both of the sprite images;

We can define an alpha cache at the top of our stack script;

local lAlphaCache

Add the following in our openStack, or somewhere else that’s convenient;

put the alphaData of img “track” into lAlphaCache[track]

And add the following to our function;

if pCacheA <> empty then put lAlphaCache[pCacheA] into tAlphaA \

     else put the alphaData of pObjA into tAlphaA

if pCacheB <> empty then put lAlphaCache[pCacheB] into tAlphaB \

     else put the alphaData of pObjB into tAlphaB

Now if we made this function call;

put molCollision(image “car”, "", image "track", "track", 160) \

     into tCollision

The function would use the current alphaData from our car image and use the cached alphaData for our track image.  This technique provides a great speed increase when dealing with large objects like a racetrack.

The pAlpha value of 160 used above means that for each pixel in the intersection rectangle, a collision will be detected only if the pixel from each sprite image has an alpha value of 160 or more.

Scanning the intersection rectangle

Now for the crunch – we have to check each pixel from both sprite images’ intersection rectangle.  If both pixels meet the pAlpha requirement then we have detected a collision and we can exit the function immediately, returning true.  The objective here is to scan ONLY the intersection rectangle portion of each sprite image, making the collision test as fast as possible.

First, get the width and height of the intersection rectangle

Note that subtracting the x1 co-ordinate from the x2 co-ordinate will result in a value that is 1 less than the width, but when using these values (below in a repeat loop), we will start counting from 0 (rather than 1) so there is no need to adjust the value.

put x2I - x1I into tWidth

put y2I - y1I into tHeight

We also need the width of each object.  Each time we have scanned a horizontal line in the intersection rectangle, we will reset the position to the beginning of that line and then add the width of the sprite image to our position – thus moving 1 pixel down.

put the width of pObjA into tWidthA

put the width of pObjB into tWidthB

The following code calculates the starting position in each sprite image’s alphaData.

put x1I - x1A into tStartXA

put y1I - y1A into tStartYA

put x1I - x1B into tStartXB

put y1I - y1B into tStartYB

put tStartXA + tStartYA * tWidthA into tPosA

put tStartXB + tStartYB * tWidthB into tPosB

Now we can loop through each row of the intersection rectangle alphaData and return true if we find a pixel from each image that is opaque enough to cause a collision.  If we do detect a collision, then our job is done – we can exit the function immediately and return ‘true’.  If we ever get past the end of the two repeat loops, then no collision is detected and we return ‘false’.

repeat with y = 0 to tHeight

  repeat with x = 0 to tWidth

    if charToNum(char tPosA of tAlphaA) > pAlpha then

      if charToNum(char tPosB of tAlphaB) > pAlpha then return true

    end if

    add 1 to tPosA

    add 1 to tPosB

  end repeat

  subtract tWidth + 1 from tPosA

  subtract tWidth + 1 from tPosB

  add tWidthA to tPosA

  add tWidthB to tPosB

end repeat

Each iteration of the inner repeat loop adds 1 to tPosA and tPosB, which are the current x positions in the alphaData for pObjA and pObjB (our sprite images).

Each iteration of the outer repeat loop resets the x position by subtracting the value we have incremented the positions by in the inner loop, then adding the width of the the actual sprite image – in effect, moving 1 pixel downwards.

If we put everything together, the whole function looks like this;

function molCollision pObjA, pCacheA, pObjB, pCacheB, pAlpha

  -- first check if the images are overlapping

  if not intersect(pObjA, pObjB) then return false

  

  -- calculate the intersection rectangle

  put the rect of pObjA into tRect

  put item 1 of tRect into x1A

  put item 2 of tRect into y1A

  put item 3 of tRect into x2A

  put item 4 of tRect into y2A

  put the rect of pObjB into tRect

  put item 1 of tRect into x1B

  put item 2 of tRect into y1B

  put item 3 of tRect into x2B

  put item 4 of tRect into y2B

  if x1A > x1B then put x1A into x1I else put x1B into x1I

  if y1A > y1B then put y1A into y1I else put y1B into y1I

  if x2A < x2B then put x2A into x2I else put x2B into x2I

  if y2A < y2B then put y2A into y2I else put y2B into y2I

  

  -- get the image's current or cached alphaData

  if pCacheA <> empty then put lAlphaCache[pCacheA] into tAlphaA else put the alphaData of pObjA into tAlphaA

  if pCacheB <> empty then put lAlphaCache[pCacheB] into tAlphaB else put the alphaData of pObjB into tAlphaB

  

  -- get the required dimensions of intersection rectangle and sprite images

  put x2I - x1I into tWidth

  put y2I - y1I into tHeight

  put the width of pObjA into tWidthA

  put the width of pObjB into tWidthB

  

  -- calculate alphadata starting positions

  put x1I - x1A into tStartXA

  put y1I - y1A into tStartYA

  put x1I - x1B into tStartXB

  put y1I - y1B into tStartYB

  put tStartXA + tStartYA * tWidthA into tPosA

  put tStartXB + tStartYB * tWidthB into tPosB

  

  -- scan each line of the intersection rectangle, returning true if a collision is detected

  repeat with y = 0 to tHeight

    repeat with x = 0 to tWidth

      if charToNum(char tPosA of tAlphaA) > pAlpha then

        if charToNum(char tPosB of tAlphaB) > pAlpha then return true

      end if

      -- increment alphadata x position

      add 1 to tPosA

      add 1 to tPosB

    end repeat

    -- reset alphaData x position to the beginning of the line

    subtract tWidth + 1 from tPosA

    subtract tWidth + 1 from tPosB

    -- add the width of the sprite image to effectively move down 1 pixel

    add tWidthA to tPosA

    add tWidthB to tPosB

  end repeat

  

  -- if we get this far then no collision has been detected

  return false

end molCollision

Now that we can detect collisions between our sprites, the fun can begin! The demo stack demonstrates the collision detection in action.

 
©2005 Runtime Revolution Ltd, 15-19 York Place, Edinburgh, Scotland, UK, EH1 3EB.
Questions? Email info@runrev.com for answers.