Maintaining a constant distance between two sprites - ios

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]];

Related

Simple pin joint in SpriteKit doesn't work as expected

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];

Scaling a pendulum made with SKSpriteNodes

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];

different reaction to collision on node using sprite kit

I have rectangle shape paddle node, my question is - is it possible to "cut" node in half so when, for example ball, hit right side of node ball will go back with bigger angle, that it came, and same thing on other side. Im bad with words so, here are image of what I want to do :
For paddle node i use this code:
paddle = [[SKSpriteNode alloc] initWithImageNamed: #"board.png"];
paddle.name = paddleCategoryName;
paddle.position = CGPointMake(self.frame.origin.x + 50, CGRectGetMidY(self.frame));
[self addChild:paddle];
paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
paddle.physicsBody.restitution = 0.1f;
paddle.physicsBody.friction = 0.4f;
paddle.physicsBody.dynamic = NO;
and for ball :
ball = [SKSpriteNode spriteNodeWithImageNamed: #"ball.png"];
[self addChild:ball];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width];
ball.physicsBody.friction = 0.0f;
ball.physicsBody.restitution = 1.0f;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;

velocity threshold for elastic collision

While I was messing with spritekit, I noticed that even the restitution is set to 1.0 (with linearDamping and friction both 0), if the node's velocity is small, it will not bounce. E.g. in the code below, I create an edge on the left, and have a ball hit the edge. I noticed that in the x direction, whenever the velocity is larger than 150, the ball will bounce; otherwise it'll just stick to the wall and stop moving.
So I am wondering, is there a way to change this threshold so that the ball can bounce even with a low speed? Thanks!
self.physicsWorld.gravity = CGVectorMake(0, 0);
SKNode *leftEdge = [[SKNode alloc] init];
leftEdge.physicsBody = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointZero toPoint:CGPointMake(0.0, self.size.height)];
leftEdge.position = CGPointZero;
[self addChild:leftEdge];
SKShapeNode *ball = [SKShapeNode shapeNodeWithCircleOfRadius:30];
ball.position = CGPointMake(self.size.width * 0.3, self.size.height * 0.5);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:30];
ball.physicsBody.velocity = CGVectorMake(-150, 0.0);//not bounce
ball.physicsBody.restitution = 1.0;
ball.physicsBody.friction = 0.0;
ball.physicsBody.linearDamping = 0.0;
[self addChild:ball];
When two bodies collide both of their restitution and friction (as well as many other) properties are taken into account. So give the wall a restitution of 1 and a friction of 0 for an elastic collision.

SpriteKit ball loses all energy hitting wall, restitution=1

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.

Resources