Objective-c, SpriteKit particles not removed after action finished - ios

I'm making a game in Xcode, with SpriteKit and I've encountered a problem while working with particles.
Method of Initializing SKEmitterNode:
-(SKEmitterNode *)newExplosionEmitter {
NSString *explosionPath = [[NSBundle mainBundle] pathForResource:#"explosionH" ofType:#"sks"];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:explosionPath];
return explosion;
}
This method of my mine is the AI of CPU1, which is called in the update method.
-(void)CPU1AI {
SKSpriteNode *CPU1 = (SKSpriteNode *)[self childNodeWithName:CPU1CategoryName];
int aiX = CPU1.position.x;
int aiY = CPU1.position.y;
int ballX = self.ball.position.x;
int ballY = self.ball.position.y;
if ((aiY-ballY)<250 && !CPU1isDead) {
//CPU1 AI
}
if (CPU1isDead) {
float posY = CPU1.position.y;
float centerX = self.frame.size.width/2;
CGPoint goToPos = CGPointMake(centerX, posY);
SKAction *moveToPoint = [SKAction moveTo:goToPos duration:3];
SKAction *removeNode = [SKAction removeFromParent];
SKAction *CPUdead = [SKAction runBlock:^{CPU1isDead = NO;}];
SKAction *sequence = [SKAction sequence:#[moveToPoint, removeNode, CPUdead]];
explosion.position = CPU1.position;
explosion.zPosition = 10;
[CPU1 runAction:sequence];
if (![explosion hasActions]) {
explosion = [self newExplosionEmitter];
[self addChild:explosion];
[explosion runAction:[SKAction sequence:#[[SKAction fadeAlphaTo:0.5 duration:3.5],[SKAction removeFromParent]]]];
}
}
[explosion removeAllChildren];
}
After the SK runActions ended, I put "[explosion removeAllChildren]" just to make sure my particles will be removed, but with or without it, one is still left in the memory ( I guess ) and is still buffering.
Is it because I declared it as a static SKEmitterNode in my scene ?
Thanks in advance !

Add these methods to your SKScene subclass (code from Apple's website that I modified slightly)...
- (SKEmitterNode*) newExplosionNode: (CFTimeInterval) explosionDuration
{
SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:#"explosionH" ofType:#"sks"]];
// Explosions always place their particles into the scene.
emitter.targetNode = self;
// Stop spawning particles after enough have been spawned.
emitter.numParticlesToEmit = explosionDuration * emitter.particleBirthRate;
// Calculate a time value that allows all the spawned particles to die. After this, the emitter node can be removed.
CFTimeInterval totalTime = explosionDuration + emitter.particleLifetime+emitter.particleLifetimeRange/2;
[emitter runAction:[SKAction sequence:#[[SKAction fadeAlphaTo:0.5 duration:totalTime],
[SKAction removeFromParent]]]];
return emitter;
}
- (void)createExplosionAtPosition:(CGPoint)position
{
SKEmitterNode *explosion = [self newExplosionNode:3.5];
explosion.position = position;
explosion.zPosition = 10;
[self addChild:explosion];
}
Remove the following from your code...
explosion.position = CPU1.position;
explosion.zPosition = 10;
[CPU1 runAction:sequence];
if (![explosion hasActions]) {
explosion = [self newExplosionEmitter];
[self addChild:explosion];
[explosion runAction:[SKAction sequence:#[[SKAction fadeAlphaTo:0.5 duration:3.5],[SKAction removeFromParent]]]];
}
and replace it with this
[self createExplosionAtPosition:CPU1.position];
I suspect you want to set CPU1isDead = NO in the if statement, so the code isn't executed multiple times. You should also delete [explosion removeAllChildren].

Just to help somebody else looking for the same answer I tried this
the one above didn't work for me either - But put me on the right track.
-(void)addTimeBonusParticles
{
SKAction *remove = [SKAction performSelector:#selector(StopParticlesFlame) onTarget:self];
SKAction *wait = [SKAction waitForDuration: .5];
SKAction* blockAction = [SKAction runBlock:^
{
NSString *file = [[NSBundle mainBundle] pathForResource:#"TimeBonusMagic" ofType:#"sks"];
stars = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
[self addChild:stars];
stars.zPosition = 5;
stars.position = CGPointMake(countDown.position.x, countDown.position.y);
}];
[self runAction:[SKAction sequence:#[blockAction,wait,remove]]];
}

Related

Move several SKSpriteNode in array with delay?

I have a problem with moving several nodes in array. There is a code:
SKSpriteNode* paddle = (SKSpriteNode*)[self childNodeWithName: paddleXCategoryName];
SKAction *move = [SKAction moveTo:CGPointMake(paddle.position.x, CGRectGetMaxY(self.scene.frame)) duration:1.5];
NSMutableArray *shots = [[NSMutableArray alloc] init];
for (int i = 1; i<=10; i++) {
SKSpriteNode *shot = [[SKSpriteNode alloc] initWithImageNamed:#"shoot.png"];
shot.size = CGSizeMake(2, 5);
shot.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(2, 6)];
shot.physicsBody.categoryBitMask = shotCategory;
shot.physicsBody.contactTestBitMask = blockCategory;
shot.physicsBody.collisionBitMask = blockCategory;
[shots addObject:shot];
}
int i = 0;
for (SKSpriteNode *shot in shots) {
SKAction *wait = [SKAction waitForDuration:0.2+0.3*i];
shot.position = paddle.position;
[self addChild:shot];
[shot runAction:[SKAction sequence:#[wait, move]] completion:^{
[shot removeFromParent];
}];
}
I have only one node moving. What am I doing wrong?
Actually all of your nodes are moving together without delay to the same point. This is because you are not incrementing your i variable inside the loop. Try the code below. For the SKAction to work properly also set affectedByGravity to false.
int i = 0;
for (SKSpriteNode *shot in shots) {
SKAction *wait = [SKAction waitForDuration:0.2+0.3*i];
shot.position = paddle.position;
[self addChild:shot];
shot.physicsBody.affectedByGravity = false; // Added line.
[shot runAction:[SKAction sequence:#[wait, move]] completion:^{
[shot removeFromParent];
}];
i++; // Added line.
}
They all run the same move action. Each sprite needs to run its own copy of the action. Try changing the runAction statement by replacing move with [move copy]:
[shot runAction:[SKAction sequence:#[wait, [move copy]]] completion:^{
[shot removeFromParent];
}];

SpriteKit - Adding a timer

I'd like to add a timer to a simple SpriteKit game. Using the code suggested in an existing answer ("Spritekit - Creating a timer") causes 5 errors (s. comments below). Maybe there are other ideas, too!
Thanks a lot!
This is my .m file:
#implementation GameScene {
BOOL updateLabel;
SKLabelNode *timerLabel;
}
int timer;
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor whiteColor];
timer = 0;
updateLabel = false;
timerLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
timerLabel.text = #"0";
timerLabel.fontSize = 48;
timerLabel.fontColor = [SKColor greenColor];
timerLabel.position = CGPointMake(700,50);
[self addChild: timerLabel];
}
return self;
}
-(void) didMoveToView:(SKView *)view {
}
The following code gives me the trouble:
id wait = [SKAction waitForDuration:1.0];//Redefinition of 'wait' as different kind of symbol
id run = [SKAction runBlock:^{//Initializer element is not a compile-time constant
timer ++;
updateLabel = true;
}];
[node runAction:[SKAction repeatActionForever:[SKAction sequence:#[wait, run]]];//1. Expected identifier or '('; 2. Use of undeclared identifier 'node'; 3. Collection element of type 'pid_t (int *)' is not an Objective-C object
-(void)update:(CFTimeInterval)currentTime {
if(updateLabel == true){
timerLabel.text = [NSString stringWithFormat:#"%i",timer];
updateLabel = false;
}
}
#end
int timer = 0;
SKLabelNode timerLabel;
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKAction *wait = [SKAction waitForDuration:1];
SKAction *performSelector = [SKAction performSelector:#selector(timerLabel) onTarget:self];
SKAction *removeChild = [SKAction runBlock:^{
[timerLabel removeFromParent];
}];
SKAction *increaseTimer = [SKAction runBlock:^{
timer++;
}];
SKAction *sequence = [SKAction sequence:#[removeChild, performSelector, wait, increaseTimer]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
return self;
}
-(void)timerLabel
{
timerLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
timerLabel.text = [NSString stringWithFormat:#"%i", timer];
timerLabel.fontSize = 48;
timerLabel.fontColor = [SKColor greenColor];
timerLabel.position = CGPointMake(700,50);
[self addChild: timerLabel];
}
Try to call with perform selector. I hope this will work.I didn't test.

Adding a start and end screen in ios game

I am having trouble having my game begin paused so that the player has to press a button to start the game. I also have collision detection working, so when the two objects collide they just fall off of the screen. Here is my current code:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
[self buildBackground];
[self startScrolling];
_firstPosition = CGPointMake(self.frame.size.width * 0.817f, self.frame.size.height * .40f);
_squirrelSprite = [SKSpriteNode spriteNodeWithImageNamed:#"squirrel"];
_squirrelSprite.position = _firstPosition;
_atFirstPosition = YES;
_squirrelSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
_squirrelSprite.physicsBody.categoryBitMask = squirrelHitCategory;
_squirrelSprite.physicsBody.contactTestBitMask = nutHitCategory;
_squirrelSprite.physicsBody.collisionBitMask = nutHitCategory;
_squirrelSprite.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild:_squirrelSprite];
// Declare SKAction that waits 2 seconds
SKAction *wait = [SKAction waitForDuration:3.0];
// Declare SKAction block to generate the sprites
SKAction *createSpriteBlock = [SKAction runBlock:^{
const BOOL isHeads = arc4random_uniform(100) < 50;
NSString* spriteName = isHeads ? #"lightnut.png" : #"darknut.png";
SKSpriteNode *nut = [SKSpriteNode spriteNodeWithImageNamed:spriteName];
BOOL heads = arc4random_uniform(100) < 50;
nut.position = (heads)? CGPointMake(257,600) : CGPointMake(50,600);
nut.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(200,160)];
nut.physicsBody.categoryBitMask = nutHitCategory;
nut.physicsBody.contactTestBitMask = squirrelHitCategory;
nut.physicsBody.collisionBitMask = squirrelHitCategory;
nut.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild: nut];
SKAction *moveNodeUp = [SKAction moveByX:0.0 y:-700.0 duration:1.3];
[nut runAction: moveNodeUp];
}];
// Combine the actions
SKAction *waitThenRunBlock = [SKAction sequence:#[wait,createSpriteBlock]];
// Lather, rinse, repeat
[self runAction:[SKAction repeatActionForever:waitThenRunBlock]];
}
return self;
}
My touchesBegan:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (_atFirstPosition)
{
SKAction *moveNodeLeft = [SKAction moveByX:-207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeLeft withKey:#"moveleft"];
} else {
SKAction *moveNodeRight = [SKAction moveByX:207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeRight withKey:#"moveright"];
}
_atFirstPosition = !_atFirstPosition;
_squirrelSprite.xScale *= -1.0;
}
Finally, my didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
if(firstBody.categoryBitMask == squirrelHitCategory || secondBody.categoryBitMask == nutHitCategory)
{
}
}
I've been trying to figure this out for the last 2 weeks and I have read many tutorials and code to start the game with a simple start button and end screen, but nothing has worked. Any help is greatly appreciated!
I would use a navigation controller and have your start button on the root view and have that button push a viewcontroller that presents your game.

Sprite Kit pause game when lose a life

I can not figure this out. I have a game where a player has to catch berries as they fall from the sky. When the player misses one, I want the screen to flash red and the berries to stop falling and for the game to pause for 3 seconds then resume.
#interface SpriteMyScene : SKScene{
SKAction *sceneUnPaused;
SKAction *scenePaused;
}
2 methods to pause the game.
-(SKAction*)unpaused {
sceneUnPaused = [SKAction runBlock:^{
self.scene.view.paused = NO;
}];
return sceneUnPaused;}
-(SKAction*)paused{
scenePaused = [SKAction runBlock:^{
self.scene.view.paused = YES;
}];
return scenePaused;
}
The method that creates the berries and their actions.
- (void)addBerry {
...
...
// Create the actions
//For each individual berry
SKAction * actionMove = ...
SKAction * gameWon = ...
SKAction * actionMoveDone = ...
SKAction *wait3Seconds = [SKAction waitForDuration:3];
SKAction * loseAction = [SKAction runBlock:^{
[self subtractLives];
NSLog(#"Lost a life");
[self vibrate];
[self runAction:scenePaused];
[self runAction:wait3Seconds];
[berry removeFromParent];
[self runAction:sceneUnPaused];
if (_playerLives == 0){
[[SoundManager sharedManager] stopMusic];
SKTransition *reveal = [SKTransition fadeWithDuration:1.0];
SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size andScore:_berriesKilled];
[self.view presentScene:gameOverScene transition: reveal];}}];
SKAction *berrie = [SKAction runBlock:^{
[berry runAction:actionMove];}];
[berry runAction:[SKAction sequence:#[berrie, gameWon, actionMove, loseAction, actionMoveDone]]];
}
I don't know if this is necessary or not for you guys to help me figure out why it won't work, but here is the update method that decides on when I will spawn the berries.
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > [self determineAmountOfBerries]) {
self.lastSpawnTimeInterval = 0;
[self addBerry];
}
}
When the player misses a berry, just do this:
[self.scene.view setPaused:YES];
Start an NSTimer that does the following after 3 seconds:
[berry removeFromParent];
[self.scene.view setPaused:NO];

SpriteKit - How to add objects with specific distance

in my game I am adding enemies to the scene every second , I need to describe an specific distance between pervious object that my main character move through these enemy , here is my codes that add enemy to the scene :
- (void)createEnemy {
int GoOrNot = [self getRandomNumberBetween:0 to:1];
if(GoOrNot == 1){
int randomEnemy = [self getRandomNumberBetween:0 to:1];
if(randomEnemy == 0)
enemy = [[SKSpriteNode alloc]initWithImageNamed:#"car.png"];
else
enemy = [[SKSpriteNode alloc]initWithImageNamed:#"block.png"];
int xPostion = [self placeRandomObject] ;
int yPostion = 1150;
enemy.position = CGPointMake(xPostion, yPostion);
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.name = #"enemy";
enemy.physicsBody.categoryBitMask = enemyCategory;
enemy.physicsBody.contactTestBitMask = carCategory;
enemy.physicsBody.collisionBitMask = 0;
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.usesPreciseCollisionDetection = YES;
[self addChild:enemy];
SKAction *wait = [SKAction waitForDuration:.20];
SKAction *move = [SKAction moveToY:self.scene.frame.origin.y-10 duration:enemySpeed];
SKAction *remove = [SKAction removeFromParent];
SKAction *runAction = [SKAction sequence:#[wait,move , remove]];
[enemy runAction:[SKAction repeatActionForever:runAction]];
}
}
adding enemy :
- (void)addEnemies {
SKAction *wait = [SKAction waitForDuration:.55];
SKAction *callEnemies = [SKAction runBlock:^{ [self createEnemy];}];
updateEnimies = [SKAction sequence:#[wait,callEnemies]];
[self runAction:[SKAction repeatActionForever:updateEnimies] withKey:#"addEnemy"];
}
You can create new variable to hold the last position of the previous enemy.
CGPoint positionOfLastEnemy;
when you create new enemy assign the position of created enemy to this variable:
positionOfLastEnemy = CGPointMake(xPostion, yPostion);
if you need to update the enemy position you can do it in your collision detection method.
After that just use the positionOfLastEnemy variable to calculate the distance you need.
The other way is to add variable to hold the last enemy
SKSpriteNode *lastEnemy;
in your createEnemy method save reference to your created enemy.
lastEnemy = enemy;
And if you need the distance of the last enemy just use
lastEnemy.position

Resources