dcp3450
dcp3450

Reputation: 11187

Why am not getting collision detection with CGRectIntersectsRect in IOS?

I have 4 elements: greenball, yellowball, orangeball, and redball that fall from the top of the screen

I also have an element, blueball, that follows my touch.

All of these things are working great! :D

However, I want to detect when the greenball intersects with the blueball

my blue ball is placed in the view at viewDidLoad, the 4 balls are placed on the view based on an nstimer that calls onTimer.

All 5 balls are created on the same layer:

[self.view insertSubview:greenImage belowSubview:bottomBar]

I can detect when the redball and greenball collide:

if (CGRectIntersectsRect(greenImage.frame, redImage.frame)) {
                UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"title" message:@"points" delegate:nil cancelButtonTitle:@"cool" otherButtonTitles:nil];
                [message show];
                [message release];
            }

If I replace "redImage" for "blueball" I don't get the alert. I assume this may be because they aren't declared in the same method.

Any ideas on how to detect this collision?

thanks!

--- EDIT ---

Using the suggestion below I'm doing the following:

        greenImage = [[UIImageView alloc] initWithImage:green];
        greenImage.frame = CGRectMake(startX,0,43,43);
        [self.view insertSubview:greenImage belowSubview:bottomBar];
        [UIView beginAnimations:nil context:greenImage];
        [UIView setAnimationDuration:5 * speed];
        greenImage.frame = CGRectMake(startX, 500.0, 43,43);
        CGFloat r1 = greenImage.frame.size.width * .5;
        CGFloat r2 = blueBall.frame.size.width * .5;
        if(CGPointDistance(greenImage.center, blueBall.center) < (r1 + r2)){
            UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"notice" message:@"hit!" delegate:nil cancelButtonTitle:@"cool" otherButtonTitles:nil];
            [message show];
            [message release];
        }

below this method I have:

CGFloat CGPointDistance(CGPoint p1, CGPoint p2)
{
    return sqrtf((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y));
}

get an error:

conflicting types for 'CGPointDistance'

and a warning:

implicit declaration of function 'CGPointDistance'

Upvotes: 0

Views: 1646

Answers (2)

RickyTheCoder
RickyTheCoder

Reputation: 93

maybe this will help some, just a little modification to the code above.

-(CGFloat)CGPointDistance:(CGPoint)p1 p2:(CGPoint)p2
{
    return sqrtf(powf(p1.x - p2.x) + powf(p1.y-p2.y));
}

-(BOOL)DidCollide:(View*)v1 v2:(View*)v2 v1Radius:(CGFloat)v1Radius v2Radius:(CGFloat)v2Radius
{
    CGFloat r1 = v1.frame.size.width * v1Radius;
    CGFloat r2 = v2.frame.size.width * v2Radius;
    return [self PointDistance:v1.center p2:v2.center] < (r1 + r2)
}

and to check for collision your just do

if([self DidCollide:view1 v2:view2 v1Radius:radius v2Radius:radius])
{
    //do something
}

Upvotes: 0

Jody Hagins
Jody Hagins

Reputation: 28419

To determine if two circles intersect, you simply see if the distance between the centers is less than the sum if their radii. For example...

CGFloat CGPointDistance(CGPoint p1, CGPoint p2)
{
    return sqrtf((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y));
}

It looks like your "balls" are views... Assuming the "ball" fills the frame, the radius will be 1/2 the width...

BOOL BallsCollide(UIView *v1, UIView *v2)
{
    CGFloat r1 = v1.frame.size.width * .5;
    CGFloat r2 = v2.frame.size.width * .5;
    return CGPointDistance(v1.center, v2.center) < (r1 + r2);    
}

OK -- I have hacked up a quick sample. You can almost drop it into your view controller. Don't use it for production stuff... just as an example of how you can do stuff... You can put this at the top and the rest of your controller below it...

