I've got a working clock with a pendulum, but the swinging arm does not scale correctly. Here's what it looks like before scaling...
And then, after scaling...
The clock face scales down, and so does the swing arm (the green line), but it looks like it scales around it's center point not the fulcrum at the center of the face. I can fix that scaling by setting the swing's anchorPoint so it scales toward one end rather than the center...
swing.anchorPoint = CGPointMake(0, 0.5);
but doing that causes the pendulum to not swing. (I think because physics forces are applied to swing's anchorPoint which, if I move it to one end, is at the pin anchor which is fixed).
How can I get the arm to scale towards the fulcrum and still allow it to swing?
Here's the code...
// in my SKScene
ClockFace *face = [ClockFace face]; // just an SKNode subclass
face.name = #"face";
face.position = CGPointMake(150, 150);
[self addChild:face];
// add the pendulum
SKSpriteNode *fulcrum = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(5, 5)];
[face addChild:fulcrum];
fulcrum.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fulcrum.frame.size];
fulcrum.physicsBody.dynamic = NO;
SKSpriteNode *swing = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(190, 2)];
[face addChild:swing];
swing.position = CGPointMake(95, 0);
swing.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:swing.frame.size];
SKPhysicsJointPin *pinJoint = [SKPhysicsJointPin jointWithBodyA:fulcrum.physicsBody bodyB:swing.physicsBody anchor:face.position];
[self.physicsWorld addJoint:pinJoint];
In Swift 3 example of this:
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: 5, y: 0)
node2.anchorPoint = CGPoint(x: 0.0, y: 0.5) // <- New Line
node2.physicsBody = SKPhysicsBody(rectangleOf: node2Size)
node2.physicsBody?.mass = 1.0
self.addChild(node2)
// Scale Line
node2.run(SKAction.repeatForever(SKAction.sequence([
SKAction.scale(to: 0.2, duration: 1.0),
SKAction.scale(to: 1.5, duration: 0.5),
])))
// Anchor Point
let a = SKPhysicsJointPin.joint(withBodyA: node.physicsBody! , bodyB: node2.physicsBody!, anchor: CGPoint(x: 0.0, y: 0.0))
self.physicsWorld.add(a)
The reason why the scaling is weird is because of how you do your anchor point. Imagine a rubber band with a thumbtack in it. The anchor point is where the thumbtack is. Now to scale. Just grab the rubber band and pull. When the thumbtack is the middle, you will find it easy to stretch both sides evenly (this is what you are doing on the y axis). When you place it on the left of the rubber band, you will only be able to scale the right side (This is what you want to be doing)
Now with PhysicsBody, you need to adjust the bodies anchor point based on the sprites anchor point. To do this you need to do some math:
let centerPoint = CGPointMake(sprite.size.width / 2 - (sprite.size.width * sprite.anchorPoint.x), sprite.size.height / 2 - (sprite.size.height * sprite.anchorPoint.y))
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size, center: centerPoint)
Using centerPoint allows you to move where the center of your body is.
// in my SKScene
ClockFace *face = [ClockFace face]; // just an SKNode subclass
face.name = #"face";
face.position = CGPointMake(150, 150);
[self addChild:face];
// add the pendulum
SKSpriteNode *fulcrum = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(5, 5)];
[face addChild:fulcrum];
fulcrum.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fulcrum.frame.size];
fulcrum.physicsBody.dynamic = NO;
SKSpriteNode *swing = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(190, 2)];
[face addChild:swing];
swing.anchorPoint = CGPointMake(0,0.5);
CGPoint *centerPoint = CGPointMake(swing.size.width / 2 - (swing.size.width * swing.anchorPoint.x), swing.size.height / 2 - (swing.size.height * swing.anchorPoint.y));
swing.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:swing.frame.size center:centerPoint];
SKPhysicsJointPin *pinJoint = [SKPhysicsJointPin jointWithBodyA:fulcrum.physicsBody bodyB:swing.physicsBody anchor:face.position];
[self.physicsWorld addJoint:pinJoint];
Related
I'd like to make a pendulum. Starting with an SKScene and everything defaulted, I do the following...
- (void)createSceneContents {
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.view.bounds];
// object 1 is the fulcrum
SKSpriteNode *object1 = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(5, 5)];
[self addChild:object1];
object1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object1.frame.size];
object1.physicsBody.dynamic = NO;
object1.position = self.view.center;
// object2 is like a broomstick, which I will pin to the fulcrum
SKSpriteNode *object2 = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(90, 2)];
[self addChild:object2];
object2.anchorPoint = CGPointMake(0.0, 0.5);
object2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object2.frame.size];
object2.position = self.view.center;
// pin the physics bodies
SKPhysicsJointPin *pinJoint = [SKPhysicsJointPin jointWithBodyA:object1.physicsBody bodyB:object2.physicsBody anchor:object1.position];
[self.physicsWorld addJoint:pinJoint];
}
If I don't add the pin logic, as expected the fulcrum stays in the middle of the scene and the broomstick falls to the floor, so I know the broomstick is subject to gravity. After adding the pin, I get this:
No motion. Why? This causes no motion either...
[object2.physicsBody applyForce:CGVectorMake(0, -5)];
I expect the broomstick to swing and oscillate because it's pinned to the fulcrum. I've seen articles about positioning the nodes first, before joints, but I've done that. Am I wrong to expect the broom to swing? How can I get it to do so?
Node 2 have bad defined anchorPoint, here an upload an example:
Full code of example
Image:
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];
Thanks to #Maetschl, I was able to make it work in Objective-c as follows (removing the anchor point and just positioning the swinging node to one edge)...
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];
I want to know is there a way to set background color for a SKLabelNode not font color. I'm looking for something like below mentioned code, which is available in ios apps.
label.backgroundColor = [UIColor redColor];
Try adding the SKLabelNode as a child of a SKSpriteNode.
SKLabelNode *label = [[SKLabelNode alloc]initWithFontNamed:#"Helvetica"];
label.position = CGPointMake(0, -label.frame.size.height/2);
SKSpriteNode *background = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(label.frame.size.width, label.frame.size.height)];
background.position = CGPointMake(200, 100);
[background addChild:label];
[self addChild:background];
Adding the SKLabelNode as a child of a SKSpriteNode works but it hides the text. So, I resolved this issue by setting the zPosition of background to a negative number. Here is the swift 3 code:
var label = SKLabelNode(fontNamed: "Helvetica")
label.position = CGPoint(x: CGFloat(0), y: CGFloat(-label.frame.size.height / 2))
var background = SKSpriteNode(color: UIColor.red, size: CGSize(width: CGFloat(label.frame.size.width), height:CGFloat(label.frame.size.height)))background.position = CGPoint(x: CGFloat(200), y: CGFloat(100))
background.zPosition = -1
label.addChild(background)
self.addChild(label)
be sure to include the text value before calculating the background’ GCSize from the label node, else the values returned will be zero as it has no text to determine the size of the label.
..correct about layer issues
Here's a distance formula I run through the update method to keep track of the distance between 2 sprites:
-(void)distance
{
double dx = (_spriteA.position.x - _spriteB.position.x); //(x2 - x1);
double dy = (_spriteA.position.y - _spriteB.position.y); //(y2 - y1);
dist = sqrt(dx*dx + dy*dy);
}
-(void)update:(NSTimeInterval)currentTime
{
[self distance]; //Calculate A & B distance
if (dist > 50)
{
//What to write here to keep a constant distance between A & B?
[_spriteB runAction:[SKAction moveTo:(_spriteA.position) duration:1]];
}
}
Distance is tracked well but the if statement has problems. Specifically the actual action being run on sprite B. What ends up happening is sprite B moves toward sprite A in a yo-yo like fashion non-stop - even when sprite A isn't moving. I need sprite B to only move when sprite A is moved and retaining the distance of 50. Sprite A only moves when a person touches the screen. Please help.
You can use the SKConstraint class to maintain the distance between the two nodes. For example :
let node1 = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(20, 10))
node1.position = CGPointMake(self.size.width/2, self.size.height/2)
self.addChild(node1)
let node2 = SKSpriteNode(color: UIColor.blueColor(), size: CGSizeMake(10, 20))
node2.position = CGPointMake(self.size.width/2, self.size.height/2 - 50)
self.addChild(node2)
// The upper and lower limit is set to 50 to maintain a constant distance.
let constraint = SKConstraint.distance(SKRange(lowerLimit: 50, upperLimit: 50), toNode : node1)
node2.constraints = [constraint]
node1.runAction(SKAction.moveToY(100, duration: 2.0))
In Objective C
SKSpriteNode *node1 = [[SKSpriteNode alloc] initWithColor:[UIColor redColor]
size:CGSizeMake(20, 10)];
node1.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild:node1];
SKSpriteNode *node2 = [[SKSpriteNode alloc] initWithColor:[UIColor redColor]
size:CGSizeMake(10, 20)];
node2.position = CGPointMake(self.size.width/2, self.size.height/2 - 50);
[self addChild:node2];
// The upper and lower limit is set to 50 to maintain a constant distance.
SKConstraint *constraint = [SKConstraint distance:[SKRange rangeWithLowerLimit:50 upperLimit:50] toNode:node1];
node2.constraints = #[constraint];
[node1 runAction:[SKAction moveToY:100 duration:2.0]];
In the following example, there are three things on the screen:
ball (a SKShapeNode)
spriteContainer (a SKSpriteNode that contains ball2, a SKShapeNode)
box (a SKSpriteNode)
Why does ball fall out of view? Does a SKShapeNode need to be inside a SKSpriteNode to have physics properly applied to it?
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKColor * warmRed = [SKColor colorWithRed:0.99 green:0.41 blue:0.25 alpha:1.0];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.backgroundColor = warmRed;
//falls out of view
SKShapeNode * ball = [[SKShapeNode alloc] init];
CGMutablePathRef ballPath = CGPathCreateMutable();
CGPathAddArc(ballPath, NULL, size.width-40, self.size.height/2, 20, 0, M_PI*2, YES);
ball.path = ballPath;
ball.lineWidth = 2;
ball.fillColor = warmRed;
ball.strokeColor = [SKColor whiteColor];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
[self addChild:ball];
//lands on bottom of screen
SKShapeNode * ball2 = [[SKShapeNode alloc] init];
CGMutablePathRef ball2Path = CGPathCreateMutable();
CGPathAddArc(ball2Path, NULL, 0, 0, 20, 0, M_PI*2, YES);
ball2.path = ball2Path;
ball2.lineWidth = 2;
ball2.fillColor = warmRed;
ball2.strokeColor = [SKColor whiteColor];
CGSize spriteContainerSize = CGSizeMake(40,40);
CGPoint spriteContainerPosition = CGPointMake(size.width/2, size.height/2);
SKSpriteNode * spriteContainer = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:spriteContainerSize];
spriteContainer.position = spriteContainerPosition;
spriteContainer.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteContainerSize];
[spriteContainer addChild:ball2];
[self addChild:spriteContainer];
//lands on bottom of screen
CGSize boxSize = CGSizeMake(40,40);
CGPoint boxPosition = CGPointMake(boxSize.width, size.height/2);
SKSpriteNode * box = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:boxSize];
box.position = boxPosition;
box.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:boxSize];
[self addChild:box];
}
return self;
}
Screenshot:
https://dl.dropboxusercontent.com/u/164157126/example.jpg
Note that you don't set a position for the node which falls off the screen, but set a position for the other two nodes you create.
The default position of a node is 0,0. Your ball will appear at the bottom left of the scene, and since it is over the edge body you defined, will fall off immediately.
Set the ball's position appropriately so that it does not intersect the edge of the screen, and the ball will not fall off.
If I apply an impulse of 1 in the y direction, the ball bounces back and forth without losing any energy. However, if the initial impulse is 0.5 or below, the ball loses all energy instantly when it hits the wall. Why is this happening? I have a pretty good understanding of the properties of the SKPhysicsBody class. Try this code to see if you get the same behavior on your computer.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
SKPhysicsBody* borderBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody = borderBody;
self.physicsBody.friction = 0.0f;
self.backgroundColor = [SKColor colorWithRed:0.7 green:0.7 blue:0.7 alpha:1.0];
SKShapeNode *ball = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
[ball setStrokeColor:[UIColor blackColor]];
CGPathMoveToPoint(pathToDraw, NULL, 0, 0);
CGPathAddEllipseInRect(pathToDraw, NULL, CGRectMake(-16, -16, 32, 32));
ball.path = pathToDraw;
ball.position = CGPointMake(size.width / 2, size.height / 2);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];
ball.physicsBody.friction = 0.0f;
ball.physicsBody.restitution = 1.0f;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;
[self addChild:ball];
[ball.physicsBody applyImpulse:CGVectorMake(0, 0.5)];
}
return self;
}
When the collision velocity is small enough (such as your CGVector of (0, 0.5)), Box2d underlying the Sprite Kit physics engine will compute the collision as inelastic (i.e. as if the restitution was 0, removing any bounciness), and the node will not bounce.
This is, per the Box2d documentation, to prevent jitter.
In the Box2d source code, you even have this line:
/// A velocity threshold for elastic collisions. Any collision with a relative linear
/// velocity below this threshold will be treated as inelastic.
#define b2_velocityThreshold
Your impulse should be above this threshold for the restitution to be respected.