iOS Changing SKAction speed in repeating action - ios

I've read in a few placing that changing the speed variable of an SKAction will change the speed; however, that doesn't seem to be working for me.
- (void)startAnimating {
SKAction *moveDown = [SKAction moveByX:0 y:-[CAUtilities screenSize].height duration:self.animationDuration];
[self setMoveDownAction:[SKAction repeatActionForever:moveDown]];
[self runAction:self.moveDownAction];
}
- (void)incAnimationSpeedBy:(CGFloat)aFloat {
self.moveDownAction.speed += 0.5;
NSLog(#"%f", self.moveDownAction.speed);
}
The actual value of self.moveDownAction.speed changes as seen in the NSLog call, but the actual animation doesn't change.
I have incAnimationSpeedBy: being called when the screen is tapped, so using a SKAction sequence with a runBlock won't work for my needs.
I've tried:
Having the initial moveDown as an instance variable.
Having the repeat forever action as an instance variable (seen above).
Changing the duration property rather than speed.
Any help is appreciated, thanks.

The problem was I was trying to change the speed on the different SKAction objects when I should have been changing the speed on the parent SKSpriteNode.
- (void)startAnimating {
SKAction *moveDown = [SKAction moveByX:0 y:-[CAUtilities screenSize].height duration:3.0];
[self runAction:[SKAction repeatActionForever:moveDown]];
}
- (void)incAnimationSpeedBy:(CGFloat)aFloat {
if (self.speed + aFloat < kMaxSpeed)
self.speed += aFloat;
}

Related

Modify SKActions during the game

I am developing a game where all the enemies are SKNodes in their own classes. Within my SKScene I am spawning the mobs via allocing them and calling a specific method for spawning.
However, when they are spawned each mob are defined with a set of actions they run during their lifetime. One example are for one specific mob is:
SKAction *moveLeft = [SKAction moveToX:0 - (fragment.size.width/2) + (width / 2) duration:1.0];
SKAction *moveRight = [SKAction moveToX:(fragment.size.width / 2) - (width / 2) duration:1.0];
SKAction *sequence = [SKAction sequence:#[moveLeft, moveRight]];
SKAction *bounceOnWalls = [SKAction repeatActionForever:sequence];
[enemy runAction:bounceOnWalls];
So, back to my question. How can I modify a specific SKAction after its been created? I would like to change lets say the speed of moveLeft for all enemies that have this SKAction.
You can run action with key :
SKAction *moveLeft = [SKAction moveToX:0 - (fragment.size.width/2) + (width / 2) duration:1.0];
SKAction *moveRight = [SKAction moveToX:(fragment.size.width / 2) - (width / 2) duration:1.0];
SKAction *sequence = [SKAction sequence:#[moveLeft, moveRight]];
SKAction *bounceOnWalls = [SKAction repeatActionForever:sequence];
[enemy runAction:bounceOnWalls withKey:#"moving"]; //Run action with key
And when you need to change the speed on all nodes which running that action, you can use enumerateChildNodesWithName method. Like this:
[parentNode enumerateChildNodesWithName:name usingBlock:^(SKNode *node, BOOL *stop){
if([node actionForKey:#"moving"]){
SKAction* action = [node actionForKey:#"moving"];
action.speed = 1.5f;
}
}];
You could probably change dynamically duration of actions and affect in that way on speed of moving nodes, but I think that changing speed of an action directly is better choice. Take a look at both answers in this example on how you can change duration parameter dynamically.

Endless Action with SKActions Objective C

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.

EXEC_BAD_ACCESS using blocks

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:

Stop applying force to a sprite

In my spritekit game I am working on applying a wind like force to my game. I have somehow implemented this a number of ways.
The first way I tried is this :
Attempt one
In this code here the sprite is first pushed as it should, however I cant get it too stop.
-(void)update:(NSTimeInterval)currentTime {
NSLog(#" %d",_time);
if (_time == 200 /* | _time==400 */ ) {
[self startPush];
}
-(void)startPush
{
SKAction *startPush = [SKAction runBlock:^{
[_player.physicsBody applyForce:CGVectorMake(50, 0)];
}];
SKAction *wait = [SKAction waitForDuration:1];
SKAction *stopPush = [SKAction runBlock:^{
// [_player.physicsBody applyForce:CGVectorMake(-50, 0)];
[_player.physicsBody applyForce:CGVectorMake(0, 0)];
}];
SKAction *sequence = [SKAction sequence:#[startPush, wait,stopPush]];
[self runAction:sequence];
}
Attempt Two
I attempted to change my code since i saw this post by LearnCocos2d where he said that AKActions are bad for movements.
Constant movement in SpriteKit
So i tried this
Here also the pushing doesnt stop even if pushOn is set to No.
Would love some help in doing the final tweaks in this.
Thank you
-(void)update:(NSTimeInterval)currentTime {
NSLog(#" %d",_time);
if (_time == 200 /* | _time==400 */ ) {
windOn=NO;
}else if (_time==300)
{
pushOn=NO;
}
if (pushOn)
{
{
[_player.physicsBody applyForce:CGVectorMake(0.1, 0)];
}
Applying a zero force is equivalent to applying ... nothing. You're basically leaving the body as is with this code:
[_player.physicsBody applyForce:CGVectorMake(0, 0)];
Instead you probably wanted to zero the velocity vector:
_player.physicsBody.velocity = CGVectorMake(0, 0);
The difference is that applyForce acts as an "add vector" operation, whereas the line above is a "set vector" operation.

Modify the speed of a running SKAction

I have this code:
#implementation MyScene {
SKAction *delayAction;
}
Inside a method:
delayAction = [SKAction waitForDuration:3.0];
[self runAction:[SKAction repeatActionForever: [SKAction sequence:
#[delayAction, [SKAction ...]]]]]
withKey:#"myKey"];
Then i want to decrease duration overtime. (This method is called on update:)
So i tried:
- (void)updateVelocity
{
NSLog(#"duration:%f",delayAction.duration);
delayAction.duration = delayAction.duration - 0.001;
}
And i get:
2014-04-04 11:45:05.781 FlyFish[5409:60b] duration:1.300000
2014-04-04 11:45:05.785 FlyFish[5409:60b] duration:1.299000
2014-04-04 11:45:05.800 FlyFish[5409:60b] duration:1.298000
2014-04-04 11:45:05.816 FlyFish[5409:60b] duration:1.297000
Which seems good, but my [SKAction ...] still continues repeating after 3 seconds.
I'd do this a different way. Something like this...
- (void)recursiveActionMethod
{
if (some end condition is met) {
return;
// this allows you to stop the repeating action.
}
self.duration -= 0.01;
// store duration in a property
SKAction *waitAction = [SKAction waitForDuration:self.duration];
SKAction *theAction = [SKAction doWhatYouWantHere];
SKAction *recursiveAcion = [SKAction performSelector:#selector(recursiveActionMethod) onTarget:self];
SKAction *sequence = [SKAction sequence:#[waitAction, theAction, recursiveAction]];
[self runAction:sequence];
}
This will perform your action and then come back to this function to be run again with a different wait time and again, and again, ...
You can even stop the sequence by having some end condition that would jump inside the if block and stop the loop.
I am a bit late, but you can instead set the action to SKActionTimingEaseOut. Also this is language native and should work slightly faster. (Though you cannot customize the speed changes)
This can be done similarly to this:
yourSKAction.timingMode = SKActionTimingEaseOut;

Resources