Tibor Udvari
Tibor Udvari

Reputation: 3012

Sprite Kit Joints

I am trying to make an animated character that can reach out for things on the screen, without moving his torso.

If he can not reach than his hand should go the maximal amount in this direction and then be counteracted by the physical limitation of his static torso.

problem

So in my code the green rectangle (hand) moves where I click and the torso follows. I would like to be able to make the red rect (body) stationary and make the green rectangle "point" at the touch point position.

I have tried to apply a fixed joint to between the scene and the red body rectangle, but this did not seem to work.

- (void) createSceneContent
{
    SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
    body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
    [self addChild:body];

    body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
    body.physicsBody.affectedByGravity = NO;
    body.physicsBody.dynamic = YES;

    self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
    self.hand.position = CGPointMake(body.position.x + body.size.width / 2 + self.hand.size.width / 2, body.position.y);
    [self addChild:self.hand];
    self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
    self.hand.physicsBody.dynamic = NO;

    self.scene.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

    [self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x + body.size.width / 2, body.position.y)]];

    [self.hand runAction:[SKAction moveByX:100 y:10 duration:0.1]];
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];

    CGPoint location = [touch locationInNode:self];
    [self.hand runAction:[SKAction moveTo:location duration:0.1]];
}

Upvotes: 1

Views: 3232

Answers (1)

Batalia
Batalia

Reputation: 2435

First of all, you don't really need to use physics here, if you only want the arm to point at the touched location.
Just change the arm node's anchorPoint to the shoulder (where you would put the pin on the joint) and rotate it around that point (the point is held in a helper shoulderNode, so that it's easy to convert to its coordinates later). You can calculate the rotation angle using atan2f. Here's the code:

- (void) createSceneContent
{
    SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
    body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
    [self addChild:body];

    self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
    self.hand.position = CGPointMake(body.position.x + body.size.width*0.5, body.position.y);
    self.hand.anchorPoint = CGPointMake(0, 0.5);
    [self addChild:self.hand];

    SKNode *shoulderNode = [SKNode node];
    shoulderNode.position = self.hand.position;
    shoulderNode.name = @"shoulderNode";
    [self addChild:shoulderNode];

}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];

    CGPoint location = [touch locationInNode:self];
    CGPoint locationConv = [self convertPoint:location toNode:[self childNodeWithName:@"shoulderNode"]];

    self.hand.zRotation = (atan2f(locationConv.y, locationConv.x));
}

On the other hand, if you ever need to use physics bodies here, it's probably a bad idea to use SKActions to move the arm, and you also might want to set body.physicsBody.dynamic = NO to make the torso static. Then, make sure you add the pin joint correctly, and set its frictionTorque property to a high value to get less dangling. One of the ways to make the hand point at the right location is its velocity vector set in the update method - just use the converted location coordinates (here they are relative to the body node's centre). Full example code:

- (void) createSceneContent {
    self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

    body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
    body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
    [self addChild:body];

    body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
    body.physicsBody.dynamic = NO;
    body.physicsBody.affectedByGravity = YES;

    self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(20, 150)];
    // experiment with the anchorPoint for different results
    self.hand.anchorPoint = CGPointMake(0.5, 0);
    self.hand.position = CGPointMake(body.position.x, body.position.y);
    [self addChild:self.hand];

    self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
    self.hand.physicsBody.dynamic = YES;
    self.hand.physicsBody.affectedByGravity = NO;

    SKPhysicsJointPin *pinHand = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x, body.position.y-5)];
    [self.physicsWorld addJoint:pinHand];
    // experiment with the friction torque value to achieve desired results
    pinHand.frictionTorque = 1.0;
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    location = [touch locationInNode:self];
}

-(void)update:(CFTimeInterval)currentTime {
    CGPoint locationConv = [self convertPoint:location toNode:body];
    // experiment with the multiplier (here: 10) for faster/slower movement
    self.hand.physicsBody.velocity = CGVectorMake(locationConv.x*10, locationConv.y*10);
}

Hope that helps!

Upvotes: 3

Related Questions