Sunday, June 12, 2011

Collision detection...

So I've been very busy doing the HTML5 version of GameMaker, and as I've been adding the collision code, it's becoming clear that the general way folk do collisions in GameMaker is pretty slow. This isn't to say that you can't do fast collisions in GameMaker, just that the way the samples are all written, it's no wonder some people find it painful and slow at times. So I thought I'd describe how I usually do collisions. Fo starters, I've only ever used what GameMaker calls "precise collisions" once in my whole career. And looking back on it, I could have easily gotten away without that as well; experience has it's advantages....

So first... what exactly does "precise collisions" mean? Well, simply put... it's using the pixel data in images to collide with other pixel data so that you only get a collision when actual pixels touch. Looking at the image of 2 circles here, you can see their bounding boxes collide, but the circles don't. What this means is that if you used a simple bounding box collision system, it would have triggered an event, while the precise collision wouldn't. Now, this may seem like a really obvious choice, if you use precise collisions, your getting them when actually happen. However... what does GameMaker have to do to determine that 2 random bitmaps have touched; because remember these shapes can be anything, but just circles.

Well, first... it has to loop through ALL instances of both these objects to find collisions with them. It does this simply by having 2 loops like this..

for( n=0; n<Obj1_Instance_Countl; n++) 
  {
     for( f=0; f<Obj2_Instance_Countl; f++) 
     {
       Test_Collisions( n, j );
     }
  }

Now, if you imagine all the collision events you have going on between different object types, you can see that this will burn up lots of time. One of the changes we'll make as time goes on, is to "bucket" sort instances. This means we'll only check for collisions of objects that are actually close to each other. But we don't do this yet....

So... once we have two instances that we need to test, what does Test_Collisions() actually do? Well, first thing it does is it checks the bounding boxes. This is a simple check and it allows an early out if they aren't even close.

if( bbox1.right < bbox2.left ) exit;
   if( bbox1.bottom < bbox2.top ) exit;
   if( bbox1.left > bbox2.right ) exit;
   if( bbox1.top > bbox2.bottom ) exit;

Now... if we were "just" doing box collisions, we'd be done... and an event would be thrown. However, because we want precise collisions, it gets more complex than that...

Next, the system works out the overlap between collision boxes (as shown in the next image). Now each "pixel" in the sprite can be treated as a TRUE/FALSE, meaning if it's not the background colour (in this case, white), then there IS a pixel there. So the image shown is two overlapping arrays. One holding a BLUE circle, and the other holding a RED circle. We then loop through the appropriate section (slowly) and see if we ever read 2 pixels from the same position. In this case,we don't. So no event has occurred.

Here's some pseudo code for the bitmap to bitmap collision detection...

for( y=YStartPos;y<YEndPos;y++)
  {
    for( x=XStartPos;x<xendpos;x++)
    {
      if(x < xSprite1Start) || (x> xSprite1End ) continue; 
      if(y < ySprite1Start) || (y> ySprite1End ) continue;
      if(x < xSprite2Start) || (x> xSprite2End ) continue;  
      if(y < ySprite2Start) || (y> ySprite2End ) continue;

      if(( CollisionMask1[ x+(y*width)]!=0 ) && ( CollisionMask2[ x+(y*width)]!=0 ) ){
           return TRUE;
      }
    }
  } 
return FALSE; 


Now, although this is simple pseudo code, you can see this isn't going to be quick! Even though it's been narrowed down to the overlapping sections, it's still having to loop through the whole thing before deciding that there is no collision! Now, if you have many of these, it's going to bring the PC to it's knees! Worse still... GameMaker wants you to use precise collisions by default. This is horrible.

Now, it's true that on very RARE occasions you do want precise collision checking; Lemmings walking on the background, or They Need to Be Fed walking around shapes, these are rare cases, and can usually be done different ways. Lemmings never did full sprite to background collision checking, in fact all it did was test a pixel at the feet of a lemming, into the mask. And you could do that directly in GML if you wanted to! In fact, Lemmings had a "1600x160" play area, and you could easily convert this to a 200x160 "BIT" per-pixel mask. This would in turn mean you'd need an array 32,000 bytes long. And that fits nicely! If you didn't want to go that far, you could have 160 arrays, each of 1600 bytes long. then have a controlling array with each of these as a line of the collision. You could then easily check this array in GML using the lemmings X and Y coordinate.

