Can't move sprites connected by joints to the right location - ios

I have a marionette connected by SKPhysicsJoints. I want it to behave so when I touch a certain point in the scene, the marionette's head moves to that point. Problem is, when I do it, the head moves TOWARDS that point, but does not reach it. If I keep clicking the same point, it will get a bit closer each time.
I have set affectedByGravity=NO on all the SKSpriteNodes that make up the marionette.
I am moving the sprite just by doing
head.position = (the position of the mouse)
and it works fine as long as the head isn't connected to the rest of the marionette by a joint.
So it's kind of like the rest of the sprites are weighing down the head. I wish I knew more about this, theres just so little info on Sprite Kit and not a lot handles this, from what I can find.
edit: Since I was asked, here is a sample project that demonstrates the problem I am having:
-(void) didMoveToView:(SKView *)view
{
self.physicsWorld.speed = 1;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
_head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
_chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck.png"];
_head.position = CGPointMake(512, 380);
_chest.position = CGPointMake(512, 290);
_head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_head.size];
_head.physicsBody.mass = 1;
_head.physicsBody.dynamic = YES;
_chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_chest.size];
_chest.physicsBody.mass = 1;
_chest.physicsBody.dynamic = YES;
[self addChild:_head];
[self addChild:_chest];
CGPoint chestJointPinAnchor = CGPointMake(_chest.position.x, _chest.position.y+39);
SKPhysicsJointPin *chestJointPin = [SKPhysicsJointPin jointWithBodyA:_head.physicsBody bodyB:_chest.physicsBody anchor:chestJointPinAnchor];
[self.physicsWorld addJoint:chestJointPin];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_head.physicsBody.affectedByGravity = NO;
_chest.physicsBody.affectedByGravity = NO;
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
_head.position = positionInScene;
}

Just set your head to: _head.physicsBody.dynamic = NO, while the chest remains dynamic: _chest.physicsBody.dynamic = YES.
You'll find the head now moves exactly to the touch location, dragging the chest along.

Related

Spritekit - UITouch effect does not work when SKparticles are taking place over the area

