Search code examples
iphonecollision-detection

iPhone SDK: Collision detection, does it have to be a rectangle?


I am making a basic platform game for the iPhone and I have encountered a problem with my collision detection.

if (CGRectIntersectsRect(player.frame, platform.frame))
    pos2 = CGPointMake(0.0, +0.0);
else
    pos2 = CGPointMake(0.0, +10.0);

The collision detection is to stop in-game-gravity existing when the player is on a platform, the problem is with the fact that the collision detection is the rectangle around the player, is there anyway to do collision detection for the actual shape of an image (with transparency) rather that the rectangle around it?


Solution

  • You'll have to program this on your own, and beware the pixel-by-pixel collision is probably too expensive for the iPhone. My recommendation is to write a Collidable protocol (called an interface in every other programming language), give it a collidedWith:(Collidable *)c function, and then just implement that for any object that you want to allow collision for. Then you can write case-by-case collision logic. Similarly, you can make a big superclass that has all the information you'd need for collision (in your case either an X, Y, width, and height, or an X, Y, and a pixel data array) and a collidesWith method. Either way you can write a bunch of different collision methods - if you're only doing pixel collision for a few things, it won't be much of a performance hit. Typically, though, it's better to do bounding box collision or some other collision based on geometry, as it is significantly faster.

    The folks over at metanetsoftware made some great tutorials on collision techniques, among them axis separation collsion and grid based collision, the latter of which sounds like it would be more viable for your game. If you want to stick with brute force collision detection, however (checking every object against every other object), then making a bounding box that is simply smaller than the image is typically the proper way to go. This is how many successful platformers did it, including Super Mario Brothers.You might also consider weighted bounding boxes - that is, you have one bounding box for one type of object and a different sized one for others. In Mario, for example, you have a larger box to hit coins with than you do enemies.

    Now, even though I've warned you to do otherwise, I'll oblige you and put in how to do pixel-based collision. You're going to want to access the pixel data of your CGImage, then iterate through all the pixels to see if this image shares a location with any other image. Here's some code for it.

    for (int i = 0; i < [objects count]; i++)
    {
        MyObject *obj1 = [objects objectAtIndex:i];
    
        //Compare every object against every other object.
        for (int j = i+1; j < [objects count]; j++)
        {
            MyObject *obj2 = [objects objectAtIndex:j];
    
            //Store whether or not we've collided.
            BOOL collided = NO;
    
            //First, do bounding box collision. We don't want to bother checking
            //Pixels unless we are within each others' bounds.
            if (obj1.x + obj1.imageWidth >= obj2.x &&
                obj2.x + obj2.imageWidth >= obj1.x &&
                obj1.y + obj1.imageHeight >= obj2.y &&
                obj2.y + obj2.imageGeight >= obj1.y)
            {
                //We want to iterate only along the object with the smallest image.
                //This way, the collision checking will take the least time possible.
                MyObject *check = (obj1.imageWidth * obj1.imageHeight < obj2.imageWidth * obj2.imageHeight) ? obj1 : obj2;
    
                //Go through the pixel data of the two objects.
                for (int x = check.x; x < check.x + check.imageWidth && !collided; x++)
                {
                    for (int y = check.y; y < check.y + check.imageHeight && !collided; y++)
                    {
                        if ([obj1 pixelIsOpaqueAtX:x andY:y] && [obj2 pixelIsOpaqueAtX:x andY:y])
                        {
                            collided = YES;
                        }
                    }
                }
            }
        }
    }
    

    I made it so pixelIsOpaque takes a global coordinate rather than a local coordinate, so when you program that part you have to be careful to subtract the x and y out of that again, or you'll be checking out of the bounds of your image.