Move all SKSpriteNodes except one - ios

How could I go about moving all nodes in a view in the same direction except one? An example of the type of game I am creating would be a pokemon kind of structure where the main character in the center of the screen remains stationary at the center and all background objects move.
If anyone has any idea as to how I could accomplish this in the most efficient manner, I would greatly appreciate it.
Thanks in advance.

The usual approach is to add all your moving objects to a worldNode and then change the position of the worldNode. Add the character to the SKScene separately.
let worldNode = SKNode()
self.addChild(worldNode) // Add to SKScene
worldNode.addChild(childNode) //Add all nodes
//let character = SKSpriteNode
character.position = centerOfScreen
self.addChild(character) // Add to SKScene
To move all sprites, change the position of the worldNode
worldNode.position = newPosition

In the update method, use enumerateChildNodesWithName:usingBlock: and pass //* to enumerate all nodes, then inside the block check for the name, and if it's not the one you don't want to move, move.
[self enumerateChildNodesWithName:#"//*" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.name != #"nameOfTheNodeYouDontWantToMove") {
node.position = CGPointMake(node.position.x - 1, node.position.y);
}
}];

Related

How do I get to know what sprite has been touched?

I have some sprites coming from the bottom of the screen in a random order. I want to swipe the sprite in the direction of the swipe. I got the algorithm for swiping. Also, I get an NSLog message whenever any sprite has been touched in the screen. The NSLog gives correct responses to any and every object touched. But how do I know which sprite has been swiped to write the code for applying impulse to that particular sprite?
I am trying the following code:
SKNode *sprite = [self nodeAtPoint:location];
[ball.physicsBody applyImpulse:CGVectorMake(dx, dy) atPoint:location];
[self addChild:sprite];
Also,
userInteractionEnabled = YES for all the sprites
And all the sprites are performing an action (Just in case this is the reason why I cannot swipe them while they are already running an action, in this case what else should I use to move the sprites?)
Sorry if this is too dumb, I am a noob
Thanks in advance!
You will need to use the name of the sprite with the name property:
node.name = #"nodeName";
for(SKNode *node in [self nodesAtPoint:location])
{
if([node.name isEqualToString:#"nodeName"])
{
// your custom code here
}
}
I hope this is what you were looking for, if not i am always here to help.

SpriteKit PhysicsWorld coordinate system, weird runtime joint anchor

Two likely related things here:
1) I can draw a box and add to child from my SKScene impelmentation file with self, self.scene, and myWorld, but not with an SKSprite node's scene property.
SKSpriteNode *bla = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(100, 100)];
[self.scene addChild:bla]; // If I use bla.scene, it doesn't work. self, self.scene, myworld all do work though.
bla.position = CGPointMake(0, 0);
bla.zPosition = 999;
2) I've seen the related questions here and here, but I'm trying to add a joint during gameplay (grabbing a rope). This method gets called after doing some sorting in `didBeginContact:
-(void)monkey:(SKPhysicsBody *)monkeyPhysicsBody didCollideWithRope:(SKPhysicsBody *)ropePhysicsBody atPoint:(CGPoint)contactPoint
{
if (monkeyPhysicsBody.joints.count == 0) {
// Create a new joint between the player and the rope segment
CGPoint convertedRopePosition = [self.scene convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node.parent];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:playerPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];
}
}
I've got showPhyiscs enabled on the scene, so I can see that the joint is ending up in a totally wacky place. Unfortunately, I don't know how to apply the linked solutions since I'm not adding the SKSpriteNodes in this method, they already exist, so I can't just flip the order of position and physicsBody.
I've tried every permutation I could for both of the convertPoint methods. Nothing worked. My best guess is that physicsWorld is using some wacky coordinate system.
Method members of SKPhysicsWorld that relate to position (CGPoint) or frame (CGRect) are to be in scene coordinates. Scene coordinates reference the point {0,0} as the bottom left corner and is consistent throughout SpriteKit.
The scene property of your object named bla will be nil when bla is first created and is set by the scene when added to it.
[bla.scene addChild:bla]; // this won't do anything as scene will be nil when first created
It looks as though convertedRopePosition is being assigned an incorrect value because the second member you're passing into, - (CGPoint)convertPoint:(CGPoint)point fromNode:(SKNode *)node , is the scene when it should be another node in the same node tree as this node. where this node is the caller (in this case the SKScene subclass).
Try replacing the line-
CGPoint convertedRopePosition = [self.scene convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node.parent];
with-
CGPoint convertedRopePosition = [self convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node];
I came up with a janky work around for this problem. It turns out that the coordinate system offset for physicsWorld was likely due to an anchor difference. Changing the anchors of every related thing made no difference, and you can't change the anchor of the physicsWorld directly, so I ended up adding half of the scene width and half of the scene height to the anchor position of my joint. That got it to show in the right place and behave normally.
This problem persisted once side scrolling was factored in. I've posted other questions here with this same problem but I'll include th
I added the following convenience method to my GameScene.m. It essentially takes the place of the seemingly useless convertTo built in method.
-(CGPoint)convertSceneToFrameCoordinates:(CGPoint)scenePoint
{
CGFloat xDiff = myWorld.position.x - self.position.x;
CGFloat yDiff = myWorld.position.y - self.position.y;
return CGPointMake(scenePoint.x + self.frame.size.width/2 + xDiff, scenePoint.y + self.frame.size.height/2 + yDiff);
}
I use this method to add joints. It handles all of the coordinate system transformations that need to be dealt with that lead to the issue raised in this question. For example, the way I add joints
CGPoint convertedRopePosition = [self convertSceneToFrameCoordinates:ropePhysicsBody.node.position];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];

Set permanent SKSpritenode image orientation

Hi I have a bunch of round SKSpriteNodes with a circle physical body. Now when these balls roll down a path I want some of these SKSpritenodes image to stay upright even when rolling. So think of an arrow pointing upwards. When the ball starts rolling the arrow spins in circles. But for some balls Id like the arrow to remain pointing up even when the ball rolls. Whats the best way of doing this?
Edit
So an answer was given but from testing it turns out it is not the correct one. Not allowing the ball to rotate affects the way it rolls down the path. So I guess what I want is rotation to be on but the image to always appear to the user like its not rotating. Thanks.
This looks like a job for SupermSKConstraint. Constraints are evaluated and applied after the physics simulation runs on each frame, so you can use them for tasks like making a node point a certain direction regardless of what physics does to it. For this, you'd want a zRotation constraint.
But there's a bit more to it than that. If you set a zero-rotation constraint on the ball:
// Swift
let constraint = SKConstraint.zRotation(SKRange(constantValue: 0))
ball.constraints = [constraint]
You'll find that SpriteKit resets the physics body's transform every frame due to the constraint, so it only sort-of behaves like it's rolling. Probably not what you want. (To get a better idea what's going on here, try adding a zero-rotation constraint to a rectangular physics body in a world without gravity, applying an angular impulse to it, and watching it try to spin in a view with showsPhysics turned on. You'll see the sprite and its physics body get out of sync and shake a bit -- probably due to accumulated rounding errors as the physics engine and the constraint engine fight it out.)
Instead, you can do a bit of what's in 0x141E's answer, but use constraints to make it less code (and run more efficiently):
Give the ball node a circular physics body. (And possibly no texture, if the only art you want for the ball is a non-rotating sprite.)
Add the arrow node as a child of the ball node. (It doesn't need its own physics body.)
Put a zero-rotation constraint on the arrow.
Wait, that doesn't work -- I told the arrow to not rotate, but it's still spinning?! Remember that child nodes are positioned (and rotated and scaled) relative to their parent node. So the arrow isn't spinning relative to the ball, but the ball is spinning. Don't worry, you can still solve this with a constraint:
Tell the constraint to operate relative to the node containing the ball (probably the scene).
Now the constraint will keep the arrow in place while allowing the ball to rotate however the physics simulation wants it to.
Here's some test code to illustrate:
// Step 1: A rectangular spinner so we can see the rotation
// more easily than with a ball
let spinner = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 300, height: 20))
spinner.position.x = scene.frame.midX
spinner.position.y = scene.frame.midY
spinner.physicsBody = SKPhysicsBody(rectangleOfSize: spinner.size)
scene.addChild(spinner)
spinner.physicsBody?.applyAngularImpulse(0.1) // wheeeeee
// Step 2: Make the arrow a child of the spinner
let arrow = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 20, height: 50))
spinner.addChild(arrow)
// Step 3: Constrain the arrow's rotation...
let constraint = SKConstraint.zRotation(SKRange(constantValue: 0))
arrow.constraints = [constraint]
// Step 4: ...relative to the scene, instead of to its parent
constraint.referenceNode = scene
Here are two methods to create a ball with a physics body and an arrow:
Add an arrow as a child of a ball
Add both the ball and the arrow directly to the scene
Here's what will happen when you add the above to the SpriteKit simulation:
The arrow will rotate when the ball rotates
Both the arrow and the ball will move/rotate independently
If you want the arrow to rotate with the ball, choose Option 1. If you want the arrow to remain fixed, choose Option 2. If you choose Option 2, you will need to adjust the rotation of the arrow to ensure that it points upward. Here's an example of how to do that.
-(void)didMoveToView:(SKView *)view {
self.scaleMode = SKSceneScaleModeResizeFill;
/* Create an edge around the scene */
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:view.frame];
// Show outline of all physics bodies
self.view.showsPhysics = YES;
CGFloat radius = 16;
SKNode *balls = [SKNode node];
balls.name = #"balls";
[self addChild:balls];
// Create 5 balls with stationary arrows
for (int i = 0;i<5;i++) {
// Create a shape node with a circular physics body. If you are targeting iOS 8,
// you have other options to create circular node. You can also create an SKSpriteNode
// with a texture
SKShapeNode *ball = [SKShapeNode node];
// Create a CGPath that is centered
ball.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-radius,-radius,radius*2,radius*2)].CGPath;
ball.fillColor = [SKColor whiteColor];
ball.position = CGPointMake(100, 100+i*radius*2);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:radius];
[balls addChild:ball];
// Create an arrow node
CGSize size = CGSizeMake(2, radius*2);
SKSpriteNode *arrow = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:size];
arrow.name = #"arrow";
arrow.position = CGPointZero;
[ball addChild:arrow];
// Apply angular impulse to the ball so it spins when it hits the floor
[ball.physicsBody applyAngularImpulse:-1];
}
}
- (void) didSimulatePhysics
{
SKNode *balls = [self childNodeWithName:#"balls"];
for (SKNode *ball in balls.children) {
SKNode *arrow = [ball childNodeWithName:#"arrow"];
arrow.zRotation = -ball.zRotation;
}
}
sprite.physicsBody.allowsRotation = NO;
The allowRotation property should control exactly what you are asking.

Xcode sprite kit how to select specific node

I'm trying to create a moving floor in sprite kit. I used a for loop to fill the screen with 'tiles' and I'm moving them across the screen. If their xposition is less than 0 they will get removed. Now I want to add a tile behind the last tile. But I don't know how to get the xposition of the last tile (this will be the tile with the highest position value since it moves from right to left). Here is my code:
-(void)createTile:(float)xpos {
SKSpriteNode *tile = [SKSpriteNode spriteNodeWithImageNamed:#"tile"];
tile.name = #"tile";
float tilesize = (CGRectGetMaxY(self.frame)/20);
tile.yScale = tilesize/24;
tile.xScale = tilesize/24;
tile.position = CGPointMake(xpos, CGRectGetMidY(self.frame));
[self addChild:tile];
}
-(void)update:(CFTimeInterval)currentTime {
//Going through all the tiles an moving them all
[self enumerateChildNodesWithName:#"tile" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0.){
[node removeFromParent];
//[self createtile:(where the argument must be the position of the last floortile + the size of floortile]
}
node.position = CGPointMake(node.position.x -3, node.position.y);
}];
}
EDIT: Forgot I had the floor array in there, I did not want the last object of the floor array but the last object of the childNodeTree. How do I get that?
Isn't the last object in your floorArray always going to be the object with the highest x value? Assuming you created the floor tiles by iterating left to right on the screen? And then as you add more to the screen, you'll add them to the array, and then that tile will be the furthest to the right and the last in the array.
SKSpriteNode *lastTile = [floorArray lastObject];
EDIT for Comment:
NSArray *allChildNodes = myScene.children;
since you know the x position of the removed tile and you should know how many tiles you have per row, you could simply calculate the position by offsetting the removed position by
numberOfTilesPerRow*tileWidth.
Even easier, assuming your tiles fill the whole screen when starting by x=0, and you have one tile extra to fill the gap while moving:
_______ _______
|OOOOO|O -> O|OOOOOO|
|OOOOO|O O|OOOOOO|
------ -------
the new position would be
parentWidth+tileWidth+removedNode.position.x

how to display an x.position in my sprite?

i have a method to create sprites with an initial position, final position, height, image of the sprite and a name property to the sprite (to access the sprite later)
The problem is when i need to debug, because i can't display, for example, a number (of a position in this case...)
In this case, i want to know what sprite is disapearing sooner than expected, so i want to set a new parameter in my method which would be an NSString number(1,2,3..)so i could localize what sprite is making troubles... but i cant display this number or i don't know how. The other debugging technique i thought to do were to put an if(x.position> 600)callAMethod with a label, but... the coordinates are CGPoint and not float. So i'm a bit lost... Do anybody know a technique to do this??
here is my method to create sprites:
-(void)crearMontanaconLaPosicionFinal:(CGFloat)xFinal inicial:(CGFloat)xInicial altura:(CGFloat)altura imagen:(NSString*)imagen nombre:(NSString*)nombre
{
CGPoint posicionFinal = CGPointMake(xFinal, altura);
SKSpriteNode *montaña = [SKSpriteNode spriteNodeWithImageNamed:imagen];
montaña.name = nombre;
SKAction* parallaxMoveAction3 = [SKAction sequence:#[[SKAction moveTo:posicionFinal duration:20],[SKAction customActionWithDuration:0 actionBlock:^(SKNode* montaña, CGFloat elapsedTime){
[montaña setPosition:CGPointMake(xInicial,altura)];
}]]];
montaña.position = CGPointMake(xInicial, altura);
SKAction *repetir=[SKAction repeatActionForever:parallaxMoveAction3];
[montaña runAction:repetir];
[self addChild:montaña];
}

Resources