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];
}];
Related
I am working on a sprite kit game in which I have an action called playCountdown:
SKAction *playCountdown = [SKAction performSelector:#selector(startCountdown) onTarget:self];
playCountdown works fine but when it completes, the completion block never gets called.
[self runAction:playCountdown completion:^{
NSLog(#"anything goes");
}];
Here is startCountdown for completeness:
- (void)startCountdown{
SKLabelNode *countdownLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
__block short countdownValue = 3;
countdownLabel.position = CGPointMake(screenWidth/2, screenHeight/2 - countdownLabel.fontSize/2);
countdownLabel.zPosition = 100.0;
countdownLabel.text = [NSString stringWithFormat:#"%d", countdownValue];
countdownLabel.fontColor = [UIColor blackColor];
countdownLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeBaseline;
[self addChild:countdownLabel];
SKAction *zoomIn = [SKAction scaleTo:1.5f duration:0.9];
SKAction *fadeOut = [SKAction fadeAlphaTo:0.0f duration:0.1];
SKAction *updateCount = [SKAction customActionWithDuration:0.0 actionBlock:^(SKNode *node, CGFloat elapsedTime) {
SKLabelNode *textNode = (SKLabelNode *)node;
countdownValue --;
if (countdownValue == 0) {
textNode.text = #"PLAY!";
}
else{
textNode.text = [NSString stringWithFormat:#"%d", countdownValue];
}
textNode.alpha = 1.0f;
textNode.xScale = 1.0f;
textNode.yScale = 1.0f;
}];
SKAction *sequence = [SKAction sequence:#[zoomIn, fadeOut, updateCount]];
SKAction *repeat = [SKAction repeatAction:sequence count:4];
[countdownLabel runAction:repeat completion:^{
[countdownLabel removeFromParent];
}];
NSLog(#"completed!");
}
I have verified that startCountdown actually reaches its end. However the completion block never gets called. Any ideas?
Thanks
I would recommend you verify that your code is indeed being called by creating a very simple NSLog only block.
If you are still having problems, consider using a SKAction sequence instead. This will accomplish the same thing as you are trying to do with a completion block.
There must be an issue with a SKAction created using a selector... The code:
SKAction *playCountdown = [SKAction performSelector:#selector(startCountdown) onTarget:self];
would execute calling:
[self runAction:playCountdown completion:^{ ... }];
although the completion block would never get called.
I have instead created the playCountdown action using a block:
SKAction *playCountdown = [SKAction runBlock:^{
[self startCountdown];
}];
and the completion block now gets called. The problem now is that the end of the playCountdown action is not "understood" by the system and the 2 actions do not run serially but almost concurrently.
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]]];
}
How can I tell the walk cycle to continue animating until an NSNumber with bool flag stops it?
And also remove the SKSpriteNode with the SKTexture from the stage?
SKSpriteNode *walk = [SKSpriteNode spriteNodeWithTexture:[_walkTextures objectAtIndex:0]];
walk.zPosition = 100;
walk.scale = spliffScale;
walk.position = location;
[self addChild:walk];
SKAction *walkAction = [SKAction animateWithTextures:_walkTextures timePerFrame:0.03];
SKAction *remove = [SKAction removeFromParent];
[walk runAction:[SKAction sequence:#[walkAction, walkAction, remove]]];
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];
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