SKAction repeatAction withKey and completion? - objective-c

I have an SKAction:
SKAction *myAction = [SKAction performSelector:#selector(methodA) onTarget:self];
I want to repeat this action 50 times before calling methodB upon completion of the 50 actions.
[[self runAction:[SKAction repeatAction:myAction count:50]
withKey:#"myActionKey"]
completion:^{
[self methodB];
}];
It is giving me a bad receiver type 'void' error. The error goes away if I take out the withKey:#"myActionKey" part but I need to get the key because I might need to call removeActionForKey:#"myActionKey" at some point.
Is there any way to work around this?

The command you are going for does not exist but you can do this:
SKAction *callMethodA = [SKAction runBlock:^{
[self methodA];
}];
SKAction *myAction = [SKAction repeatAction:callMethodA count:50];
SKAction *callMethodB = [SKAction runBlock:^{
[self methodB];
}];
SKAction *sequence = [SKAction sequence:#[myAction, callMethodB]];
[self runAction:sequence withKey:#"myKey"];

Related

Running actions one after another on different nodes

What I am trying to do is to run one action (playForwardAnimation) which is consisting of
multiple actions (running on different nodes) and after all actions are done, to run an action called playBackwardAnimation.
Here is the code: `
SKAction *wait = [SKAction waitForDuration:3.5];
SKAction *forwardAnimationAction = [SKAction animateWithTextures:forwardAnimationFrames timePerFrame:0.1];
SKAction *backwardAnimationAction = [SKAction animateWithTextures:backwardAnimationFrames timePerFrame:0.1];
SKAction *forwardAnimation = [SKAction sequence:#[wait , forwardAnimationAction, wait, forwardAnimationAction]];
SKAction *backwardAnimation = [SKAction sequence:#[wait , backwardAnimationAction, wait, forwardAnimationAction]];
SKAction *playForwardAnimation = [SKAction runBlock:^{
[node5 runAction:forwardAnimation completion:^{
[node4 runAction:[SKAction rotateByAngle:-0.1 duration:0.3]];
[node3 runAction: [SKAction rotateByAngle:-0.2 duration:0.3]];
[node2 runAction: [SKAction rotateByAngle:-0.3 duration:0.3]];
NSLog(#"forward action executed");
}];
}];
SKAction *playBackwardAnimation = [SKAction runBlock:^{
[node5 runAction:backwardAnimation completion:^{
[node4 runAction:[SKAction rotateByAngle:0.1 duration:0.3]];
[node3 runAction: [SKAction rotateByAngle:0.2 duration:0.3]];
[node2 runAction: [SKAction rotateByAngle:0.3 duration:0.3]];
NSLog(#"backward action executed");
}];
}];
SKAction *sequence = [SKAction sequence:#[playForwardAnimation,playBackwardAnimation]];
SKAction *action = [SKAction repeatActionForever:sequence];
[holder runAction:action];`
The problem is that playBackwardAnimation is not called after the playForwardAnimation is over completely. It looks like both animations are called at the same time. How this can be done? The hierarchy of nodes used in animation looks like this:
holder (upperArea)
upperArea (node4,node3,node2)
node4(node5)
Unfortunately it gets messy when youre running actions on different nodes like this and you need a strict sequence. Here's one way to do it. I'm sure there are many.
Hopefully I got this right
SKAction *wait = [SKAction waitForDuration:3.5];
SKAction *forwardAnimationAction = [SKAction animateWithTextures:forwardAnimationFrames timePerFrame:0.1];
SKAction *backwardAnimationAction = [SKAction animateWithTextures:backwardAnimationFrames timePerFrame:0.1];
SKAction *forwardAnimation = [SKAction sequence:#[wait , forwardAnimationAction, wait, forwardAnimationAction]];
SKAction *backwardAnimation = [SKAction sequence:#[wait , backwardAnimationAction, wait, forwardAnimationAction]];
CGFloat rotateDur = 0.3;
SKAction *rotateWait = [SKAction waitForDuration:rotateDur];
SKAction *forwardAnimationBlock = [SKAction runBlock:^{
[node4 runAction:[SKAction rotateByAngle:-0.1 duration:rotateDur]];
[node3 runAction: [SKAction rotateByAngle:-0.2 duration:rotateDur]];
[node2 runAction: [SKAction rotateByAngle:-0.3 duration:rotateDur]];
}];
SKAction *backwardAnimationBlock = [SKAction runBlock:^{
[node4 runAction:[SKAction rotateByAngle:-0.1 duration:rotateDur]];
[node3 runAction: [SKAction rotateByAngle:-0.2 duration:rotateDur]];
[node2 runAction: [SKAction rotateByAngle:-0.3 duration:rotateDur]];
}];
SKAction *sequence = [SKAction sequence:#[forwardAnimation, forwardAnimationBlock, rotateWait, backwardAnimation, backwardAnimationBlock, rotateWait]];

How do I implement enemies that shoot?

I made a game with SpriteKit. I structured my game by coding a GameScene (SKScene) and a separate enemy class.
I want the spawning enemies to shoot a particle e.g. every 2 seconds (just moving an SKEmitterNode by the y axis) I tried to do it with a timer, but it doesn't really work.
I call my Enemy class from the gaming Scene with this code:
GameScene.m
-(void)enemiesLevel1{
EnemyClass* wave1 = [[EnemyClass alloc] init];
[wave1 enemiesLevel1:self];
}
And I am basically calling this method from EnemyClass.m
-(void)enemiesLevel1:(SKScene *)scene
{
enemy = [SKSpriteNode spriteNodeWithImageNamed:Enemy];
//Enemy Path
(...)
SKAction *followPath2 = [SKAction followPath:pathRef2
asOffset:NO
orientToPath:YES
duration: pathSpeed];
SKAction *forever = [SKAction repeatActionForever:followPath2];
//PhysicsBody Eigenshaften
enemy.physicsBody =[SKPhysicsBody bodyWithCircleOfRadius:enemy.size.width];
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.categoryBitMask = enemyCategory;
[enemy runAction:forever];
[scene addChild:enemy];
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(weaponParticle)
userInfo:nil
repeats:YES];
}
-(void)weaponParticle{
screenHeight = self.frame.size.height;
screenWidth = self.frame.size.width;
//Schuss-Particles
enemyParticlePath = [[NSBundle mainBundle] pathForResource:#"ShootFire" ofType:#"sks"];
enemyParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:enemyParticlePath];
enemyParticle.physicsBody =[SKPhysicsBody bodyWithCircleOfRadius:0.2];
enemyParticle.physicsBody.categoryBitMask = shootCategory;
enemyParticle.physicsBody.contactTestBitMask = playerCategory;
//Schuss-Action
moveDown = [SKAction moveByX:0.0 y:-screenHeight duration:1.0];
remove = [SKAction removeFromParent];
weaponShot = [SKAction sequence:#[moveDown, remove]];
enemyParticle.position = CGPointMake(enemy.position.x, enemy.position.y+10);
[self addChild:enemyParticle];
[enemyParticle runAction: weaponShot];
}
The enemies are spawning 1 by 1 just how I wanted, but they can't shoot. Can anyone help me out?
Instead of scheduling NSTimer try something like this:
SKAction *shoot = [SKAction runBlock:^{
// add code that shoots here
}];
SKAction *wait = [SKAction waitForDuration:0.5];
[enemy runAction:[SKAction repeatActionForever:[SKAction sequence:#[shoot, wait]]]];

How to repeat this action 3 times instead of repeating it forever- Sprite Kit

How to repeat this action 3 or 2 times instead of repeating it forever
SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:#"AmericanTypewriter-Bold"];
label.text = #"Boom";
label.fontColor = [SKColor blackColor];
label.fontSize = 90;
label.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame)+25);
SKAction *disappear = [SKAction fadeAlphaTo:0.0 duration:0.2];
SKAction *appear = [SKAction fadeAlphaTo:1.0 duration:0.2];
SKAction *pulse = [SKAction sequence:#[disappear,appear]];
[label runAction:[SKAction repeatActionForever:pulse]];
[self addChild:label];
You need to use SKAction's repeatAction:count: method documented here.
[label runAction:[SKAction repeatAction:pulse count:3]];

Stop and quit SKVideo

H! I'm using SKVideoNode to play a video on a project. The thing is that when I try to stop it and get back to the current actions sometimes it works but sometimes not.
Here is the code where I play it. Thanks in advance.
SKAction *actionPlayVideo = [SKAction runBlock:^{
SKVideoNode *introVideoLevel1 = [SKVideoNode videoNodeWithVideoFileNamed:#"escenario_intermedio.mov"];
introVideoLevel1.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[introVideoLevel1 setName:#"IntroVideo"];
introVideoLevel1.size = CGSizeMake(ipad_2_width, ipad_2_height);
[self addChild: introVideoLevel1];
[introVideoLevel1 play];
}];
SKAction *actionStopVideo = [SKAction runBlock:^{
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
usleep(6900000);
//[[self childNodeWithName:#"IntroVideo"] stop];
[[self childNodeWithName:#"IntroVideo"] removeFromParent];
});
}];
[self runAction:[SKAction sequence:#[actionPlayVideo, actionStopVideo]]];
Solved it! Just added another action (waitTime) between play and stop and removed the dispatch_async and the usleep

Objective c loop with time delay

I want that every object from the NSMuttableArray to appear for 4 sec. and than disappear.And on this way to iterate all items from the array.Instead the result i got is that all items appears and after the period disappear together.
-(void)showTreasures{
for (int i = 0; i < _treasures.count; i++)
{
SKSpriteNode *obj = [_treasures objectAtIndex:i];
SKAction *show = [SKAction runBlock:^{
obj.hidden = NO;
}];
SKAction *wait = [SKAction waitForDuration:4];
SKAction *hide = [SKAction runBlock:^{
obj.hidden = YES;
}];
SKAction *sequence = [SKAction sequence:#[show, wait, hide]];
[obj runAction:sequence completion:^{
NSLog(#"Item %d", i);
}];
}
}
I think it's good job for recursive method, method which call itself.
You can created array of all of your objects (SKSpriteNode) and pass it to the method which takes first (or last) object and run appropriate action, remove the object and call the method again:
NSMutableArray *arrOfObject = //arrat with all of the sprites you want to show
[self runShowAction:arrOfObject];
-(void)runShowAction:(NSMutableArray*)array {
//if no object in the array return
if(array.count <= 0) return;
SKSpriteNode *obj = [array firstObject];
//Run your code here
//...
//On completion remove object from array and run this method again
[obj runAction:sequence completion:^{
NSLog(#"Item %d", i);
[array removeObject:obj];
[self runShowAction:array];
}];
}
Why don't you use an array to build up a list of all the actions, then run a sequence of all of those actions?
-(void)showTreasures
{
NSMutableArray *actions = [NSMutableArray array];
for (int i = 0; i < _treasures.count; i++)
{
SKSpriteNode *obj = [_treasures objectAtIndex:i];
SKAction *show = [SKAction runBlock:^{
obj.hidden = NO;
}];
SKAction *wait = [SKAction waitForDuration:4];
SKAction *hide = [SKAction runBlock:^{
obj.hidden = YES;
}];
SKAction *finish = [SKAction runBlock:^{
NSLog(#"Item %d", i);
}];
[actions addObjectsFromArray:#[show, wait, hide, finish]];
}
SKAction *sequence = [SKAction sequence:actions];
[obj runAction:sequence completion:^{
NSLog(#"Finished all items");
}];
}