I have a bubble, when touched will pop. This pop effects work fine with the code below. However when a spritekit particle effect is used which covers the bubble, the touch action does not work. When the particle effect finishes, the touch action will work. Does the particle effect have a non-touch field? and is there anyway around this? I attempted to change the z-position but that did not work.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKLabelNode *touchedNode = (SKLabelNode *)[self nodeAtPoint:positionInScene];
if ([[touchedNode name] isEqualToString:#"bubbleBall1"]) {
ballNode.position = bubble1.position;
[_gameNode addChild:ballNode];
[bubble1 removeFromParent];
[self playBubblePopSound];
}
}
This is the particle effect im using in the physics contact area.
SKEmitterNode *starCollectParticle = [SKEmitterNode emitterNamed:#"starCollect"];
starCollectParticle.position = star1Node.position;
starCollectParticle.zPosition = 0;
//starCollectParticle.particleLifetime = 2;
[self addChild:starCollectParticle];
It was the z.position. I change the z.position of the particles. This did not work. However also changing the z.position of the SKSpritenode did the trick.
-(void)addBubble1:(CGPoint)pos{
gameObjects = [SKTextureAtlas atlasNamed:#"GameObjects.atlas"];
bubble1 = [SKSpriteNode spriteNodeWithTexture:[gameObjects textureNamed:kBubble]];
bubble1.size = bubble1.texture.size;
bubble1.size = CGSizeMake(70,70);
bubble1.position = pos;
bubble1.name = bubble1CategoryName;
**bubble1.zPosition = 1;**
bubble1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bubble1.frame.size.width/2];
bubble1.physicsBody.affectedByGravity = NO;
bubble1.physicsBody.dynamic = NO;
bubble1.physicsBody.usesPreciseCollisionDetection = YES;
bubble1.physicsBody.categoryBitMask = bubble1Category;
bubble1.physicsBody.contactTestBitMask = ballCategory | completeObjectNodeCategory | completeObjectBoxNodeCategory;
bubble1.physicsBody.collisionBitMask = completeObjectNodeCategory | lineNodeCategory;
[_gameNode addChild:bubble1];
}
SKEmitterNode *starCollectParticle = [SKEmitterNode emitterNamed:#"starCollect"];
starCollectParticle.position = star1Node.position;
**starCollectParticle.zPosition = 0;**
//starCollectParticle.particleLifetime = 2;
[self addChild:starCollectParticle];

Sprite kit moving sprite

I have made this sprite that i can grab and move around.
My issue is that i want to be able to "throw" the sprite. Meaning, when i release the sprite i want it to continue in the direction that i moved it. Just like throwing a ball.
What should i do?
#implementation NPMyScene
{
SKSpriteNode *sprite;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
self.scaleMode = SKSceneScaleModeAspectFit;
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"GreenBall"];
sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite.physicsBody.velocity = self.physicsBody.velocity;
sprite.physicsBody.affectedByGravity = false;
sprite.physicsBody.dynamic = true;
sprite.physicsBody.friction = 0;
[sprite.physicsBody isDynamic];
[sprite.physicsBody allowsRotation];
[self addChild:sprite];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[sprite runAction:[SKAction moveTo:[[touches anyObject] locationInNode:self]duration:0.21]];
}
If you are talking about applying physics to the ball, then you need to think deeper about how Sprite Kit deals with physics.
When you specify the 'moveTo' action, you're not actually using Sprite kit's physics engine at all. You're simply specifying a point to animate that sprite to.
What you should be doing to achieve the effect you're looking for is attaching the sprite's position to your finger as it's moved around on the screen like so:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
sprite.position = [[touches anyObject] locationInNode:self];
}
Then when the finger is lifted, you need to calculate the direction and speed in which your finger just moved and apply the appropriate amount of force to the sprite's physics body using the applyForce method:
[sprite.physicsBody applyForce:calculatedForce];
You will need to figure out how to calculate the force to be applied, but play around with the applyForce method and look at what information you are able to get back from the touchedMoved: event to help you apply that force. Hope that helps.

Getting location of section of sprite

I'm using IOS7's sprite kit and using NSSpriteNode with default anchor of (.5, .5) [center]. The sprite will be rotating around a lot, is there a way to get the location relative to the anchor point? Like, if I'm looking for the sprites top-center anchorpoint of (.5,1) or bottom-center (.5, 0)? This way I can always get the same location of a part of a sprite however it is rotated?
self.player = [SKSpriteNode spriteNodeWithImageNamed:#"ship.png"];
self.player.anchorPoint = CGPointMake(.5, 1);
CGPoint p = [self.player convertPoint:self.player.position toNode:self]];
NSLog(#"x=%f,y=%f", p.x, p.y);
self.player.anchorPoint = CGPointMake(.5, 0);
CGPoint p = [self.player convertPoint:self.player.position toNode:self]];
NSLog(#"x=%f,y=%f", p.x, p.y);
This ends up yielding the same point even though I'm changing my anchor point to be different parts. I know this is a bad idea to change the anchor point, but trying to give an idea of what I'm trying to do.
I'd like a method something like:
CGPoint imageTopLoc = [self.player getLocationUsingAnchorPoint:CGPointMake(.5, 1)];
CGPoint imageBottomLoc = [self.player getLocationUsingAnchorPoint:CGPointMake(.5, 0)];
// calculate vector of direction between of two points... (next)
Thanks,
Chris
This code returns the coordinates / touch point inside a node, regardless of the node's rotation. (It's based upon the SpriteKit Game template.)
Is that what you are looking for?
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKSpriteNode *ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
ship.name = #"ship";
ship.position = CGPointMake(100, 100);
ship.zRotation = M_PI * 0.5;
[self addChild:ship];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *ship = [self childNodeWithName:#"ship"];
CGPoint p = [self convertPoint:location toNode:ship];
NSLog(#"x=%f,y=%f", p.x, p.y);
}
}

Have particle emitter trail follow finger path in spriteKit