But what about normal games? Shooters and platformers? These should never use precise collision detection, there is simply no need. First, when you use precise collision detection, there is no room for error on the behalf of the player. Everything must be perfect. The can't stray even a pixel into a baddie or background block. From a gaming perspective, this is horrible. There should always be a little room for error, because as humans, we simply aren't that precise when controlling games. Joysticks/keys aren't good enough, and our judgement isn't nearly good enough. So this means players end up either being over cautious and not pushing near baddies/backgrounds; or they simply get frustrated and give up.

So whats the best solution? Well, personally I've always either stuck with box or radial collisions. Both these mehods are fast, and simple to implement, and when you use things like buckets to gether objects together, you can also cut down on what you have to check in the first place! For those that don't know... "buckets", are a simple way of grouping objects/instances together into groups. For collision, we tend to have a grid of buckets and place objects into whatever "bucket" cell they touch. This means if you want to check collisions, you no longer have to check everything to everything... just everything in the surrounding buckets. This is much, much quicker.


So... how do you define a "fair" bounding box? I normally try and create a box that is completly "contained" within an enemy, this helps avoid cases (like the 2 circles above) where players die without ever touching baddies at all. Players are MORE than willing to forgive cases where you run into objects a little, but they are very unforgiving of cases where they die without hitting anything. So, as shown in the player character on the left, the yellow box is well within the player, but it's also more than enough to get killed, by baddies or bullets. Theres also no rule saying this is the bounding box you would use to collide with the background... so chances are you'll have something that doesn't let to player get to far inside the background. Whatever looks and feels nice is all you need; if the gun goes into the background, it doesn't really matter....

Now, while I've said I'd use box or radial, one thing I wouldn't do myself.. is use a bounding box check like the one above. The reason for this is that "IFs" are slow, so if you can minimise them, all the better! So I use a trick that I learnt from Dave Jones when he was doing Blood Money; I use a box center, with a 1/2 width and 1/2 height.

Now remember that these boxes are INSIDE sprites, so they will probably be well inside each other, meaning the player knows his goose is about to be cooked. So, in this case, you can see the 1/2 width and 1/2 heights are 16x32 and 20x20, while the distance to centers are 25x35. So how do I work out theres a collision?

This is pretty simple... so I'll do X, and you'll easily see how Y works. I simply subtract the 2 X coordinates, and this gives the distance between centers (in this case 25 pixels), and if that distance is less than both 1/2 widths added together, (16pix+20pix) then we have a collision! What this does, is lets us do the X check with only 1 IF. Looking at the code below you'll notice abs(), and normally you would have to do an IF in there, but if you're sneeky, can do abs() without an IF.

So for a simple bounding box, this is probably the quickest way, and you can see the code below is nice and simple. In fact, it's fair to say I always prefer having a center and width/height collision, but that may well just be the way my brain works!

   if( (halfwidth1+halfwidth2) < abs(x1-x2) ) exit;
   if( (halfheight1+halfheight2) < abs(y1-y2) ) exit;

