Sprite Kit Pin Joints Appear to Have an Incorrect Anchor

Sprite Kit pin joints appear to have an incorrect anchor

I also had this issue and the cause is setting the physics body before setting the sprites position.

carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));

Change the above to

carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];

It should work. Thanks Smick.

SpriteKit: How to create Basic Physics Joints

Sprite Kit - Spring Joints (shock absorber)

Editing my answer. The suspension requires the wheels to be attached to a sliding body versus attaching the wheels via the slide joint. Doing the former allows wheels to rotate. The latter does not.

Vehicle.m

#import "Vehicle.h"

@implementation Vehicle

- (SKSpriteNode*) makeWheel
{
SKSpriteNode *wheel = [SKSpriteNode spriteNodeWithImageNamed:@"wheel.png"];
// wheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:wheel.size.width/2];
return wheel;
}

-(id)initWithPosition:(CGPoint)pos {

if (self = [super init]) {

_joints = [NSMutableArray array];

int wheelOffsetY = 60;
CGFloat damping = 1;
CGFloat frequency = 4;

SKSpriteNode *chassis = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(120, 8)];
chassis.position = pos;
chassis.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:chassis.size];
[self addChild:chassis];

_ctop = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(70, 16)];
_ctop.position = CGPointMake(chassis.position.x+20, chassis.position.y+12);
_ctop.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_ctop.size];
[self addChild:_ctop];

SKPhysicsJointFixed *cJoint = [SKPhysicsJointFixed jointWithBodyA:chassis.physicsBody
bodyB:_ctop.physicsBody
anchor:CGPointMake(_ctop.position.x, _ctop.position.y)];

_leftWheel = [self makeWheel];
_leftWheel.position = CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y - wheelOffsetY); //Always set position before physicsBody
_leftWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_leftWheel.size.width/2];
_leftWheel.physicsBody.allowsRotation = YES;
[self addChild:_leftWheel];

SKSpriteNode *rightWheel = [self makeWheel];
rightWheel.position = CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y - wheelOffsetY);
rightWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rightWheel.size.width/2];
rightWheel.physicsBody.allowsRotation = YES;
[self addChild:rightWheel];

//------------- LEFT SUSPENSION ----------------------------------------------------------------------------------------------- //

SKSpriteNode *leftShockPost = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(7, wheelOffsetY)];
leftShockPost.position = CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y - leftShockPost.size.height/2);
leftShockPost.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:leftShockPost.size];
[self addChild:leftShockPost];

SKPhysicsJointSliding *leftSlide = [SKPhysicsJointSliding jointWithBodyA:chassis.physicsBody
bodyB:leftShockPost.physicsBody
anchor:CGPointMake(leftShockPost.position.x, leftShockPost.position.y)
axis:CGVectorMake(0, 1)];

leftSlide.shouldEnableLimits = TRUE;
leftSlide.lowerDistanceLimit = 5;
leftSlide.upperDistanceLimit = wheelOffsetY;

SKPhysicsJointSpring *leftSpring = [SKPhysicsJointSpring jointWithBodyA:chassis.physicsBody bodyB:_leftWheel.physicsBody
anchorA:CGPointMake(chassis.position.x - chassis.size.width / 2, chassis.position.y)
anchorB:_leftWheel.position];
leftSpring.damping = damping;
leftSpring.frequency = frequency;

SKPhysicsJointPin *lPin = [SKPhysicsJointPin jointWithBodyA:leftShockPost.physicsBody bodyB:_leftWheel.physicsBody anchor:_leftWheel.position];

//------------- RIGHT SUSPENSION ----------------------------------------------------------------------------------------------- //

SKSpriteNode *rightShockPost = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(7, wheelOffsetY)];
rightShockPost.position = CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y - rightShockPost.size.height/2);
rightShockPost.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rightShockPost.size];
[self addChild:rightShockPost];

SKPhysicsJointSliding *rightSlide = [SKPhysicsJointSliding jointWithBodyA:chassis.physicsBody
bodyB:rightShockPost.physicsBody
anchor:CGPointMake(rightShockPost.position.x, rightShockPost.position.y)
axis:CGVectorMake(0, 1)];

rightSlide.shouldEnableLimits = TRUE;
rightSlide.lowerDistanceLimit = 5;
rightSlide.upperDistanceLimit = wheelOffsetY;

SKPhysicsJointSpring *rightSpring = [SKPhysicsJointSpring jointWithBodyA:chassis.physicsBody bodyB:rightWheel.physicsBody
anchorA:CGPointMake(chassis.position.x + chassis.size.width / 2, chassis.position.y)
anchorB:rightWheel.position];
rightSpring.damping = damping;
rightSpring.frequency = frequency;

