Make SKPhysicsBody react to contact but not to collide

abito per sposarsi in comune As you know, Sprite Kit offers you a great and simple physics engine. You can easily setup collisions and contact detection between nodes.

abito velluto Sometimes you might just want to react to contact but you don’t want to show collision, you don’t want two bodies to “bounce” from each other.

spritenodecontact

You can see below an example where you get callbacks when two SKSpriteNodes contact but they will not “bounce” from each other.

This is how you setup this kind of behaviour:

first be sure that your SKScene(or any object that has the node) responds to SKPhysicsContactDelegate:

@interface YourScene : SKScene <SKPhysicsContactDelegate>
@end

and set “self” as the delegate:

self.physicsWorld.contactDelegate = self;

node categories for contact detection:

static const uint32_t firstNodeTypeCategory = 0x1 << 1;
static const uint32_t secondNodeTypeCategory = 0x1 << 2;

setup physics:

//somewhere in your code you have two SKSpriteNodes created as you like it 🙂
//SKSpriteNode *firstNode, *secondNode
//
//setup physicsBody:
firstNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:firstNode.size];
secondNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:secondNode.size];
//setup category masks so you know which is which node type:
firstNode.physicsBody.categoryBitMask = firstNodeTypeCategory;
secondNode.physicsBody.categoryBitMask = secondNodeTypeCategory;
//
//contact part
//A mask that defines which categories of bodies cause intersection notifications with this physics body.
firstNode.physicsBody.contactTestBitMask = secondNodeTypeCategory;
secondNode.physicsBody.contactTestBitMask = firstNodeTypeCategory;
//
//collision part - set the mask to be category of the node to have the effect that we need
firstNode.physicsBody.collisionBitMask = firstNodeTypeCategory;
secondNode.physicsBody.collisionBitMask = secondNodeTypeCategory;

And the last part is to react to contact callback:

-(void)didBeginContact:(SKPhysicsContact*)contact
{
    SKPhysicsBody *firstBody, *secondBody;
    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    }
    else{
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }
    NSLog(@"first body %@",firstBody);
    NSLog(@"second %@",secondBody);

    if ((firstBody.categoryBitMask & firstNodeTypeCategory) != 0 &&
        (secondBody.categoryBitMask & secondNodeTypeCategory) != 0) {
        NSLog(@"CORRECT CONTACT!");
    }
}

Hope this helps you in some of you games 🙂

If you want to learn more about Sprite Kit, you can check out the documentation on Apple website.

Posted in Apple, featured, Games, iOS, iOS development, iPad, iPhone, Seodoa, Sprite Kit Tagged with: , , ,
18 comments on “Make SKPhysicsBody react to contact but not to collide
  1. NIX says:

    Why don’t just try set `collisionBitMask = 0` in both physicsBodys?

    • bozidar says:

      Hi!

      In this case you could do that. But it is a better practice not to use exact number but use variable like:
      static const uint32_t firstNodeTypeCategory = 0x1 << 1; so you can differentiate types/categories of your objects. If you set it to exact number like "0" and not use variable like firstNodeTypeCategory if you read the code later some time you might not know what was the effect you were trying to make. That is why I set the collisionBitMask of the node to its own category. So that it only collides with its own kind. You could for example make it like this: static const uint32_t noCollideNodeTypeCategory = 0x1 << 1; static const uint32_t firstNodeTypeCategory = 0x1 << 2; static const uint32_t secondNodeTypeCategory = 0x1 << 3; And set collisionBitMask to just noCollideNodeTypeCategory on nodes that you want not to collide with any other nodes. But then you should not set the categoryBitMask of any object to noCollideNodeTypeCategory. If you have different scenario, with other types of node, you might set it up differently.

  2. Tommy says:

    Is there a method I can call from touchesEnded to see if a collision occurred after the user drags an SKSpriteNode over another SKSpriteNode?
    Thanks,
    Tommy

    • bozidar says:

      I don’t know any specific method from sprite kit but you could code it yourself. In touches ended you can check with CGRectContainsRect if one of the nodes contains the other.
      if(CGRectContainsRect(firstnode.frame, secondnode.frame))
      {
      //they are overlaping
      }
      But if you want to check it like this, you should set theirs physics bodies so they do not physicaly collide.

      • Parker says:

        Hey, I’m using CGRectContainsRect to check for overlap and it works. The problem I’m having is that I’m using SKShapeNode objects that are lines, and when drawn diagonally, the frame is a large box drawn around the line, with lots of clear space that isn’t visible but still counts for my hit overlap detection. Do you know a way to wrap the frame around the actual drawn line, or another way around this? Thanks, Parker

  3. Parker says:

    Here is where I make it, getting the xw and yw is a big chunk of code so I didn’t include it, basically it just makes the frame start from where it should, not (0,0).
    ********************

    LINE = [SKShapeNode node];
    LINE.position = CGPointMake(xw, yw);

    pathToDraw = CGPathCreateMutable();
    CGPathMoveToPoint(pathToDraw, NULL, startPoint.x-xw, startPoint.y-yw);
    if (isLongah){
    CGPathAddLineToPoint(pathToDraw, NULL, x-xw, y-yw);
    } else {
    CGPathAddLineToPoint(pathToDraw, NULL, currentLoc.x-xw, currentLoc.y-yw);
    }
    LINE.path = pathToDraw;

    [LINE setStrokeColor:[UIColor redColor]];

    LINE.lineWidth = 5;
    LINE.zPosition = 3;

    [self addChild:LINE];

    • bozidar says:

      I will reply you in the morning when I check something 😉

    • bozidar says:

      This code make just a straight line. Are you trying to make a node that is just a straight line or? If you are, I would suggest to you just to make SKSpriteNode node with some color and then rotate it. For example:
      SKSpriteNode *node = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 5)];
      node.position = somePoint;
      node.zRotation = M_PI/4.0;
      [self addChild:node];
      This will make a straight line node.

      Could you explain a bit more the use case where you are creating this node?

      • Parker says:

        I am trying to make a straight line. The line is created from where the you touch to where you release the touch(touchesEnded). Then the line remains, until you draw another one, and other objects should run into it constantly.

        • bozidar says:

          if that is the case, I would recommend to you to use SKSpriteNode. And every time you would change the ending of that line you just calculate the correct position and size of the node so it looks like you are drawing a straight line.
          For example if you have:
          CGPoint startingPoint; and CGPoint endPoint; variables(the bottom left is (0,0))
          then you change “yourNode” like this:
          //I suppose you have some node like this with fixes height(5) and other properties like color, etc

          //SKSpriteNode *yourNode = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 5)];
          //CGPoint startingPoint, endPoint;
          BOOL startingIsHigher = NO;
          if (startingPoint.y > endPoint.y) {
          startingIsHigher = YES;
          }
          if(startingPoint.x > endPoint.x)
          {//change swap the variables so the starting point is the one on the left
          CGPoint tmp = startingPoint;
          startingPoint = endPoint;
          endPoint = tmp;
          }
          //calculate the new width for the node and calculate the position variable
          CGFloat xDist = fabsf(endPoint.x - startPoint.x); //fabs so we have absolute value
          CGFloat yDist = fabsf(endPoint.y - startPoint.y); //
          CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
          //now change yourNode properties:
          //size
          CGSize newSize = yourNode.size;
          newSize.width = distance;
          yourNode.size = newSize;
          //position
          CGFloat newYposition = 0;
          if (startingIsHigher)
          {
          newYposition = endPoint.y + yDist/2.0;
          }
          else
          {
          newYposition = startingPoint.y + yDist/2.0;
          }
          CGPoint newPosition = CGPointMake(startingPoint.x + xDist/2.0, newYposition);
          yourNode.position = newPosition;
          //rotation
          CGFloat rotation = atanf(yDist/xDist);
          if (startingIsHigher) {
          yourNode.zRotation = -rotation;
          }
          else yourNode.zRotation = rotation;

          • Parker says:

            Thanks again, this helps me so much. With this method the collision works perfectly using [intersectsNode]. There is one last problem, though. In the cases where the startingPoint and the endPoint need to be switched(when dragging right to left) the resulting line is drawn flipped over the x-value of the startingPoint(if the startPoint is higher than the endPoint), and over the x-value of the endPoint(if the endPoint is higher). I’ve tried to debug it myself but I’m honestly just learning some of this math, I hope I’ve explained the problem well enough.
            -Parker

          • bozidar says:

            hm… it should be the same like when going left to right.. I am not sure now how to fix it but when I do, I will post it here in reply 😉

          • bozidar says:

            I will send you email now so we can communicate faster and when we find solution we will post it here 🙂

          • bozidar says:

            As I said in email. Just need to move the part where I check if starting point is higher after the swapping part 😀
            You can delete NSLogs but I added them just to debug it 😀
            if(startingPoint.x > endPoint.x)
            {//change swap the variables so the starting point is the one on the left
            CGPoint tmp = startingPoint;
            startingPoint = endPoint;
            endPoint = tmp;
            }
            BOOL startingIsHigher = NO;
            if (startingPoint.y > endPoint.y) {
            startingIsHigher = YES;
            }
            NSLog(@"starting %f %f ending %f %f",startingPoint.x,startingPoint.y,endPoint.x,endPoint.y);
            //calculate the new width for the node and calculate the position variable
            CGFloat xDist = fabsf(endPoint.x - startingPoint.x); //fabs so we have absolute value
            CGFloat yDist = fabsf(endPoint.y - startingPoint.y); //
            CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
            //now change yourNode properties:
            //size
            CGSize newSize = yourNode.size;
            newSize.width = distance;
            yourNode.size = newSize;
            //position
            CGFloat newYposition = 0;
            if (startingIsHigher)
            {
            newYposition = endPoint.y + yDist/2.0;
            NSLog(@"higher");
            }
            else
            {
            newYposition = startingPoint.y + yDist/2.0;
            NSLog(@"not higher");
            }
            CGPoint newPosition = CGPointMake(startingPoint.x + xDist/2.0, newYposition);
            yourNode.position = newPosition;
            //rotation
            CGFloat rotation = atanf(yDist/xDist);
            NSLog(@"ydist %f xdist %f rotation %f",yDist,xDist,rotation);
            if (startingIsHigher) {
            yourNode.zRotation = -rotation;
            }
            else yourNode.zRotation = rotation;

  4. Kubilay says:

    Hey, thank you! You just made my game better.

Leave a Reply

Your email address will not be published. Required fields are marked *

*