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.
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.
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 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;
I have a runAction which is animating a SKSpriteNode. I have the node moving up and down in a repeatActionForever. I would like the node to slow down as the node is moving up and speed up as the node is moving down.
[node runAction:[SKAction repeatActionForever:
[SKAction sequence:#
[[SKAction speedTo:0.1 duration:0.5],
[SKAction moveToY:2 * node.size.height / 3 duration:0.5],
[SKAction speedTo:1 duration:0.5],
[SKAction moveToY:node.size.height / 2 duration:0.5],
[SKAction moveToY:node.size.height duration:1],
[SKAction moveToY:node.size.height / 2 duration:1]]]]];
When I add the line [SKAction speedTo:0 duration:0.5], the rest of the code is run at speed of 0 after the 0.5 seconds even though I added a second speedTo action which would increase the speed to 1.
The problem: How do I change the speed of a node as the node is moving rather than having a stagnant speed for each direction.
Thanks in advance.
Look up the various types of SKActionTimingMode and apply the ones as needed to your situation. This will remove the need for anything like [SKAction speedTo:0.1 duration:0.5].
https://developer.apple.com/library/mac/documentation/SpriteKit/Reference/SKAction_Ref/Reference/Reference.html#//apple_ref/c/tdef/SKActionTimingMode
You can use the SKActionTimingEaseOut for the action that makes the node move up and SKActionTimingEaseIn for the action that makes the node move down.
SKAction *actionMoveUp = [SKAction moveToY:2 * node.size.height / 3 duration:0.5];
actionMoveUp.timingMode = SKActionTimingEaseOut;
SKAction *actionMoveDown = [SKAction moveToY:node.size.height / 2 duration:0.5];
actionMoveDown.timingMode = SKActionTimingEaseIn;
SKAction *actionMoveUpHalf = [SKAction moveToY:node.size.height duration:1];
actionMoveUp.timingMode = SKActionTimingEaseOut;
[node runAction:[SKAction repeatActionForever:
[SKAction sequence:#
[actionMoveUp,
actionMoveDown,
actionMoveUpHalf,
actionMoveDown]]]];
I have a trouble trying to make one circle big and small using [SKAction scaleBy: duration:]
SKAction *scaleDown = [SKAction scaleBy:0.2 duration:1.8];
SKAction *scaleUp= [scaleDown reversedAction];
SKAction *fullScale = [SKAction sequence:#[scaleDown, scaleUp, scaleDown, scaleUp]];
[_circleChanging runAction:fullScale];
What I get is the circle becoming so small that disappears and then doesn't come back. It has to become small and then come back to his original size doing it 2 times.
Try:
SKAction *scaleDown = [SKAction scaleTo:0.2 duration:0.75];
SKAction *scaleUp= [SKAction scaleTo:1.0 duration:0.75];
SKAction *fullScale = [SKAction repeatActionForever:[SKAction sequence:#[scaleDown, scaleUp, scaleDown, scaleUp]]];
[_circleChanging runAction:fullScale];
Not all actions are reversible, and the reverse sometimes doesn't mean "go back to the original value".
If you check the documentation, the reverse action of scaleBy is actually scaling to -0.2 in your case. Just create a new scale action instead of reversing.
Also try making a copy of the actions for the 2nd use:
SKAction *fullScale = [SKAction sequence:
#[scaleDown, scaleUp, [scaleDown copy], [scaleUp copy]]];