I have created a particle emitter in Xcode that has quite a lengthy trail. When i move it inside of the particle generator it leaves a trail following my mouse path.
some background info on my goal:
In my spriteKit game the user drags their finger around the screen to shoot moving objects. I am attempting to create a "Bullet Time" effect where the objects slow down and highlights when the current finger location touches them. When the finger stops moving or they run out of ammo the touchesEnded method is fired shooting all of the highlighted objects. Currently I have the path that they travel showing up as a line drawn to the screen using SKShapeNode & CGPath, but I would like the trail to be highlighted with the emitter trail instead.
In the touches began method I create a circle SpriteNode that moves around wherever the finger moves to (the physics collision is attached to the circle not the path). I've attached the particle trail emitter to this circle and it moves with the circle when I move it around the screen, but does not leave a trail.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
startPoint = touchLocation;
CGPathMoveToPoint(pathToDraw, NULL, touchLocation.x, touchLocation.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.lineWidth = 2;
lineNode.zPosition = 110;
lineNode.strokeColor = [SKColor whiteColor];
[self addChild:lineNode];
circle = [SKSpriteNode spriteNodeWithTexture:[animationsAtlas textureNamed:#"anim_countdown_9"]];
circle.name = #"circle";
circle.scale = 0.3;
circle.alpha = 0.5;
circle.zPosition = 110;
circle.position = touchLocation;
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
circle.physicsBody.categoryBitMask = weaponCategory;
circle.physicsBody.dynamic = NO;
circle.physicsBody.contactTestBitMask = billyCategory;
circle.physicsBody.collisionBitMask = 0;
[self addChild:circle];
pathEmitter = [SKEmitterNode skt_emitterNamed:#"FollowPath"];
//pathEmitter.position = circle.position;
//pathEmitter.targetNode = self;
//pathEmitter.scale = 0.2;
//pathEmitter.zPosition = 60;
[circle addChild:pathEmitter];
}
In touchesMoved method I move the circle accordingly to the new position
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInNode:self];
circle.position = currentPoint;
SKAction *wtf = [SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:0.1];
//pathEmitter.position = currentPoint;
//[pathEmitter runAction:wtf];
//pathEmitter.particleAction = wtf;
pathEmitter.particleAction = [SKAction moveTo:currentPoint duration:1.0];
CGPathAddLineToPoint(pathToDraw, NULL, currentPoint.x, currentPoint.y);
lineNode.path = pathToDraw;
I've tried setting pathEmitter.targetNode = self; like this post Making a particle follow a path in spriteKit suggests but then the emitter doesn't appear at all.
and if i set the particleAction to followPath it does not leave a trail either.
In my code you can see I've commented out some lines, basically I've tried every combination of targetNode & particleAction I can think of.
Any suggestions on how I can get the emitter to leave a trail on my finger path?
thanks
actually pathEmitter.targetNode = self; is the one that will let you have a particle to leave a trail as your object moves, I'm not seeing any reason why it's not working for you 'cause I've been using this method for like a long time ago now, check your position specially for method touchesMoved
I think this code is all what you need;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
circle = [SKSpriteNode spriteNodeWithTexture:[animationsAtlas textureNamed:#"anim_countdown_9"]];
circle.name = #"circle";
circle.scale = 0.3;
circle.alpha = 0.5;
circle.zPosition = 110;
circle.position = touchLocation;
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
circle.physicsBody.categoryBitMask = weaponCategory;
circle.physicsBody.dynamic = NO;
circle.physicsBody.contactTestBitMask = billyCategory;
circle.physicsBody.collisionBitMask = 0;
[self addChild:circle];
pathEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile: [[NSBundle mainBundle] pathForResource:#"FollowPath"ofType:#"sks"]];
pathEmitter.position = CGPointMake(0, -60);
[circle addChild:pathEmitter];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInNode:self];
circle.position = currentPoint;
}
- (void)update:(CFTimeInterval)delta
{
if (!pathEmitter.targetNode) {
pathEmitter.targetNode = self;
}
}

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.
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]];
}
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!

Resources