Add SKSpriteNode children back to the parent after removing - ios

I'm working on a game that has 3 clouds in the background which are each SKSpriteNodes. In order to create a parallax effect I'm moving them down the screen as the character is moving up (the screen is moving up as well).
I removing them in the update method after they reach the bottom of the screen. I would like to add them back at a Y position 20px above the viewable scene so that would be 500x on an iPhone 4 and 588px on a iPhone 5. Then just repeat over and over.
Then only way I can think of to do this is to chain SKActions and run a sequence, but I don't think that is the best way. I'm trying to find a way to add them back after they have been removed in the update method.
-(id)initWithSize:(CGSize)size {
...
//Moving Clouds
[self performSelector:#selector(createClouds) withObject:nil afterDelay:.2];
}
-(void)createClouds {
SKSpriteNode *cloud1 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud1"];
cloud1.position = CGPointMake(40, 200);
cloud1.xScale = .5;
cloud1.yScale = .5;
cloud1.name = #"Cloud1";
cloud1.zPosition = 0;
SKAction *moveCloud1 = [SKAction moveToY:-20 duration:15];
[cloud1 runAction:[SKAction repeatActionForever:moveCloud1]];
[self addChild:cloud1];
SKSpriteNode *cloud2 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud2"];
cloud2.position = CGPointMake(300, 320);
cloud2.xScale = .5;
cloud2.yScale = .5;
cloud2.name = #"Cloud2";
cloud2.zPosition = 0;
SKAction *moveCloud2 = [SKAction moveToY:-40 duration:15];
[cloud2 runAction:[SKAction repeatActionForever:moveCloud2]];
[self addChild:cloud2];
SKSpriteNode *cloud3 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud3"];
cloud3.position = CGPointMake(200, 450);
cloud3.xScale = .5;
cloud3.yScale = .5;
cloud3.name = #"Cloud3";
cloud3.zPosition = 0;
SKAction *moveCloud3 = [SKAction moveToY:-200 duration:20];
[cloud3 runAction:[SKAction repeatActionForever:moveCloud3]];
[self addChild:cloud3];
}
-(void)update:(CFTimeInterval)currentTime {
....
// Remove Cloud from parent
[self enumerateChildNodesWithName:#"Cloud1" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0 || node.position.y < 0) {
[node removeFromParent];
//Add node back to the scene at a Y position above the screen then move
// the cloud downwards again. Repeat Forever.
// Exp: Move Cloud 1 to X position 40, Y position 500 (20 pixels above the top of the screen for an iPhone 4s)
}
else {
// Do something?
}
}];

You can add an SKAction to move your cloud back to the top:
SKSpriteNode *cloud1 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud1"];
cloud1.position = CGPointMake(40, 200);
cloud1.xScale = .5;
cloud1.yScale = .5;
cloud1.name = #"Cloud1";
cloud1.zPosition = 0;
SKAction *moveCloud1 = [SKAction moveToY:-20 duration:15];
// SKAction to move cloud back to top of screen
SKAction *moveToTop = [SKAction moveToY:CGRectGetHeight(self.frame)+20 duration:0];
SKAction *cloud1Action = [SKAction sequence:#[moveCloud1, moveToTop]];
[cloud1 runAction:[SKAction repeatActionForever:cloud1Action]];
You won't need the code in the update method.

I think you can simply change the position
Also, use CGRectGetMinY to use relative positioning, easier to manage different screen sizes.
And check only the Y position too, because X won't change if they're moving vertically.
This is my idea:
-(void)update:(NSTimeInterval)currentTime {
for(int i = 1; i<=3; i++){
NSString* cloud = [NSString stringWithFormat:#"Cloud%d",i];
[self enumerateChildNodesWithName:cloud usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0) {
node.position = CGPointMake(node.position.x,CGRectGetMaxY(self.frame)+20);
}
}];
}
}

Related

creating physics body for a SKSpriteNode in order to detect contact

