Xcode sprite kit how to select specific node - ios

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

Related

increment rotation while spacing and loading sprites around parentnode

Progress so far:
So what I have at the moment is this:
(the green point represents the parent "BlankNode, adding children then rotating them around that node,
Im a bit stick how to get it work properly, for some reason they dont sit next to eachother but opposite (as showen in http://i.stack.imgur.com/w7QvS.png)
inGameLevel
myArc = [[Arcs alloc]initWithArcCount:myAmmountOfSprites];
[self addChild:myArc];
My wish is for the sprite.rotation to be slightly offset from the next loaded...here they are split...
(The diagram belows showing the arc shape I would like to load the sprites in)
**With one stick loaded, maybe its easier to spot the mistake
(if I load a second sprite it loads directly opposite to the previous and not at the expected angle incremented
In this version I have just loaded the stick and blanknode, positioned it using anchor points, Im confused how the rotation works... **
SKSpriteNode *blank = [[SKSpriteNode alloc]
///like the otherone
blank.zRotation=0;
blank.anchorPoint = CGPointMake(0.5, 0.5);
[self addChild:blank];
//set to 0 value so I can see what its natural state is (it is vertical and above the parent node)
//but this value will be incremented each time a new sprite is added
int rotationAmount = 0;
Rotation = Rotation-rotationAmount; //will increment
objectPic = [SKSpriteNode spriteNode....as normal
//use blank nodes anchorpoint
objectPic.anchorPoint = blank.anchorPoint;
//Rotation
objectPic.zRotation = Rotation;
float moveUp_donut = 0.3;
//"moveUp_donut" moving this value up moves the stick up
//and outward from the center
objectPic.anchorPoint =
CGPointMake(0.0,-moveUp_donut); //(0.0,-moveOutward);
[blank addChild:objectPic];
}
}
I have made an xcode project available for anyone interested to have a look at the problem, hopefully you can explain how to get the rotation working correctly.
at the moment it is just loading one sprite, so you might need to play with the setting,
myArc = [[Arcs alloc]initWithArcCount:addLotsOfSticks];
//and play with the rotation ammount
int rotationAmount = 3;
http://www.filedropper.com/rotationtest
Solution Found! see below:
🌸
A huge thanks to WangYudong for giving such a great answer!
I made a sample project and hope it can help. The algorithm is not base on your project, so make some change to fit your need.
Firstly, add a blank node to the middle of the scene:
self.blank = [[SKSpriteNode alloc] initWithColor:[SKColor greenColor]size:CGSizeMake(20, 20)];
self.blank.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:self.blank];
Then, create the stick:
- (SKSpriteNode *)newStick
{
SKSpriteNode *stick = [[SKSpriteNode alloc] initWithColor:[SKColor redColor]size:CGSizeMake(5, 100)];
return stick;
}
And given the amount of sticks, the radius (of the inner circle), the starting radian and ending radian, add a method:
- (void)loadStickArcWithStickAmount:(NSUInteger)amount radius:(CGFloat)radius startRadians:(CGFloat)startRad endRadians:(CGFloat)endRad
{
for (NSUInteger index = 0; index < amount; index++) {
SKSpriteNode *stick = [self newStick];
CGFloat halfStickLength = stick.size.height / 2;
CGFloat rotateRad = startRad + (endRad - startRad) / (amount - 1) * index;
stick.zRotation = M_PI_2 + rotateRad;
stick.position = CGPointMake((radius + halfStickLength) * cos(rotateRad),
(radius + halfStickLength) * sin(rotateRad));
[self.blank addChild:stick];
}
}
Some hints:
rotateRad divides radians of endRad - startRad.
M_PI_2 is an offset of zRotation.
Trigonometric maths calculates the position of sticks.
Both anchor points of blank node and stick remain default (0.5, 0.5).
Use the method:
[self loadStickArcWithStickAmount:27 radius:50.0 startRadians:M_PI endRadians:2*M_PI];
to achieve the following result:

Sprites are removed by themselfs as soon as they pass the screen height

I'm currently trying SpriteKit for first time and using some actions. I was under the impression that every node/sprite you create you would need to remove it but for some reason they are removed by themselfs as soon as they pass the screen height. Is this a normal behavior in SpriteKit?
Here is the code.
I'm basically using an action to animate a sprite across the screen (from bottom to top in protrait-mode), the funny thing is that the node count stays at 0 no matter how many sprites I create, it actually counts them but it subtracts them as soon as they pass the screen height.
-(void) shoot
{
bullet = [SKSpriteNode spriteNodeWithImageNamed:#"bullet"];
bullet.position = CGPointMake(airPlane.position.x, airPlane.position.y + 50);
[self addChild:bullet];
SKAction *moveBullet = [SKAction moveTo:CGPointMake(airPlane.position.x, self.size.height + 20) duration:0.5];
[bullet runAction:moveBullet];
}
If I change my action to where the sprite doesn't cross the screen height then the node count keeps the count.
Change this line...
SKAction *moveBullet = [SKAction moveTo:CGPointMake(airPlane.position.x, self.size.height + 20) duration:0.5];
... to this
SKAction *moveBullet = [SKAction moveTo:CGPointMake(airPlane.position.x, self.size.height) duration:0.5];
Again Is this a normal behavior in SpriteKit?
Thanks a lot
The node count displayed on the lower right corner is just an indication of the nodes which are being rendered on the screen. They are not an indication of the total nodes in the scene.
Use the following code
[skView setValue:#(YES) forKey:#"_showsCulledNodesInNodeCount"];
At the same place where you call
skView.showsFPS = YES;
skView.showsNodeCount = YES;
Also, please have a look at this answer.
that can be a memory leak because sprite kit holds textures in the memory for further use for better result remove it from the screen when its passing out of the screen and set it manually to nil
for eg
[self enumerateChildNodesWithName:name usingBlock:^(SKNode *node, BOOL *stop) {
if(node.x>900)
{
//remove all the action
[node removeAllActions];
//remove node from parent
[node removeFromParent];
//set nil//not required set automatically to nil when node is remove from parent
node=nil;
}
}];

SKPhysicBody In the Wrong position

I am creating a simple Sprite Kit game however when i am adding the PhysicsBody to one of my sprites it seems to be going in the wrong position. i know that it is in the wrong position as i have have set
skView.showsPhysics = YES;
and it is showing up in the wrong position.
The Square in the bottom corner is the physics body for the first semicircle. I am using a square at the moment just for testing purposes.
My app includes view following and follows my main sprite when it moves. I implemented this by following apples documentation and creating a 'myworld' node and creating all other nodes from that node.
myWorld = [SKNode node];
[self addChild:myWorld];
semicircle = [SKSpriteNode spriteNodeWithImageNamed:#"SEMICRICLE.png"];
semicircle.size = CGSizeMake(semicircle.frame.size.width/10, semicircle.frame.size.height/10);
semicircle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:semicircle.frame.size];
semicircle.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
semicircle.physicsBody.dynamic = YES;
semicircle.physicsBody.collisionBitMask = 0;
semicircle.name = #"semicircle";
[myWorld addChild:semicircle];
To centre on the node I call these methods
- (void)didSimulatePhysics
{
[self centerOnNode: [self childNodeWithName: #"//mainball"]];
}
- (void) centerOnNode: (SKNode *) node
{
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(node.parent.position.x - cameraPositionInScene.x, node.parent.position.y - cameraPositionInScene.y);
}
I don't know if the my world thing makes any difference to the SkPhysics body...
SKPhysicsBody starts at coordinates 0,0 which is at the bottom left hand corner. If you make the area smaller, as you did by width/10 and height/10, you decrease the size but from the bottom left.
I think what you are looking for is bodyWithRectangleOfSize:center: which allows you to manually set the center from which you base your physics body area on.
Update:
Based on what I understand, your smallest semi circle pic size is the same as the screen size. I would suggest you modify the image size to something like the example I have. You can then set the sprite's position as required and set the physics body to the half of the image containing your semi circle.
Your centerOnNode call should be put in the didEvaluateActions function instead of the didSimulatePhysics function. This is because you need to move the world before the physics are drawn so that they stay in sync. Similar question found here: https://stackoverflow.com/a/24804793/5062806

SpriteKit conveyor belt

I am trying to create a conveyor belt effect using SpriteKit like so
MY first reflex would be to create a conveyor belt image bigger than the screen and then move it repeatedly forever with actions. But this does not seem ok because it is dependent on the screen size.
Is there any better way to do this ?
Also obviously I want to put things (which would move independently) on the conveyor belt so the node is an SKNode with a the child sprite node that is moving.
Update : I would like the conveyor belt to move "visually"; so the lines move in a direction giving the impression of movement.
Apply physicsBody to all those sprites which you need to move on the conveyor belt and set the affectedByGravity property as NO.
In this example, I am assuming that the spriteNode representing your conveyor belt is called conveyor. Also, all the sprite nodes which need to be moved are have the string "moveable" as their name property.
Then, in your -update: method,
-(void)update:(CFTimeInterval)currentTime
{
[self enumerateChildNodesWithName:#"moveable" usingBlock:^(SKNode *node, BOOL *stop{
if ([node intersectsNode:conveyor])
{
[node.physicsBody applyForce:CGVectorMake(-1, 0)];
//edit the vector to get the right force. This will be too fast.
}
}];
}
After this, just add the desired sprites on the correct positions and you will see them moving by themselves.
For the animation, it would be better to use an array of textures which you can loop on the sprite.
Alternatively, you can add and remove a series of small sprites with a sectional image and move them like you do the sprites which are travelling on the conveyor.
#akashg has pointed out a solution for moving objects across the conveyor belt, I am giving my answer as how to make the conveyor belt look as if it is moving
One suggestion and my initial intuition was to place a larger rectangle than the screen on the scene and move this repeatedly. Upon reflecting I think this is not a nice solution because if we would want to place a conveyor belt on the middle, in a way we see both it ends this would not be possible without an extra clipping mask.
The ideal solution would be to tile the SKTexture on the SKSpriteNode and just offset this texture; but this does not seem to be possible with Sprite Kit (no tile mechanisms).
So basically what I'm doing is creating subtextures from a texture that is like so [tile][tile](2 times a repeatable tile) and I just show these subtextures one after the other to create an animation.
Here is the code :
- (SKSpriteNode *) newConveyor
{
SKTexture *conveyorTexture = [SKTexture textureWithImageNamed:#"testTextureDouble"];
SKTexture *halfConveyorTexture = [SKTexture textureWithRect:CGRectMake(0.5, 0.0, 0.5, 1.0) inTexture:conveyorTexture];
SKSpriteNode *conveyor = [SKSpriteNode spriteNodeWithTexture:halfConveyorTexture size:CGSizeMake(conveyorTexture.size.width/2, conveyorTexture.size.height)];
NSArray *textureArray = [self horizontalTextureArrayForTxture:conveyorTexture];
SKAction *moveAction = [SKAction animateWithTextures:textureArray timePerFrame:0.01 resize:NO restore:YES];
[conveyor runAction:[SKAction repeatActionForever:moveAction]];
return conveyor;
}
- (NSArray *) horizontalTextureArrayForTxture : (SKTexture *) texture
{
CGFloat deltaOnePixel = 1.0 / texture.size.width;
int countSubtextures = texture.size.width / 2;
NSMutableArray *textureArray = [[NSMutableArray alloc] initWithCapacity:countSubtextures];
CGFloat offset = 0;
for (int i = 0; i < countSubtextures; i++)
{
offset = i * deltaOnePixel;
SKTexture *subTexture = [SKTexture textureWithRect:CGRectMake(offset, 0.0, 0.5, 1.0) inTexture:texture];
[textureArray addObject:subTexture];
}
return [NSArray arrayWithArray:textureArray];
}
Now this is still not ideal because it is necessary to make an image with 2 tiles manually. We can also edit a SKTexture with a CIFilter transform that could potentially be used to create this texture with 2 tiles.
Apart from this I think this solution is better because it does not depend on the size of the screen and is memory efficient; but in order for it to be used on the whole screen I would have to create more SKSpriteNode objects that share the same moveAction that I have used, since tiling is not possible with Sprite Kit according to this source :
How do you set a texture to tile in Sprite Kit.
I will try to update the code to make it possible to tile by using multiple SKSpriteNode objects.

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