Now, while this is fine for standard sprite-2-sprite collision, what about sprite to background? Well, GameMaker has a handy function move_outside_solid(), but when you have precise collisions, it will sit in a loop doing collision checks over and over as it slowly (at whatever "step" speed you've given) until your sprite is move outside of whatever collisions have occured. This is a horrible function, particually if you have look back at whats involved in doing even a single "precise" collision check.

Again, I've never needed this... For "tilemap" style background, I always collide to the tile array, and then move myself out of a background collision. So how do i do that? Well... If you use tiles that are a power of 2 (8, 16, 32 etc...) then it's an easy thing to move to a tile boundary. For example, If I collide with a 32x32 tile at X=64 and Y=32, and my 32x32 instance coordinate is 56,12 ( so it's overlapping by 24,12), then all I need to do to move the instance OUT of collision, is AND it with $$FFFFFFE0. This removes the lower "fraction" bits, just like doing:
   x = x-(x%32);

Now... even if you were to keep the system GameMaker currently uses, it would still be much quicker by not using "precise" collisions, but still slower than a normal "pro" would use. Now, I could go on and on about how to do efficiant collision detection, but really... I should really do a post on each method, and you would then easily see what system was best for you particualr case.. but lets see how much time I have. :)

So final words... Well, GameMaker is often accused of being slow, but in fact, on a modern PC it's a pretty quick system for 2D games. However, because of the fairly nice commands like the complex collision systems it has built in, it's very easy to abuse them, and think theres nothing wrong with your code... however, being a good coder is all about knowing the code thats being run, even if it's not yours! If it's your game, it's your problem, and GameMaker has all the tools you need to make a the game great.

21 Smart arse replys:

Yellow said...

Nice article. I have seen similar methods being used in many other frameworks too.

Several questions:
1. When both objects have a sprite with 'rectangle' mask set, does the collision tester still go through first pixel(s) or it returns true after checking bounding boxes?
2. Similar question about 'ellipses' - do these shapes in sprite properties actually mean something, or they are converted to 'pixelmask' data?
3. (less related) For rotated & scaled sprite collision check, are there any optimisations, like reverting to normal check if angle and scale are too close to 0\1 to make difference, or choosing loop directions depepending on colliding 'corner'?

mark13673 said...

Nice article, Mike. It's always nice to know how things work and find better ways to do stuff. However it does raise other questions, specifically those that Yellow has outlined about non-rectangular sprite masks and transformed masks... maybe a follow up post with those?

Artem said...

I wonder how collision checks when one object use precise collision and another use non-precise (for example a circle). Is it still checks every pixel in intersection of bounding boxes or it works different more faster way?

Mike said...

If either sprite has precise collisions, then the slow collision path is taken - even if the "mask" is rectangular. If the sprite is scaled, or scaled+rotated, then even slower paths are taken. It's pretty nasty in there. :)

I've been optimizing it a bit for HTML5, and these will (at some point) feed back into the C++ one, but all that code is pretty nasty.

Pretty much if you don't want slow collisions, then don't mark anything as precise unless you REALLY need it to be!

In general, the transformed one is very nasty, particularly if BOTH sprites are in some way rotated. Theres not much you can do with it, and if you wanta simple "hit" or "not hit", then it's actually okay. The real issue comes when you start using it to move you out of SOLID walls etc as it's going to run that code over and over and over and over and over... you get the idea.

Artem said...

function mp_potential_step also uses collision checks?
And also what about collision_point, collision_rectangle, collision_circle, collision_ellipse and collision_line. I think line and point collision checking did'n need to use slower path or or it use it?
And also place_free, place_empty, place_meeting functions? Looks like this also need to use slow path?
And for full picture what about position_empty, position_meeting. It's only one point to check. Its fast?

Really very interesting and important subject. Thanks for raised it)

Mike said...

ANYTHING which uses the collision system will use the precise flag - as it should! Although functions like Collision_Point (and the rest of them) do allow you to override that setting, meaning you can use simple collision types if you want - I'd recommend that whenever you can.

Also, functions like position_meeting that can take an OBJECT type and ALL as well a single instance. Just remember your telling it to loop through ALL the instances in the world, not just on screen, and are testing every instance of that object, or in the case of ALL -EVERYTHING!

Use collision with care, it can be a serious CPU hog.

Mike said...

I should also say... it's one of the areas we will work on speeding up in the future, but when simple collision types occur, and you need precise collisions - there's not much we can do about it, you HAVE to loop through the masks and check to see if they overlap!

The only way to avoid this is for you not to use it in the first place!

Artem said...
This comment has been removed by the author.
Artem said...

If I define rectangle mask for a sprite. Which way it use to determine collision with another rectangle mask if it's only rotated?

