I made a little prototype game and so far everything works fine, collision also. Now I want to optimize some stuff. I have creeps with different properties and I want to put them in an array. But I don't know how to check the objects in an array for collision because the command is expecting a SKnode I guess. Here I am defining the creeps and the array:
SKSpriteNokde *creep1 = [SKSpriteNode spriteNodeWithTexture:_creepTexture1];
[creep1 setScale:1];
creep1.position = CGPointMake( 10, y )
creep1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:creep1.frame.size];
creep1.physicsBody.dynamic = NO;
[CreepPair addChild:creep1];
SKSpriteNokde *creep2 = [SKSpriteNode spriteNodeWithTexture:_creepTexture2];
[creep2 setScale:2];
creep2.position = CGPointMake( 50, y + creep1.size.height);
creep2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:creep2.frame.size];
creep2.physicsBody.dynamic = NO;
[CreepPair addChild:creep2];
..
SKSpriteNode *Level1 = [NSArray arrayWithObjects:creep1,creep2,creep3,creep4,nil];
Here I am checking for collision and this works:
if ([creep1 intersectsNode:Player] {
creep1.hidden = YES;
NSLog(#"Lost 1 Life!");
}
But I want to check all creeps like this:
if ([Level1.allobjects intersectsNode:Player] {
creep1.hidden = YES;
NSLog(#"Lost 1 Life!");
}
The last code obviously don't work, but how can I manage this?
This might work
for (SKSpriteNokde *creep in Level1)
{
if ([creep intersectsNode:Player] {
creep.hidden = YES;
NSLog(#"Lost 1 Life!");
}
}
here I assume that Level1 is array.
Related
I have a hero, ground and a table. I want to make a table-top active for collision and contact with hero. But hero should be able not to jump on the top and just run "through" and jump on it, if player wants it, wherever he wants. For better example of what i'm trying to achieve - think about Mario. When you are running on ground, some sky platforms appearing. You could jump on it in the middle of a platform and stay there. So I need physics body to not stop hero when he is contacting it from the bottom, but hold him if he is on top of it.
By now i'm using body with texture for table:
self.table.physicsBody = SKPhysicsBody(texture:table.texture, size:self.table.size)
self.table.physicsBody?.dynamic = false
self.table.physicsBody?.categoryBitMask = ColliderType.Table.rawValue
self.table.physicsBody?.contactTestBitMask = ColliderType.Hero.rawValue
self.table.physicsBody?.collisionBitMask = ColliderType.Hero.rawValue
It obviously, is not working. How can I implement such a thing?
The answer to this question is actually not too difficult but the implementation into a full fledged game will be much more difficult for you. This is not something for a novice programmer to start out with.
First the same code project (tap/click on screen to jump up):
#import "GameScene.h"
typedef NS_OPTIONS(uint32_t, Level1PhysicsCategory) {
CategoryPlayer = 1 << 0,
CategoryFloor0 = 1 << 1,
CategoryFloor1 = 1 << 2,
};
#implementation GameScene {
int playerFloorLevel;
SKSpriteNode *node0;
SKSpriteNode *node1;
SKSpriteNode *node2;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor whiteColor];
node0 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(400, 10)];
node0.position = CGPointMake(300, 200);
node0.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node0.size];
node0.physicsBody.dynamic = NO;
node0.physicsBody.categoryBitMask = CategoryFloor0;
node0.physicsBody.collisionBitMask = CategoryPlayer;
[self addChild:node0];
node1 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(400, 10)];
node1.position = CGPointMake(300, 300);
node1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node1.size];
node1.physicsBody.dynamic = NO;
node1.physicsBody.categoryBitMask = CategoryFloor1;
node1.physicsBody.collisionBitMask = CategoryPlayer;
[self addChild:node1];
node2 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(50, 50)];
node2.position = CGPointMake(300, 250);
node2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node2.size];
node2.physicsBody.categoryBitMask = CategoryPlayer;
node2.physicsBody.collisionBitMask = CategoryFloor0;
[self addChild:node2];
playerFloorLevel = 0;
}
-(void)update:(CFTimeInterval)currentTime {
if(((node2.position.y-25) > (node1.position.y+10)) && (playerFloorLevel == 0)) {
node2.physicsBody.collisionBitMask = CategoryFloor0 | CategoryFloor1;
playerFloorLevel = 1;
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
//SKNode *node = [self nodeAtPoint:touchLocation];
// change 75 value to 50 to see player jump half way up through floor 1
[node2.physicsBody applyImpulse:CGVectorMake(0, 75)];
}
}
The gist of the code is the player node (node2) has to keep checking its y position (update method) in relation to the other floors. In the example, the player jumps up through floor1. Once the player is higher than floor1, the player node's physics body modifies its collision bit mask to include floor1.
Sounds easy enough. However, in a real game you will have a large number of floors and all floors might not be evenly spaced y distances. You have to keep all that in mind when coding.
I am not sure how your platforms looks like (edge based or volume based bodies) but you can consider some of these:
1. Checking positions
Check if the hero's position.y is beneath/above the platform and ignore/handle the collision.
2. Checking velocity
Or to check if player node if falling, which is indicated by a negative velocity.dy value.
I can't say if any of these can fully help you with your game or is it possible with your setup, but you can get some basic idea on where to start.
Enabling/disabling collisions can be done by changing player's and platform's collision bitmasks. If possible try to avoid tracking states like isInTheAir, isOnPlatform, isFaling, isJumping and similar because it can become messy as number of states grows. For example, instead of adding custom boolean variable called "isFalling" and constantly maintaining its state, you can check if velocity.dy is negative to see if player is falling.
I tried changing the platforms collision bitmask but wasn't working fine. I found a different solution.
Inside the update() function you can check the following
if player.physicsBody?.velocity.dy <= 0 {
player.physicsBody?.collisionBitMask = PhysicsCategory.Platform
} else {
player.physicsBody?.collisionBitMask = PhysicsCategory.None
}
In this way, every time the player is going up, it can pass through rocks, and every time it is falling, it can stand.
Using swift you can create a sprite node subclass like this:
class TableNode: SKSpriteNode {
var isBodyActivated: Bool = false {
didSet {
physicsBody = isBodyActivated ? activatedBody : nil
}
}
private var activatedBody: SKPhysicsBody?
init(texture: SKTexture) {
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
// physics body setup. Assuming anchorPoint = (0.5, 0.5)
let bodyInitialPoint = CGPoint(x: -size.width/2, y: +size.height/2)
let bodyEndPoint = CGPoint(x: +size.width/2, y: +size.height/2)
activatedBody = SKPhysicsBody(edgeFromPoint: bodyInitialPoint, toPoint: bodyEndPoint)
activatedBody!.categoryBitMask = ColliderType.Table.rawValue
activatedBody!.collisionBitMask = ColliderType.Hero.rawValue
physicsBody = isBodyActivated ? activatedBody : nil
name = "tableNode"
}
}
Then, update all tableNodes in the gameScene:
override func didSimulatePhysics() {
self.enumerateChildNodesWithName("tableNode") {
node, stop in
if let tableNode = node as? TableNode {
// Assuming anchorPoint = (0.5, 0.5) for table and hero
let tableY = tableNode.position.y + tableNode.size.height/2
let heroY = hero.position.y - hero.size.height/2
tableNode.isBodyActivated = heroY > tableY
}
}
}
Im experiencing a problem with my Hero Character, when he lands on a moving platform, rather then moving along with it, he literally stands on the same X position until the platform moves offscreen. I searched many answers with no avail.
Here is my methods for my hero character and the moving platforms.
-(void)HeroAdd
{
_Hero = [SKSpriteNode spriteNodeWithImageNamed:#"Hero-1"];
_Hero.name = #"Daniel";
_Hero.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_Hero.size];
_Hero.physicsBody.categoryBitMask = fPlayerCategory;
_Hero.physicsBody.contactTestBitMask = fPlatformCategory | fEnemyCategory;
_Hero.physicsBody.usesPreciseCollisionDetection = YES;
_Hero.physicsBody.affectedByGravity = YES;
_Hero.physicsBody.dynamic = YES;
_Hero.physicsBody.friction = .9;
_Hero.physicsBody.restitution = 0;
_Hero.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
_Hero.position = CGPointMake(_Hero.position.x - 252, _Hero.position.y + 50);
if (self.size.width == 480) {
_Hero.position = CGPointMake(_Hero.position.x + 44, _Hero.position.y);
}
[self addChild:_Hero];
}
My moving platform code
-(void)createPlatform {
SKTexture *objectTexture;
switch (arc4random_uniform(2)) {
case (0):
objectTexture = [SKTexture textureWithImageNamed:#"shortPlatform"];
break;
case (1):
objectTexture = [SKTexture textureWithImageNamed:#"highPlatform"];
default:
break;
}
SKSpriteNode *variaPlatform = [SKSpriteNode spriteNodeWithTexture:objectTexture];
variaPlatform.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
variaPlatform.position = CGPointMake(variaPlatform.position.x + 500, variaPlatform.position.y - 140);
variaPlatform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:variaPlatform.size];
variaPlatform.physicsBody.usesPreciseCollisionDetection = YES;
variaPlatform.physicsBody.categoryBitMask = fPlatformCategory;
variaPlatform.physicsBody.contactTestBitMask = fPlatformCategory |fPlayerCategory | fEnemyCategory;
variaPlatform.physicsBody.dynamic = NO;
variaPlatform.physicsBody.affectedByGravity = NO;
SKAction *moveLeft = [SKAction moveTo:CGPointMake(180, variaPlatform.position.y) duration:3];
SKAction *moveDown = [SKAction moveTo:CGPointMake(180, -700) duration:4];
SKAction *removeFromParent = [SKAction removeFromParent];
SKAction *AllThree = [SKAction sequence:#[moveLeft, moveDown, removeFromParent]];
[self addChild:variaPlatform];
[variaPlatform runAction:AllThree];
}
Any type of information would be truly appreciated.
I ran into the same issue when I added conveyer belts into a game. Sprite Kit does not drag objects no matter how much resistance is added. You currently have 2 options.
Option 1 - Add a ledge at each end of your platform. This is the easiest to implement but is less graceful and still allows the player to slide off if he lands on the ledge.
Option 2 -
Step 1: Add code which makes the player move in sync with the horizontal moving platform. You can either use something like self.physicsBody.velocity = CGVectorMake(-50, self.physicsBody.velocity.dy); or use self.position = CGPointMake(self.position.x+10, self.position.y);. You will have to play around with the x values to sync them to the platform's speed.
Step 2: Activate the above code whenever the player makes contact with the platform and deactivate when contact is lost.
Step 3: In case the platform switches directions, set up left and right limits which notify you via contact when the platform switches direction. Depending on the platform's direction you apply either +x or -x movement values to your player.
I know this option sounds complicated but it is not. You just need to go step by step.
* EDIT to provide sample code *
This is the logic I have behind the horizontal moving platforms:
PLATFORMS
If you have more than 1 horizontal moving platform, you will need to store them in an array in the GameScene (my Levels). I have created my own class for them but you do not have to do this.
You will have to set left and right limits (invisible SKNodes with contacts) to set a BOOL property for the platform which tells the player class which way to push as each platform will probably not be the same length. This is why you need to keep a reference to each platform (hence the array).
PLAYER
When the player jumps on the platform, set a Player class property BOOL to TRUE which activates the constant left or right push depending on which way the platform is currently moving. On the flip side, losing the contact cancels the push.
// This code in my "Levels class" which is the default GameScene class.
- (void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryPlatformHorizontal | CategoryPlayer))
{
[_player setPlatformHorizontalContact:true];
for(Platform *platformObject in platformArray)
{
if(([platformObject.name isEqualToString:contact.bodyB.node.name]) || ([platformObject.name isEqualToString:contact.bodyA.node.name]))
{
_player.currentPlatform = platformObject;
}
}
}
}
- (void)didEndContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryPlatformHorizontal | CategoryPlayer))
{
[_player setPlatformHorizontalContact:false];
}
}
// This code is in Player.h
#property (strong) Platform *currentPlatform;
#property BOOL platformHorizontalContact;
// This code is in Player.m
- (void)update:(NSTimeInterval)currentTime
{
if(self.platformHorizontalContact == true)
{
if(self.currentPlatform.movingLeft == true)
{
self.physicsBody.velocity = CGVectorMake(-75, self.physicsBody.velocity.dy); // set your own value depending on your platform speed
} else {
self.physicsBody.velocity = CGVectorMake(75, self.physicsBody.velocity.dy); // set your own value depending on your platform speed
}
}
}
i'm pretty new to Objective-C and Cocos2D so go easy, I know this is basic stuff.
I'm using an array to randomly place 4 gold coin sprites on the screen, and i'm using another sprite (a dragon) to fly around and collect the coins. Obviously, I want the coin to disappear and another to randomly appear (like collecting apples in Snake). My problem is that I don't know how to reference the individual sprites. I've added some handling to the update method but a) I don't think its appropriate and b) it doesn't do anything. Please help. I think I need to utilise:
[self removeChild:sprite cleanup:YES];
But I'm not sure how. This is how i'm populating the screen with rings::
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
CCSprite *_goldRing;
NSMutableArray *_goldRings;
}
-(void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_goldRing.physicsBody.collisionType = #"Level";
_goldRing.physicsBody.sensor = TRUE;
_physicsNode.collisionDelegate = self;
_hero.physicsBody.collisionType = #"hero";
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
_meteorites = [NSMutableArray array];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
}
-(void)spawnNewGoldRing {
CGFloat randomX = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat randomY = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat rangeX = 200;
CGFloat rangeY = 300;
CCNode *goldRing = [CCBReader load:#"goldRing"];
goldRing.position = ccp(randomX * rangeX, (randomY * rangeY)+100);
[_physicsNode addChild:goldRing];
[_goldRings addObject:goldRing];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y);
if(_hero.position.x <= _hero.contentSize.width/2 * -1.5) {
_hero.position = ccp(_ground.contentSize.width + _hero.contentSize.width/2, _hero.position.y);
NSMutableArray *collectedGoldRings = nil;
for (CCNode *goldRing in _goldRings) {
CGPoint goldRingWorldPosition = [_physicsNode convertToWorldSpace:goldRing.position];
CGPoint goldRingScreenPosition = [self convertToNodeSpace:goldRingWorldPosition];
if (goldRingScreenPosition.x < -goldRing.contentSize.width) {
if (!collectedGoldRings) {
collectedGoldRings = [NSMutableArray array];
}
[collectedGoldRings addObject:goldRing];
}
}
for (CCNode *goldRingToRemove in collectedGoldRings) {
[goldRingToRemove removeFromParent];
[_goldRings removeObject:goldRingToRemove];
// for each removed goldRing, add a new one
[self spawnNewGoldRing];
}
}
}
Would it be too much to ask how to keep count of these too to display a score?
Thanks so much for any help.
EDIT*
NSLog on the array of gold rings
"<goldRing = 0x9b756b0 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
)
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Gold Ring Loaded
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Array:(
"<goldRing = 0x9b77e50 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
As I see, you are using spritebuilder, so you are using the new version of Cocos2D.
In this version you can track a collision between two types of nodes. In your case, the coins and the hero.
This is as simple as:
1) Set your coin.physicsBody.collisionType = #"coin";
2) Set your hero.physicsBody.collisionType = #"hero";
3) Implement this method:
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair hero:(CCNode *)nodeA coin:(CCNode *)nodeB{
//this method will be automatically called when the hero hits a coin.
//now call your method to remove the coin and call the method to add another coin.
}
To keep a count of the score, just make an int variable, int score;. In your didLoadFromCCB method, initialize it to 0, score=0; and inside this collision method, just do score++; and maybe do a NSLog(#"Your score:%i",score);
Hope this helps.
for things like that, you do not need to use physics. When your hero will touch a coin, just update the user score (it could be an integer) and the coin position.
in your update method do something like
for(CCSprite *gold in _goldRings){
if(CGRectIntersectsRect(_hero.boundingBox,gold.boundingBox)){
//touched a coin
//do whatever u want
//do not remove the coin from the array, just change it's position!
_userPoints += 1;
gold.position=ccp(arc4random()%200 , arc4random()%300);
break;
}
}
I am trying to create a game where a character runs forever to the right (the game is landscape). On the ground there are spikes that the character can jump over. Currently, I am creating a new (and somewhat random) set of spikes in almost a checkpoint-like style where once the character reaches a certain distance, the next set of randomly organized spikes are created and the checkpoint distance gets pushed back and so on. Along with the spikes, I have a separate but very similar checkpoint-like system that is used to create the tiles that make up the ground.
This is my code for that portion, 'endlessX' and 'endlessGroundX' are the checkpoint value:
- (void) didSimulatePhysics {
if (player.position.x > endlessX) {
int random = player.position.x + self.frame.size.width;
[self createSpike:random];
endlessX += self.frame.size.width/2.2 + arc4random_uniform(30);
}
if (player.position.x + self.frame.size.width > endlessGroundX) {
[self createGround:endlessGroundX];
endlessGroundX += tile1.frame.size.width;
}
[self centerOnNode: player];
}
The parameter of the createSpike and createGround method is just the 'x' value for the SKSpriteNodes.
I am currently having it as the character itself is the one moving and the spikes and tiles are stationary. This is how I am creating the character:
-(void) createPlayer {
player = [SKSpriteNode spriteNodeWithImageNamed:#"base"];
player.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
player.name = #"player";
player.zPosition = 60;
player.xScale = 0.8;
player.yScale = 0.8;
player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player.frame.size.height/2];
player.physicsBody.mass = 1;
player.physicsBody.linearDamping = 0.0;
player.physicsBody.angularDamping = 0.0;
player.physicsBody.friction = 0.0;
player.physicsBody.restitution = 0.0;
player.physicsBody.allowsRotation = NO;
player.physicsBody.dynamic = YES;
player.physicsBody.velocity = CGVectorMake(400, 0);
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = wallCategory;
player.physicsBody.contactTestBitMask = wallCategory | spikeCategory;
[myWorld addChild:player];
}
With that, the character will never lose any of its kinetic energy to friction or any other force like that. Then, I am using the 'center on node' method that apple used in their adventure game so that the character will always remain in the same x-position on the screen:
- (void) centerOnNode: (SKSpriteNode *) node {
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(self.frame.size.width/5 + node.parent.position.x - cameraPositionInScene.x, node.parent.position.y);
}
I am calling this method in 'didSimulatePhysics.'
When I run this for some time, the programs gets slower and slower. I am guessing that that is due to the fact that I am never removing these nodes and they are always being added. However, to fix this problem, I tried doing something like this:
-(void)update:(CFTimeInterval)currentTime {
[self enumerateChildNodesWithName:#"*" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x + 50 < player.position.x) {
[node removeFromParent];
}
}];
}
(the +50 would be just to make sure that the node is off the screen before removing it)
However, when I did this, instead of removing the specific node that satisfies the 'if' statement, the program removes all of the sprite nodes. Is there a different method or something that I am missing to fix this? Or are there any other simple ways to remove the specific nodes?
Lacking quite a few details, like how you are animating the spikes for instance, makes it a bit hard to be too specific. Nevertheless, from what you are sharing I guess you might be looking for something a little like this:
SKAction *moveSpikeAction = [SKAction moveToX:-50 duration:5];
SKAction *removeSpikeAction = [SKAction removeFromParent];
SKAction *spikeSequence = [SKAction sequence:#[moveSpikeAction, removeSpikeAction]];
[yourSpikeSpriteNode runAction:spikeSequence];
The idea simply being that when the spike has animated to the off screen position you use the removeFromParent action to clear it.
I clearly don't understand the SKPhysicsJoint very well, but there is so little info on the web yet, other than the Apple docs of course. What is wrong with the following code, which I would think should keep the head and neck permanently joined - my intention is that they act like 2 pieces of paper with a pin, so that they can rotate a bit, but not just totally come apart. When I run this code, they fall to the bottom of the SKScene they're in, hit the ground, then the head falls off the body.
Maybe the joint is not moving WITH them or something, it's just staying in place while they move??
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
}
[self addChild:self.head];
[self addChild:self.chest];
[self addChild:self.leftLeg];
self.chestJointPinAnchor = CGPointMake(self.chest.position.x, self.chest.position.y+39);
self.chestJointPin = [SKPhysicsJointPin jointWithBodyA:self.head.physicsBody bodyB:self.chest.physicsBody anchor:self.chestJointPinAnchor];
[self.physicsWorld addJoint:self.chestJointPin];
This is because you set sprite's position after you set up its physicsBody property.
I haven't discovered any mention of that in documentation, but I broke my head last weekend trying to figure out why my manually created rope works, but recursively one doesn't.
SpriteNodes' position MUST be set before physicsBody.
So, just reorder your code somehow like that:
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
}
Oh, I've noticed, you've already found an answer by yourself... Could you please mark your question as answered then.
That seems about right.
A pin joint allows both bodies to rotate around the joint's anchor point. A real world example is an analog clock. Or a bicycle's pedals. Or a car wheel's axle.
One thing you have to know is that bodies connected through a joint will not collide with each other. You can use the pin joint limits though to prevent the head from doing a full 360 spin around the pin joint anchor.
Okay, so I found out that this is an actual bug in Sprite Kit. The fix is to set the sprite's position before setting its physicsBody. I did that and it worked perfectly, as expected.
Additionally, not invalidating what was said above, but apparently the physics engine assumes the scene's anchorPoint is left at (0, 0). If you change it to something else, the physics engine will still treat it like (0, 0).