Search code examples
objective-csprite-kitskphysicsjoint

Sprite Kit SKPhysicsJoint


I'm trying to experiment with Sprite Kit's joint system. So far, I've had difficulty making even a single strand of "string"/"rope". The end goal is to produce something like a basketball net where there will be 1 left rope (vertical), 1 right rope (vertical) & at least 2 ropes in-between connecting the left & right rope (horizontally) - 4 ropes in total. Afterwards, figuring out how to make the 2 horizontal ropes not collide with an object (cannonball?) when it passes through them. Does anyone know how to do something like this? I've been trying this for 2 weeks and my eyes & fingers are sore.


Solution

  • You can connect several physics bodies together with limit joints to create a rope like structure which is affected by collisions with other bodies. My code is quite messy but here is the Scene class for a sprite kit project. It has a basketball net and when you tap the screen it spawns a ball.

    with screenshot here https://dl.dropboxusercontent.com/s/flixgbhfk2yysr8/screenshot.jpg?dl=0

    EDIT: sorry about that, code is now in obj-C

    EDIT: paste these methods into your SKScene subclass:

    -(void)addBasketAtPos:(CGPoint)pos WithSize:(CGSize)size HoopThickness:(CGFloat)hoopThickness RopeThickness:(CGFloat)ropeThickness GapSize:(CGFloat)gapSize {
    
        int nSect=ceil(size.height/(ropeThickness*4));
    
        SKSpriteNode *hoop=[SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(size.width+hoopThickness, hoopThickness)];
        hoop.position=pos;
        //[self addChild:hoop];
    
        SKNode *lHoopEdge=[SKNode node];
        lHoopEdge.position=CGPointMake(pos.x-size.width/2, pos.y);
        lHoopEdge.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:hoopThickness/2];
        lHoopEdge.physicsBody.dynamic=false;
        lHoopEdge.physicsBody.restitution=0.9;
        [self addChild:lHoopEdge];
        SKNode *rHoopEdge=[SKNode node];
        rHoopEdge.position=CGPointMake(pos.x+size.width/2, pos.y);
        rHoopEdge.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:hoopThickness/2];
        rHoopEdge.physicsBody.dynamic=false;
        rHoopEdge.physicsBody.restitution=0.9;
        [self addChild:rHoopEdge];
    
        NSMutableArray *rope1=[self makeRopeAtPos:lHoopEdge.position node:lHoopEdge num: nSect gap:size.height/nSect width:ropeThickness];
        NSMutableArray *rope2=[self makeRopeAtPos:rHoopEdge.position node:lHoopEdge num: nSect gap:size.height/nSect width:ropeThickness];
    
        CGFloat insetStep=(size.width-gapSize)/2/nSect;
        for (int i=0;i<rope1.count;++i) {
            SKNode *a=[rope1 objectAtIndex:i];
            SKNode *b=[rope2 objectAtIndex:i];
            a.position=CGPointMake(a.position.x+i*insetStep, a.position.y);
            b.position=CGPointMake(b.position.x-i*insetStep, b.position.y);
        }
    
        for (int i=0;i<rope1.count;++i) {
            /*
            SKNode *n1=[rope1 objectAtIndex:i];
            SKNode *n2=[rope2 objectAtIndex:i];
            SKPhysicsJointLimit* joint=[self joinBodyA:n1.physicsBody bodyB:n2.physicsBody ropeThickness:ropeThickness];
            [self.physicsWorld addJoint:joint];
             */
            if (i>0) {
                [self.physicsWorld addJoint:[self joinBodyA:((SKNode*)[rope1 objectAtIndex:i]).physicsBody
                                                      bodyB:((SKNode*)[rope2 objectAtIndex:i-1]).physicsBody
                                              ropeThickness:ropeThickness]];
                [self.physicsWorld addJoint:[self joinBodyA:((SKNode*)[rope2 objectAtIndex:i]).physicsBody
                                                      bodyB:((SKNode*)[rope1 objectAtIndex:i-1]).physicsBody
                                              ropeThickness:ropeThickness]];
            }
            if (i==rope1.count-1) {
                [self.physicsWorld addJoint:[self joinBodyA:((SKNode*)[rope1 objectAtIndex:i]).physicsBody
                                                      bodyB:((SKNode*)[rope2 objectAtIndex:i]).physicsBody
                                              ropeThickness:ropeThickness]];
            }
        }
    
        [self addChild:hoop];
    }
    
    -(NSMutableArray*)makeRopeAtPos:(CGPoint)p node:(SKNode*)node num:(int)num gap:(CGFloat)gap width:(CGFloat)width {
        NSMutableArray *array=[[NSMutableArray alloc] init];
        SKNode *last=node;
        CGPoint pos=p;
        CGPoint anchor=pos;
        for (int i=0; i<num; ++i) {
            pos=CGPointMake(pos.x, pos.y-gap);
            last=[self makeRopeSegAtPos:pos prev:last anchor:anchor first:(i==0) width:width];
            anchor=pos;
            [array addObject:last];
        }
        return array;
    }
    
    -(SKNode*)makeRopeSegAtPos:(CGPoint)pos prev:(SKNode*)prev anchor:(CGPoint)anchor first:(BOOL)first width:(CGFloat)width {
        //SKNode *r=[SKNode node];
        SKSpriteNode *r=[SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(anchor.y-pos.y, width)];
        r.position=pos;
        r.anchorPoint=CGPointMake(0, 0.5);
        if (first) {
            //r.size=CGSizeMake(10, 20);
            r.constraints=@[[SKConstraint orientToPoint:anchor inNode:self offset:nil]];
        }else {
            r.constraints=@[[SKConstraint orientToNode:prev offset:nil]];
        }
        r.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:width];
        r.physicsBody.allowsRotation=NO;
        //r.physicsBody.density=0.2;
        r.physicsBody.linearDamping=0.8;
        [self addChild:r];
        SKPhysicsJointLimit *j=[SKPhysicsJointLimit jointWithBodyA:r.physicsBody bodyB:prev.physicsBody anchorA:r.position anchorB:anchor];
        [self.physicsWorld addJoint:j];
        return r;
    
    }
    
    -(SKPhysicsJoint*)joinBodyA:(SKPhysicsBody*)bodyA bodyB:(SKPhysicsBody*)bodyB ropeThickness:(CGFloat)ropeThickness {
    SKPhysicsJointLimit *j=[SKPhysicsJointLimit jointWithBodyA:bodyA bodyB:bodyB anchorA:bodyA.node.position anchorB:bodyB.node.position];
    SKSpriteNode *rope=[SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(j.maxLength, ropeThickness)];
    
    rope.anchorPoint=CGPointMake(0, 0.5);
    rope.constraints=@[[SKConstraint distance:[SKRange rangeWithUpperLimit:1] toNode:bodyA.node],[SKConstraint orientToNode:bodyB.node offset:nil]];
    SKAction *keepLength=[SKAction repeatActionForever:[SKAction sequence:@[[SKAction waitForDuration:1/60],[SKAction runBlock:^{
        rope.size=CGSizeMake(sqrtf(powf(bodyA.node.position.x-bodyB.node.position.x, 2)+powf(bodyA.node.position.y-bodyB.node.position.y, 2)), rope.size.height);
    }]]]];
    [rope runAction:keepLength];
    [self addChild:rope];
    return j;
    

    }

    use

    [self addBasketAtPos: CGPointMake(self.size.width/2, self.size.height*0.7)
                WithSize: CGSizeMake(100, 100)
           HoopThickness: 10
           RopeThickness: 5
                 GapSize: 80
    ];
    

    to add a basket

    see new screenshot here: https://dl.dropboxusercontent.com/s/xe07ri70rgpxp47/screenshot%202.jpg?dl=0