Search code examples
iossprite-kitskspritenodeskphysicsbody

Node with Two Physics Bodies


I know that this question has been "asked" here but it was not helpful, nor did it cover what I am asking.

That being said, I want to know how to achieve the following:

  • I need a node that has 2 physics bodies: 1) a smaller body (200x200) that represents the node's frame, and 2) a larger body (500x500) that represents an outer perimeter of an area that will act as a "detection" boundary. The outer body will not physically impact colliding objects, it just needs to register that an object has encountered that boundary line. Below is a simple idea of what I am describing:

enter image description here

My initial approach was to use SKPhysicsBody bodyWithBodies:<NSArray> but that would have just created the overlap body as the smaller rect.

So I figured I would have to create an SKSpriteNode for the smaller body (the black square), and another SKSpriteNode for the larger body (the blue square), and add it as a child to the smaller square (or vice versa).

My problem is that I can't seem to get the outer boundary to allow another object to pass through while registering that interaction. The closest I have come is getting a movable node to hit the boundary, then continually get pushed back and forth along the line of the 500x500 boundary as long as I attempt to pass through the boundary. I have been able to make the moving node UNABLE to pass through the 500x500, but this is quite the opposite of what I need.

Here is the code that I am using, perhaps someone can see something I am doing wrong:

/// TESTING

// Detection body - 500x500
SKSpriteNode *dbody = [SKSpriteNode spriteNodeWithImageNamed:@"object500"];
dbody.position = CGPointMake(500, 100);
dbody.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:dbody.frame];
dbody.physicsBody.categoryBitMask = detection;
dbody.physicsBody.collisionBitMask = 0;
dbody.physicsBody.contactTestBitMask = player;

// Node body - 200x200
SKSpriteNode *testBoundary = [SKSpriteNode spriteNodeWithImageNamed:@"object"];
testBoundary.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:testBoundary.frame];
testBoundary.physicsBody.categoryBitMask = object;
testBoundary.physicsBody.contactTestBitMask = player;
testBoundary.position = dbody.position;

// Add boundary as child to main node
[testBoundary addChild:dbody];

// Add to scene
[chapterScene addChild:dbody];

All of this is inside LevelScene.m, an SKScene set with <SKPhysicsContactDelegate> in the header. Also, I have defined chapterScene as such:

// Set up main chapter scene
self.anchorPoint = CGPointMake(0.5, 0.5); //0,0 to 1,1
chapterScene = [SKNode node];
[self addChild:chapterScene];

, with chapterScene defined at the top of LevelScene.

@interface LevelScene () {

#pragma 1 Scene objects
    SKNode *chapterScene;

}

If someone can help me figure out what I am doing wrong, or perhaps an alternative approach that would give me what I am looking for I would appreciate it.

NOTE: My backup solution would be seemingly expensive in terms of CPU and memory, but I believe it would work nonetheless. I would have a BOOL isDetected to represent if the moving player node IS within the 500x500 area, then in update I would monitor the distance to a smaller 200x200 rect boundary of the center of the 500x500 node. But I would much rather use the 2 SKPhysicsBody's as I am sure it can be done and would be much easier to implement.

UPDATE: here is my moving character (a separate SKNode class called Character.m)

- (void)createCharacterWithName:(NSString *)charName {

    character = [SKSpriteNode spriteNodeWithImageNamed:charName];
    [self addChild:character];

    self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:character.frame.size.width/2.0];
    self.physicsBody.dynamic = YES;
    self.physicsBody.allowsRotation = YES;

    // Define physics body relationships
    self.physicsBody.categoryBitMask = player;
    self.physicsBody.collisionBitMask = player;
    self.physicsBody.contactTestBitMask = detection;

}

Here is an image of my simulator (it is a little different, in terms of sizes, as my description. My description was for ease of understanding not the exact scaling of the nodes) enter image description here


Solution

  • this is working for me. sorry i didnt write it on obj c. just a lot faster for me in swift

    import SpriteKit
    
    let CategoryOuter:UInt32     = 1 << 0
    let CategoryInner:UInt32     = 1 << 1
    let CategoryTester:UInt32    = 1 << 2
    
    class GameScene: SKScene, SKPhysicsContactDelegate {
    
        let outerSprite = SKSpriteNode(color: SKColor.redColor(), size: CGSizeMake(100, 100))
        let innerSprite = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(40, 40))
        let tester = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(10, 10))
    
        override init(size: CGSize) {
            super.init(size: size)
            physicsWorld.contactDelegate = self
            physicsWorld.gravity = CGVectorMake(0, 0)
    
            outerSprite.position = CGPointMake(size.width/2, size.height/2)
            outerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: outerSprite.size)
            outerSprite.physicsBody!.dynamic = false
            outerSprite.physicsBody!.categoryBitMask = CategoryOuter
            outerSprite.physicsBody!.collisionBitMask = 0
            outerSprite.physicsBody!.contactTestBitMask = CategoryTester
    
            innerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: innerSprite.size)
            innerSprite.physicsBody!.dynamic = false
            innerSprite.physicsBody!.categoryBitMask = CategoryInner
            innerSprite.physicsBody!.collisionBitMask = CategoryTester
            innerSprite.physicsBody!.contactTestBitMask = CategoryTester
    
            outerSprite.addChild(innerSprite)
            addChild(outerSprite)
    
            tester.physicsBody = SKPhysicsBody(rectangleOfSize: tester.size)
            tester.physicsBody!.categoryBitMask = CategoryTester
            tester.physicsBody!.collisionBitMask = CategoryInner
            addChild(tester)
        }
    
        func didBeginContact(contact: SKPhysicsContact) {
            let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
    
            if collision == (CategoryOuter | CategoryTester) {
                print("outer collision")
            }
            else if collision == (CategoryInner | CategoryTester) {
                print("inner collision")
            }
        }
    
        override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
            if let touch = touches.first {
                let location = touch.locationInNode(self)
                tester.position = location
            }
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }