How to build a physical object hanging on chains for your game with SKPhysics and SKPhysicsJointPins. Here is the class code for a free configurable object with chains like in this video
The class can be called like this
1 2 3 4 5 6 |
[WWPot addPot:CGPointMake(220, 600) secondAnchorPos:CGPointMake(700, 600) ropeWidth:20 potWidth:270 game:game potId:0]; |
The first argument is the position of the left first anchor where the chain will be fixed. „secondAnchorPos“ is doing the same for the right side. „ropeWidth“ configures the length of the chains. „potWidth“ describes the size of the hanging pot in the middle. The height will be scaled automatically. The „game„-argument should be your SKScene where the whole stuff would be added. At last „potId“ should be a unique number, so you can add more of these objects to your Sprite Kit SKScene avoiding naming conflicts.
The physic bodies are arranged with lots of SKPhysicsJointPins as you can see in this debug view.
Here comes the complete class code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
+(void) addPot:(CGPoint)firstAnchorPos secondAnchorPos:(CGPoint)secondAnchorPos ropeWidth:(int)ropeWidth potWidth:(float)potWidth game:(SKScene*)game potId:(int)potId { //make the pot //load the box SKSpriteNode* pot = [SKSpriteNode spriteNodeWithImageNamed:@"pot.png"]; pot.name = [NSString stringWithFormat:@"pot_%d", potId]; pot.zPosition = 3; pot.size = CGSizeMake(potWidth, potWidth/1.69); pot.position = CGPointMake(firstAnchorPos.x , firstAnchorPos.y); pot.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: pot.size]; pot.physicsBody.mass = 0.1; [game addChild:pot]; //make the first rope [self addRopeJointItems:firstAnchorPos countJointElements:ropeWidth game:game potId:potId ropeNumber:1]; //make the second rope [self addRopeJointItems:secondAnchorPos countJointElements:ropeWidth game:game potId:potId ropeNumber:2]; pot.position = CGPointMake(firstAnchorPos.x + ((secondAnchorPos.x - firstAnchorPos.x) / 2), secondAnchorPos.y); } +(void) addRopeJointItems:(CGPoint)leftStartPosition countJointElements:(int)countJointElements game:(SKScene*)game potId:(int)potId ropeNumber:(int)ropeNumber { float itemJointWidth ; if ( IDIOM != IPAD ) { itemJointWidth = 5; } else { itemJointWidth = 8; } //first Physics Anchor SKSpriteNode * firstAnchor = [SKSpriteNode spriteNodeWithImageNamed:@"dummypixel_transparent.png"]; firstAnchor.position = CGPointMake(leftStartPosition.x, leftStartPosition.y); firstAnchor.size = CGSizeMake(1, 1); firstAnchor.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: firstAnchor.size]; firstAnchor.physicsBody.affectedByGravity = false; firstAnchor.physicsBody.mass = 99999999999; firstAnchor.name = @"potRopeAnchorLeft"; [game addChild:firstAnchor]; //second Physics Anchor SKSpriteNode* secondAnchor = (SKSpriteNode*)[game childNodeWithName:[NSString stringWithFormat:@"pot_%d", potId]]; //add RopeElements for (int i=0; i<countJointElements; i++) { SKSpriteNode * item = [SKSpriteNode spriteNodeWithImageNamed:@"chain_piece.png"]; item.name = [NSString stringWithFormat:@"%d_%d_ropeitem_%d", potId, ropeNumber, i]; if(ropeNumber == 1) { item.position = CGPointMake(leftStartPosition.x + (i*itemJointWidth) + itemJointWidth/2, leftStartPosition.y); } else { item.position = CGPointMake(leftStartPosition.x - (i*itemJointWidth) - itemJointWidth/2, leftStartPosition.y); } if ( IDIOM != IPAD ) { item.size = CGSizeMake(itemJointWidth + 2, 2); } else { item.size = CGSizeMake(itemJointWidth + 5, 10); } item.zPosition = 6; item.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: item.size]; //item.physicsBody.categoryBitMask = kNilOptions; [game addChild:item]; //Add Joint to the element before SKPhysicsBody* bodyA; if (i == 0) { bodyA = firstAnchor.physicsBody; } else { bodyA = [game childNodeWithName:[NSString stringWithFormat:@"%d_%d_ropeitem_%d", potId, ropeNumber, i-1]].physicsBody; } SKPhysicsJointPin* joint; joint = [SKPhysicsJointPin jointWithBodyA:bodyA bodyB:item.physicsBody anchor:CGPointMake((item.position.x - item.size.width/2) + 5, item.position.y)]; [game.physicsWorld addJoint:joint]; if (i == countJointElements -1 && ropeNumber == 1) { [game childNodeWithName:[NSString stringWithFormat:@"pot_%d", potId]].position = CGPointMake(item.position.x+secondAnchor.size.width/2, item.position.y-secondAnchor.size.height/2); } else { [game childNodeWithName:[NSString stringWithFormat:@"pot_%d", potId]].position = CGPointMake(item.position.x-secondAnchor.size.width/2, item.position.y-secondAnchor.size.height/2); } } //Add the Last Joint SKPhysicsJointPin* jointLast; if (ropeNumber == 1) { jointLast = [SKPhysicsJointPin jointWithBodyA: [game childNodeWithName:[NSString stringWithFormat:@"%d_%d_ropeitem_%d", potId, ropeNumber, countJointElements - 1]].physicsBody bodyB:secondAnchor.physicsBody anchor:CGPointMake((secondAnchor.position.x-secondAnchor.size.width/2), secondAnchor.position.y+secondAnchor.size.height/2)]; } else { jointLast = [SKPhysicsJointPin jointWithBodyA: [game childNodeWithName:[NSString stringWithFormat:@"%d_%d_ropeitem_%d", potId, ropeNumber,countJointElements - 1]].physicsBody bodyB:secondAnchor.physicsBody anchor:CGPointMake(secondAnchor.position.x+secondAnchor.size.width/2, secondAnchor.position.y+secondAnchor.size.height/2)]; } [game.physicsWorld addJoint:jointLast]; } |
really cool effect
rope physics are a cool thing to add in your game
thanks for sharing thins code
thx for the comment 🙂
Great blog and great post! I have been working on a sprite kit game in my free time for almost a year now and the main mechanic is using ropes. I occasionally see a bug where the joints break but yours look very stable in this video. I’m looking at your code now to see how you did it differently than I did. Thanks again!
no prob, glad that it helps 🙂