Mike said...

As long as the MASK doesn't have precise collision enabled on it (and neither does anything it's going to hit!), then it'll use the faster path.

You should really be able to define the bounding box directly, but you can't. :(

True Valhalla said...

Really good article, Mike! A nice read, and very informative.

GStick said...

Mike, I pointed out to you on the GMC a while back about defining the bounding box directly, and how it used to be possible in GM7. Hopefully you will be able to add this feature back into GM in future versions.

Anonymous said...

Is there any advantage (performance-wise) of using the "Disk" mask shape, rather than using precise collision?

Artem said...

About user defined bounding box.
Looks like function sprite_collision_mask(ind,sepmasks,bboxmode,bbleft,bbright,bbtop,bbbottom,kind,tolerance) must define custom bounding box if you set "kind" to 1=bounding box and "bboxmode" to 2=user defined. But it does not work with this combination. This happens due to some restrictions or it's bug in function?

Reefpirate said...

Great article!

But I can't seem to figure out how to implement these ideas in one instance in my game.

It seems that if I set 'precise' to 'false' when using the collision_line() function, it checks the bounding box of the whole sprite, rather than a smaller un-precise rectangle mask that I made for the sprite.

Is there a way to make smaller bounding boxes for sprites, but still check with un-precise, 'fast' code?

Eg. I'm using collision_line() to check where my bullets are hitting in the world. I have doors that slide open and closed. I put a mask on the door that is 0, 0, 0, 0, basically nothing. However, when i set precise to 'false' with collision_line() it still hits the door with an empty mask!

So if you do unprecise collision checks you have to use the whole image bounding box?? Help!

Reefpirate said...

Better rewording of question maybe:

If I do NOT use precise MASKS on any of my objects I will be using this 'faster' code pipe that you talk about, even if I'm using collision_line()'s and collision_circle()'s with the 'precise' option set to 'true'?

Paul said...

Funny thing is that I'm exactly doing this (creating an engine for lemmings :P)..
What I experienced however, was that the game grinded to a halt on larger images.. Even through I only ever used "collision_line(front,top,front,bottom)" from lemmings. The size of the image isn't used in that function. (Yet the speed WAS slowing down a lot on larger images).

"Also, functions like position_meeting that can take an OBJECT type and ALL as well a single instance. Just remember your telling it to loop through ALL the instances in the world, not just on screen, and are testing every instance of that object, or in the case of ALL -EVERYTHING!"

Uhm well you could use spatial hasing to quickly rule out most instances :P.


Would it maybe be possible to use geometric shapes as collision masks (apart from ellipses & AABB)? Especially if you wish to implement some physics you'll have to know the normal to the surface, and currently that's only possible with exact collision masks :(.



@anonymous:
The advantage of a disk-shape is simply that it is a disk shape. Also rotating is a bit "better". (If you rotate an object with a square mask, the mask size increases - reaching its largest point at 45 degrees).

@Reefpirate:
If you use "true" on collision_*() functions, where you check against something that hasn't a precize collision mask, from what I understand/experienced the "true" is simply ignored.

Anonymous said...

It is for reasons like this that I often find myself wanting to turn off GM's collision system entirely. (The problem being that GM uses the collision system by default and I use faster, more versatile collision detection)

Boris said...

Kind of confirms what I suspected, I remember thinking "I'm glad I'm not the one that has to code that", but I think rather than simply take stuff away you need to really "sell" the benefits of fixed bounding boxes (like much less chance of "stuck" states) and maybe have nonprecise collisions set by default?

Currently I'm doing stuff with 3D collisions but I'm using the 2D collision mechanism to filter out obvious misses. If I use round masks I get less false collisions to filter, but make the 2D detector work harder. If I use square then I get more unwanted events=more gml code execution.

Unfortunately I'm stuck with round as I use move_bounce_solid in places.

deer antler spray said...

The script confuses me a little. But still a great method.

Runescape Gold said...

Nice post, Robert. It certainly is wonderful to understand exactly how points perform and find possible ways to perform products.