SKPhysicsJointPin *rPin = [SKPhysicsJointPin jointWithBodyA:rightShockPost.physicsBody bodyB:rightWheel.physicsBody anchor:rightWheel.position];

// Add all joints to the array.

[_joints addObject:cJoint];

[_joints addObject:leftSlide];
[_joints addObject:leftSpring];
[_joints addObject:lPin];

[_joints addObject:rightSlide];
[_joints addObject:rightSpring];
[_joints addObject:rPin];

}

return self;
}

@end

Sprite Kit Joints

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!

SpriteKit: How to create Basic Physics Joints

Thank you Smick.. After Comparing Smick's code with mine I found out the order of these two lines are causing the issue.

head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:head.size];
head.position = CGPointMake(backBone.position.x, backBone.position.y-40);

When I set the position of the Sprite before Setting its physics body, everything started to work correctly.

head.position = CGPointMake(backBone.position.x, backBone.position.y-40);
head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:head.size];

Now I have attached Smick's code also to the full code and attached the link down here. Enjoy.

https://dl.dropboxusercontent.com/u/62559842/PhysicsTest_Final_Working.zip

Spritekit SKPhysicsBody pin joints

After wrestling with this for a little while and creating a simple testing project I realized that my issue was that I was attaching the body of the ferris wheel to the base as a child node and I was also attaching the seats to the body as child nodes. While there may be a way to get this to work I based all coordinates on world coordinates instead. This may help someone out in future. Here's the fixed code:

-(void) initFerrisWheel {
//creates the body of the ferris wheel and attaches it to the base
SKSpriteNode *ferrisWheel = (SKSpriteNode*)[self childNodeWithName:@"ferriswheel_base"];
SKTexture *bodyTexture = [[SharedAssetsManager sharedData].menuAssets objectForKey:@"ferriswheel_body"];
SKSpriteNode *body = [[SKSpriteNode alloc] initWithTexture:bodyTexture];
[body setZPosition:2];
[body setPosition:CGPointMake(ferrisWheel.position.x, ferrisWheel.position.y + 55)];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.affectedByGravity = NO;
[self addChild:body];

//rotates the body of the ferris wheel
SKAction *rotateSequence = [SKAction rotateByAngle:-0.785 duration:2.0];
SKAction *rotateFinal = [SKAction repeatActionForever:rotateSequence];
[body runAction:rotateFinal];

SKTexture *seatTexture = [[SharedAssetsManager sharedData].menuAssets objectForKey:@"ferriswheel_seat"];

int r = 130; //radius of the body for the ferris wheel
for (int i = 0; i < 16; i++) {
//creates a seat and places it on the body
SKSpriteNode *seat = [[SKSpriteNode alloc] initWithTexture:seatTexture];
float x = body.position.x + r * cosf(2*M_PI*i/16);
float y = body.position.y + r * sinf(2*M_PI*i/16);
[seat setPosition:CGPointMake(x, y)];
[seat setZPosition:3];
[self addChild:seat];

//physics body stuff
seat.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:seat.size];
seat.physicsBody.affectedByGravity = NO;

SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:seat.physicsBody anchor:seat.position];
[self.physicsWorld addJoint:pin];
}
}

Simple pin joint in SpriteKit doesn't work as expected

Node 2 have bad defined anchorPoint, here an upload an example:

Full code of example

Image:

3 Seconds running code

Swift 3 code:

    let nodeSize = CGSize(width: 10, height: 10)
let node = SKSpriteNode(color: .red, size: nodeSize)
node.physicsBody = SKPhysicsBody(rectangleOf: nodeSize)
node.physicsBody?.isDynamic = false
self.addChild(node)

let node2Size = CGSize(width: 60, height: 8)
let node2 = SKSpriteNode(color: .green, size: node2Size)
node2.position = CGPoint(x: 30, y: 0)
node2.physicsBody = SKPhysicsBody(rectangleOf: node2Size)
node2.physicsBody?.mass = 1.0
self.addChild(node2)

let a = SKPhysicsJointPin.joint(withBodyA: node.physicsBody! , bodyB: node2.physicsBody!, anchor: CGPoint(x: 0.0, y: 0.0))
self.physicsWorld.add(a)

Objective-C:

SKSpriteNode *object1 = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(5, 5)];
[self addChild:object1];
object1.position = self.view.center;
object1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object1.frame.size];
object1.physicsBody.dynamic = NO;

SKSpriteNode *object2 = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(90, 2)];
[self addChild:object2];
object2.position = CGPointMake(self.view.center.x+45, self.view.center.y);

object2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object2.frame.size];

SKPhysicsJointPin *pinJoint = [SKPhysicsJointPin jointWithBodyA:object1.physicsBody bodyB:object2.physicsBody anchor:self.view.center];
[self.physicsWorld addJoint:pinJoint];


Related Topics



Leave a reply



Submit