// I do not recommend using this approach for naything non-trivial,
// but it demonstrates the very simple collision detection for circle shapes.
@interface Ball : UIView {
}
@property (nonatomic, strong) UIColor *origColor;
@property (nonatomic, strong) UIColor *color;
+ (id)ballWithRadius:(CGFloat)radius color:(UIColor*)color;
- (BOOL)doesIntersectWith:(Ball*)ball;
@end
@implementation Ball
@synthesize color = _color;
@synthesize origColor = _origColor;
+ (id)ballWithRadius:(CGFloat)radius color:(UIColor*)color
{
    CGFloat diameter = radius * 2;
    Ball *ball = [[Ball alloc] initWithFrame:CGRectMake(0, 0, diameter, diameter)];
    ball.color = ball.origColor = color;
    ball.backgroundColor = [UIColor clearColor];
    return ball;
}
- (BOOL)doesIntersectWith:(Ball *)ball
{
    // Two circles overlap if the sum of their radii
    // is less than the distance between the two centers.
    CGFloat r1 = self.frame.size.width * .5;
    CGFloat r2 = ball.frame.size.width * .5;
    CGFloat diffX = self.center.x - ball.center.x;
    CGFloat diffY = self.center.y - ball.center.y;
    return (diffX*diffX) + (diffY*diffY) < (r1+r2)*(r1+r2);
}
- (BOOL)containsPoint:(CGPoint)point
{
    // Contains a point if distance from center is less than radius
    CGFloat r = self.frame.size.width *.5;
    CGFloat diffX = self.center.x - point.x;
    CGFloat diffY = self.center.y - point.y;
    return (diffX*diffX) + (diffY*diffY) < r*r;
}
- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.color setFill];
    [self.color setStroke];
    CGContextSetLineWidth(context, 1);
    CGContextFillEllipseInRect(context, self.bounds);
}
@end


@interface CollideVC() {
    NSMutableDictionary *activeTouches;
    NSMutableSet *currentCollisions;
}
@end

@implementation CollideVC
- (void)ball:(Ball*)ball touched:(UITouch*)touch
{
    [activeTouches setObject:ball forKey:[NSValue valueWithPointer:(__bridge void*)touch]];
}
- (Ball*)getBallTouchedBy:(UITouch*)touch
{
    return [activeTouches objectForKey:[NSValue valueWithPointer:(__bridge void*)touch]];
}
- (void)stopTouch:(UITouch*)touch
{
    [activeTouches removeObjectForKey:[NSValue valueWithPointer:(__bridge void*)touch]];
}
- (void)ball:(Ball*)ball1 hasCollidedWith:(Ball*)ball2
{
    [currentCollisions addObject:ball1];
    [currentCollisions addObject:ball2];
    ball1.color = ball2.color = [UIColor yellowColor];
    [ball1 setNeedsDisplay];
    [ball2 setNeedsDisplay];
}

// NOTE: You should delegate handling of collisions...
- (void)checkForCollisions
{
    // Should filter those that don't actually need redisplay...
    // For simplicity, all that were or are colliding get it...
    [currentCollisions enumerateObjectsUsingBlock:^(Ball *ball, BOOL *stop) {
        [ball setNeedsDisplay];
        ball.color = ball.origColor;
    }];
    [currentCollisions removeAllObjects];
    NSArray *views = self.view.subviews;
    for (size_t i = 0; i < views.count; ++i) {
        id v = [views objectAtIndex:i];
        if (![v isKindOfClass:[Ball class]]) continue;
        Ball *ball1 = v;
        for (size_t j = i+1; j < views.count; ++j) {
            v = [views objectAtIndex:j];
            if (![v isKindOfClass:[Ball class]]) continue;
            Ball *ball2 = v;
            if ([ball1 doesIntersectWith:ball2]) {
                [self ball:ball1 hasCollidedWith:ball2];
            }
        }
    }
}
- (void)addBallWithRadius:(CGFloat)radius point:(CGPoint)point color:(UIColor*)color
{
    Ball *ball = [Ball ballWithRadius:radius color:color];
    ball.center = point;
    [self.view addSubview:ball];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        CGPoint touchLocation = [touch locationInView:self.view];
        for (Ball *ball in self.view.subviews) {
            if ([ball containsPoint:touchLocation]) {
                [self ball:ball touched:touch];
            }
        }
    }
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        // Use relative distance so center does nt jump to tap location.
        CGPoint prev = [touch previousLocationInView:self.view];
        CGPoint curr = [touch locationInView:self.view];
        CGPoint c = [self getBallTouchedBy:touch].center;        
        [self getBallTouchedBy:touch].center = CGPointMake(c.x+curr.x-prev.x, c.y+curr.y-prev.y);
    }
    // NOTE: This does not check the path between the moves, only the new point.
    // So a fast movement "past" the other object will not get detected.
    // If you want to do comple detection, use an existing engine
    [self checkForCollisions];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        [self stopTouch:touch];
    }
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesEnded:touches withEvent:event];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    activeTouches = [NSMutableDictionary dictionary];
    currentCollisions = [NSMutableSet set];
    [self addBallWithRadius:20 point:CGPointMake(110, 100) color:[UIColor blueColor]];
    [self addBallWithRadius:20 point:CGPointMake(200, 200) color:[UIColor redColor]];
    [self addBallWithRadius:20 point:CGPointMake(235, 250) color:[UIColor greenColor]];
}

Upvotes: 1

Related Questions