as the title suggest, i have issue with creating contact between my enemies sprite and the hero laser(bullet). i create my enemies trough the following method and am adding them to the view.
-(void)Enemies{
//not always come
int GoOrNot = [self getRandomNumberBetween:0 to:1];
if(GoOrNot == 1){
SKSpriteNode *enemy;
int randomEnemy = [self getRandomNumberBetween:0 to:1];
if(randomEnemy == 0)
enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy_spaceship.png"];
else
enemy = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship15.png"];
enemy.scale = 0.4;
enemy.position = CGPointMake(450, 0);
enemy.hidden = NO;
CGPoint location = CGPointMake(450, 320);
SKAction *moveAction = [SKAction moveTo:location duration:2.5];
SKAction *doneAction = [SKAction runBlock:(dispatch_block_t)^() {
//NSLog(#"Animation Completed");
enemy.hidden = YES;
}];
SKAction *moveAsteroidActionWithDone = [SKAction sequence:#[moveAction,doneAction ]];
[enemy runAction:moveAsteroidActionWithDone withKey:#"asteroidMoving"];
[self addChild:enemy];
_enemyBullets = [[NSMutableArray alloc] initWithCapacity:kNumEnemyBullet];
for (int i = 0; i < kNumEnemyBullet; ++i) {
SKSpriteNode *enemyBullets = [SKSpriteNode spriteNodeWithImageNamed:#"rocket"];
enemyBullets.hidden = YES;
[enemyBullets setXScale:0.5];
[enemyBullets setYScale:0.5];
[_enemyBullets addObject:enemyBullets];
[enemy addChild:enemyBullets];
}
}
}
then i add the bullets through a mutable array and then adding my bullets to the enemies sprites as their child. that part works. i can create contact between hero and the enemy bullet and it gets detected. what i have issue with is that my hero's laser does not create contact with the enemy thus i can't make the enemy take hit from the laser. i tries adding the physics body to the method i am using but throws all the other sprites into hell and they don't respond properly anymore.
the following code is my collision code which i am using in update method:
for (SKSpriteNode *enemyBullets in _enemyBullets) {
if (enemyBullets.hidden) {
continue;
}
if ([_ship intersectsNode:enemyBullets]) {
enemyBullets.hidden = YES;
SKAction *blink = [SKAction sequence:#[[SKAction fadeOutWithDuration:0.1],
[SKAction fadeInWithDuration:0.1]]];
SKAction *blinkForTime = [SKAction repeatAction:blink count:4];
SKAction *shipExplosionSound = [SKAction playSoundFileNamed:#"explosion_large.caf" waitForCompletion:NO];
[_ship runAction:[SKAction sequence:#[shipExplosionSound,blinkForTime]]];
_lives--;
_livesLabel.text = [NSString stringWithFormat:#"%d Lives", _lives];
NSLog(#"your ship has been hit!");
}
}
is there any way that i can use to create a physics body for my enemies so i can create the contact between the hero laser and the enemies sprite with the same structure i have currently ? any help is amazingly appreciated.
You most certainly can create a physics body for your enemies. Right after this code enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy_spaceship.png"]; you can add a physics body like this:
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
enemy.physicsBody.categoryBitMask = CategoryEnemy; // or whatever you need
enemy.physicsBody.collisionBitMask = CategoryRock; // or whatever you need
enemy.physicsBody.contactTestBitMask = CategoryBullet // or whatever you need
enemy.physicsBody.dynamic = NO; // or YES
enemy.physicsBody.allowsRotation = NO; // or YES
enemy.physicsBody.restitution = 0; // or another value between 0 to 1
enemy.physicsBody.friction = 0.0; // or another value between 0 to 1
There are many ways to create a physics body. You can do a rectangle shape, circle shape, outline a texture or draw your own.
You should read the Apple docs on this subject to get a better understanding of what you can do and what properties are available.

Sprites spawning in incorrect position

I have a row of 5 sprites and they spawn every 4 seconds and move down along the Y-axis. The sprites are spawning the correct amount each time, I have 5 sprites equal spaced apart across the screen, they're all center perfectly.
However when the performSelector action is called, when it spawns them in they don't go to the right position. The move over to the left so far that I can only see half a sprite on the left side of the screen. After testing it, it seems they are all stacked on top of each other at the wrong position.
Any idea whats going on? I'd appreciate any help. Here's all the code I'm using in the scene.
-(void) addBricks:(CGSize)size {
for (int i = 0; i < 5; i++) {
//create brick sprite from image
SKSpriteNode *brick = [SKSpriteNode spriteNodeWithImageNamed:#"brick"];
//resize bricks
brick.size = CGSizeMake(60, 30);
//psoition bricks
int xPos = size.width/7.5 * (i+.5);
int yPos = 450;
brick.position = CGPointMake(xPos, yPos);
//add move action
SKAction *wait = [SKAction waitForDuration:3];
SKAction *move = [SKAction moveByX:0 y:-36.9 duration:1];
SKAction *sequence = [SKAction sequence:#[wait, move]];
SKAction *repeatMove = [SKAction repeatActionForever:sequence];
[brick runAction:repeatMove];
[self addChild:brick];
}
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:(243.0f/255) green:(228.0f/255) blue:(165.0f/255) alpha:1.0];
//add action to spawn bricks
SKAction *spawn = [SKAction performSelector:#selector(addBricks:) onTarget:(self)];
SKAction *delay = [SKAction waitForDuration:4];
SKAction *delayThenSpawn = [SKAction sequence:#[delay, spawn]];
[self runAction:[SKAction repeatActionForever:delayThenSpawn]];
[self addBricks:size];
}
return self;
}
as LearnCocos2D mentioned in your last question.. you cant use a selector when youre passing a parameter into a method.
addBricks expects size .. you arent passing size into it.
change your spawn action to
SKAction *spawn = [SKAction runBlock:^{
// make this whatever size you want, i'm just using the scene's size
[self addBricks:self.size];
}];
that should get you started

method not spawning sprite properly

Right now I have five bricks at the top of the screen all on the same y position and equally spaced apart. I would like to have five more breaks spawn outside of the top of the scene and slide down every 5 seconds. While the new bricks are sliding down I want the previous bricks to slide down to make room for the new. I want this sequence to continue on forever. I would like the new bricks to pick up the same properties of the old ones , such as when a ball hits them the react.
With the code I put in to accomplish this, I get a single brick spawning and then sliding down. Also the new brick does not act the same way as the old, it does not act like its a part of the physics world. How do I get 5 bricks spawning then sliding down and applying the same properties as the other bricks.
This code is the code I added to spawn more bricks:
-(void) spawnMoreBricks:(CGSize)size {
for (int i = 0; i < 5; i++) {
SKSpriteNode *brick = [SKSpriteNode spriteNodeWithImageNamed:#"brick"];
//resize bricks
brick.size = CGSizeMake(60, 30);
//position it
brick.position = CGPointMake(self.size.width/2, self.size.height);
SKAction *wait = [SKAction waitForDuration:5];
SKAction *spawn = [SKAction runBlock:^{[self addChild: brick];
SKAction *move = [SKAction moveByX:0 y:-80 duration:3];
[brick runAction: move];
}];
SKAction *spawnSequence = [SKAction sequence:#[wait, spawn]];
SKAction *repeatSequence = [SKAction repeatActionForever:spawnSequence];
[self runAction:repeatSequence];
}
}
This code adds the bricks into the scene right when it initializes, they react to the ball colliding with them and everything:
- (void) addBricks:(CGSize)size {
for (int i = 0; i < 5; i++) {
//create brick sprite from image
SKSpriteNode *brick = [SKSpriteNode spriteNodeWithImageNamed:#"brick"];
//resize bricks
brick.size = CGSizeMake(60, 30);
//psoition bricks
int xPos = size.width/5 * (i+.5);
int yPos = size.height - 25;
brick.position = CGPointMake(xPos, yPos);
brick.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:brick.frame.size];
brick.physicsBody.dynamic = NO;
brick.physicsBody.categoryBitMask = brickCategory;
//add bricks to scene
[self addChild:brick];
}
}

How to make object change in contact

I have a object in the scene which is determine by:
SKSpriteNode* object = [SKSpriteNode spriteNodeWithTexture:_objectTexture];
[object setScale:2];
object.position = CGPointMake(0, y);
object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object.size];
object.physicsBody.dynamic = NO;
then if my character hit it, it will stay there. Now, I want whenever the character hit the object, the object also move its upper half. How it moves depends on the index.
if index value in range 1, object change texture
if index value in range 2, object and character reflex by normal physical contact law
if index value in range 3, object broken into half
I tried to put
object.physicsBody.dynamic = YES;
then I saw it pulled down and disappear by gravity.
How do I set object dynamic and condition to satisfy the above need?
Thanks a lot for your help !
Added (as requested): My delegate method:
#interface MyScene () <SKPhysicsContactDelegate> {
SKSpriteNode* _character;
SKTexture* _object1;
SKTexture* _object2;
SKAction* _moveObjects;
SKNode* _moving;
SKNode* _objects;
BOOL _canRestart;
NSInteger _index;
SKTexture* characterTexture;
SKSpriteNode* _characterSprite;
}
later part (not sure if it is relevant)
-(id)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
/*Setup Scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -6.0);
self.physicsWorld.contactDelegate = self;
My objects are moved on scence by:
CGFloat distanceToMove = self.frame.size.width + 2 * _object1.size.width;
SKAction* moveObjects = [SKAction moveByX:-distanceToMove y:0 duration:0.01* distanceToMove];
SKAction* removeObjects = [SKAction removeFromParent];
_moveObjectsAndRemove = [SKAction sequence:#[moveObjects,removeObjects]];
Ps: add didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (_moving.speed > 0) {
//Score node
if ((contact.bodyA.categoryBitMask & scoreCategory) == scoreCategory || (contact.bodyB.categoryBitMask & scoreCategory) == scoreCategory)
{
_score++;
// Add a little visual
[_scoreLabelNode runAction:[SKAction sequence:#[[SKAction scaleTo:1.5 duration:0.1], [SKAction scaleTo:1.0 duration:0.1]]]];
}
else
//Collided with world
{
_moving.speed = 0;
_character.physicsBody.collisionBitMask = worldCategory;
[_character runAction:[SKAction rotateByAngle:M_PI * _character.position.y*0.01 duration:_character.position.y * 0.03] completion:^{_character.speed = 0; }];
....
}
}
You need to set
object.physicsBody.affectedByGravity = NO;

How to do impulse physics

Okay guys, I have two sprites one the enemy who moves to pick up items around the map as the items appear and it does that using SKActions then also I have the wall which he shouldn't go through. anyway. all I am trying to do is to make him bounce out if he hits the wall. How would I go about that?
Here is the code below:
// for the Enemy wall I have this code:
- (void) EnemyBelongings {
EnemyWall = [SKSpriteNode spriteNodeWithImageNamed:#"EnemyWall#2x"];
EnemyWall.name = #"hisWall";
EnemyWall.position = CGPointMake(512, 260);
EnemyWall.xScale = 0.09;
EnemyWall.yScale = 0.09;
EnemyWall.physicsBody.categoryBitMask = PhysicsCategoryEnemyWall;
EnemyWall.physicsBody.contactTestBitMask = PhysicsCategoryEnemy;
EnemyWall.physicsBody.collisionBitMask = 0;
[self addChild:EnemyWall];
}
// For the enemy character I have this code
- (void) Enemy {
_Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
_Enemy.position = CGPointMake(520, _Enemy.size.height/1.50);
_Enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width];
_Enemy.physicsBody.usesPreciseCollisionDetection = YES;
_Enemy.physicsBody.categoryBitMask = PhysicsCategoryEnemy;
_Enemy.physicsBody.contactTestBitMask = PhysicsCategoryEnemyWall;
_Enemy.physicsBody.collisionBitMask = 0;
[self addChild:_Enemy];
}
// For the enemy movement I have this code
- (void) EnemySpawner {
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
[_Enemy runAction:wait];
SKAction *actionXMove2 = [SKAction moveToX:ForEnemy.position.x duration:0.14];
SKAction *actionYMove2 = [SKAction moveToY:ForEnemy.position.y duration:0.14];
[_Enemy runAction:actionYMove2];
[_Enemy runAction:actionXMove2];
}];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if (contact.bodyA.categoryBitMask == PhysicsCategoryEnemy && contact.bodyB.categoryBitMask
== PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyA.node; // a is the enemy here
// Code here
SKNode *wall = contact.bodyB.node; // b is the enemy's wall here
// Code here
}
else if (contact.bodyB.categoryBitMask == PhysicsCategoryEnemy &&
contact.bodyA.categoryBitMask == PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyB.node;
// Code here
SKNode *wall = contact.bodyA.node;
// Code here
}
}
You need to applyImpulse on the node's physicsBody to make it simulate naturally and interact with other physicsBodies.
Please look at my answer here on how to do the same.
Copy the rwAdd, rwSub, rwMult, rwLength and the rwNormalize methods from this tutorial by Ray Wenderlich.
Then, try using this code:
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
CGPoint offset = rwSub(location, ForEnemy.position);
CGPoint direction = rwNormalize(offset);
float forceValue = 200; //Edit this value to get the desired force.
CGPoint shootAmount = rwMult(direction, forceValue);
CGVector impulseVector = CGVectorMake(shootAmount.x, shootAmount.y);
[_Enemy.physicsBody applyImpulse:impulseVector];
}];
PhysicsWorld should simulate bounces automatically. Just set your nodes' physics properties accordingly. Look for the 'restitution' property. It will determine the bounciness of your objects.
And don't use SKAction to move your enemies. This will just "teleport" your nodes around. They don't get to have any real velocity in the physicsWorld (and hence don't bounce). Instead, apply forces to your bodies (or set velocity vector manually).

Resources