I have an SKScene where I am adding an SKSpriteNode. I have subclassed SKSpriteNode class to create this node. In the subclass i am defining certain SKActions on the sprite. What i want is that when the SKAction sequence that runs on this sprite ends, i add a new sprite node to the scene. How is this possible. Following is my code:
code for sequence that i am running on skspritenode subclass (TEMissileNode) :-
SKAction *moveDown = [SKAction moveToY:self.position.y - 20 duration:0.2];
SKAction *animation = [SKAction animateWithTextures:textures timePerFrame:time/7];
SKAction *moveMissileProjectile = [SKAction moveTo:pointoffScreen duration: time];
SKAction *group = [SKAction group:#[animation, moveMissileProjectile]];
SKAction *sequence = [SKAction sequence : #[moveDown,group, [SKAction removeFromParent]]];
[self runAction:sequence];
From the main scene I am calling the method which executes these actions
TEMissileNode *missile = [TEMissileNode missileAtPoint: CGPointMake(copter.position.x + copter.size.width/2, copter.position.y - 20)
Type:TEMissileTypeA];
[self addChild:missile];
[missile moveTowardsPosition:position];
What i want is that after the completion of the method (moveTowardsPosition:position), I add another child sprite node to the scene but how to get a completion notification from the method.
There are two ways of going about calling code after the completion of an action.
Use the completion block after runAction.
[self runAction:sequence completion:^{
//Add relevant code here.
}];
Or, add another block to execute at the end of the sequence.
SKAction *actionBlock = [SKAction runBlock:^{
//Add relevant code here.
}];
SKAction *sequence = [SKAction sequence : #[moveDown,group, [SKAction removeFromParent], actionBlock]];
Related
I am building a game with sprite kit and have a sprite moving from left to right with an endless action.
SKAction *moveRight = [SKAction moveByX:3.0 y:0 duration:3.5];
SKAction *moveLeft = [SKAction moveByX:-3.0 y:0 duration:3.5];
SKAction *reversedMoveRight = [moveRight reversedAction];
SKAction *reversedMoveLeft = [moveLeft reversedAction];
SKAction *completion = [SKAction runBlock:^{
SKAction *sequence = [SKAction sequence:#[moveRight, moveLeft, reversedMoveRight,reversedMoveLeft]];
SKAction *endlessAction = [SKAction repeatActionForever:sequence];
[snake runAction:endlessAction];
}];
[snake runAction:completion withKey:#"KeySnake"];
This works, but after a short period of time my game slows down. The CPU and memory usage continues to grow in the debug navigator in Xcode. I think the endless action is causing the problem, but I don't know any other way to move it constantly like I want to.
From your comment I understand you are calling
[snake runAction:completion withKey:#"KeySnake"];
inside the update method. This is the problem, infact you are creating and running a new action every frame.
Move the whole block of code (you showed in your question) inside a method that is called only once.
Example: here I also refactored the construction of your action and changed the x value (in the action) from 3.0 to 100.0
#import "GameScene.h"
#implementation GameScene
{
SKSpriteNode * _snake;
}
- (void)didMoveToView:(SKView *)view {
[self addSnake];
[self startSnakeMoving];
}
- (void)addSnake{
_snake = [SKSpriteNode spriteNodeWithImageNamed:#"Snake"];
_snake.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:_snake];
}
- (void)startSnakeMoving {
SKAction * moveRight = [SKAction moveByX:100.0 y:0 duration:3.5];
SKAction * sequence = [SKAction sequence:#[moveRight, moveRight.reversedAction, moveRight.reversedAction, moveRight]];
SKAction * endlessAction = [SKAction repeatActionForever:sequence];
[_snake runAction:endlessAction withKey:#"KeySnake"];
}
#end
Rather than making snake (whatever is snake) calling your endless action (that is in fact 'sequence' repeated forever), you should call 'sequence' via a CADisplayLink (which is a screen refresh), that is make to drive the rendering of anything (so game) at screen refresh frequency.
I have used sequence of action and one of the action plays sound for me.
SKAction *wait = [SKAction waitForDuration:1];
NSString* completion_sound = self.gameData[#"level_data"][#"completion_sound"];
SKAction *playSound = [SKAction playSoundFileNamed:completion_sound waitForCompletion:NO];
SKAction *completion = [SKAction runBlock:^{
// do some code here.
}];
SKAction *sequence = [SKAction sequence:#[wait, playSound, completion]];
[self runAction:sequence];
But I want to detect a completion of sound. seems like sound is still playing while block is invoking.
It appears you are passing NO for the waitForCompletion: argument and it should be YES.
If YES, the duration of this action is the same as the length of the audio playback. If NO, the action is considered to have completed immediately.
So change to YES in this line of code...
SKAction *playSound = [SKAction playSoundFileNamed:completion_sound waitForCompletion:YES];
...and the playSound action duration will be the length of the sound, therefor the 'completion block' SKAction *completion will not be executed until the sound is finished.
Your sequence is wrong. The correct order should be:
SKAction *sequence = [SKAction sequence:#[playSound, wait, completion]];
I have subclassed SKSpriteNode for my enemies. One of the methods in this class is a "blood squirt" SKAction sequence comprised of blocks which takes about 0.9 secs to complete.
-(void)runBloodBurst
{
SKSpriteNode *bloodBurst = [SKSpriteNode spriteNodeWithTexture:[_animations bloodBurstLeft2Start]];
bloodBurst.zPosition = 250;
bloodBurst.position = CGPointMake(-32, 0);
SKAction *wait0 = [SKAction waitForDuration:0.2];
SKAction *wait1 = [SKAction waitForDuration:0.7];
SKAction *block0 = [SKAction runBlock:^{
[bloodBurst runAction:[_animations bloodBurstLeft2]];
[self addChild:bloodBurst];
}];
SKAction *block1 = [SKAction runBlock:^{
[bloodBurst removeFromParent];
self.readyBloodBurst2 = true;
}];
[self runAction:[SKAction sequence:#[wait0, block0, wait1, block1]]];
}
If the player is attacking very enthusiastically, the blood squirt method could be called again before the 0.9 secs have passed (numerous blood squirts). However, this would randomly throw a EXEC_BAD_ACCESS error.
My solution was use a BOOL ivar to not allow running the sequence again until it has completed. This stopped the EXEC_BAD_ACCESS but I do not understand the root cause behind it.
An explanation would be greatly appreciated.
EDIT: As per LearnCocos2D request, added exception breakpoint and got this:
So in the game I'm building I want to repeat an action, but I want it to have an initial delay. So for example, the action would execute three seconds after the user started the game, but after it executes for the first time, there's no longer a three second delay. What can I do to solve this?
Thanks in advance!
You could use an SKAction to make a delay, then put it at the beginning of your sequence.
Apple gives some sample code on sequences:
SKAction *moveUp = [SKAction moveByX:0 y:100.0 duration:1.0];
SKAction *zoom = [SKAction scaleTo:2.0 duration:0.25];
SKAction *wait = [SKAction waitForDuration: 0.5];
SKAction *fadeAway = [SKAction fadeOutWithDuration:0.25];
SKAction *removeNode = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:#[moveUp, zoom, wait, fadeAway, removeNode]];
[node runAction: sequence];
You can use SKAction waitForDuration to make a delay.
I'm trying to make a node scale down to a small size, move down, and then once all that happens animate it repeatedly (at the same scaled down size with a different texture).
Here is my method that scales the node down and moves it. That part is working correctly. However once it finishes doing that I need to change the sprites texture and animate it.
- (void)shrinkAndMoveToPosition:(CGPoint)position {
SKAction *move = [SKAction moveTo:position duration:.5];
SKAction *scale = [SKAction scaleTo:.3 duration:.5];
SKAction *moveAndScale = [SKAction group:#[move, scale]];
[self runAction:moveAndScale completion:^{
NSArray *textures = #[[SKTexture textureWithImageNamed:#"ship-small_01"],
[SKTexture textureWithImageNamed:#"ship-small_02"],
[SKTexture textureWithImageNamed:#"ship-small_03"],
[SKTexture textureWithImageNamed:#"ship-small_04"]];
SKAction *animate = [SKAction animateWithTextures:textures timePerFrame:0.5];
[self runAction:[SKAction repeatActionForever:animate]];
}];
}
The problem is that whenever my completion block runs the sprite jumps back up to the size of the texture. How can I maintain my scaled down size?
I just needed to call a different animate method and pass in YES to the resize parameter:
SKAction *animate = [SKAction animateWithTextures:textures timePerFrame:0.5 resize:YES restore:NO];