I want to run two animations on my spriteNode depending on its rotation. If the value is negative run one of the animations, if it's positive run the other. And I managed to do that (kind of) but I have a problem. If Animation1 is running, and zRotation changes to positive, they both run because they are repeating forever. So I did this :
NSMutableArray *walkingTextures = [NSMutableArray arrayWithCapacity:14];
for (int i = 1; i < 15; i++) {
NSString *textureName =
[NSString stringWithFormat:#"character%d", i];
SKTexture *texture =
[SKTexture textureWithImageNamed:textureName];
[walkingTextures addObject:texture];
}
SKAction *spriteAnimation = [SKAction animateWithTextures:Textures timePerFrame:0.04];
repeatWalkAnimation = [SKAction repeatActionForever:spriteAnimation];
[sprite runAction:repeatWalkAnimation withKey:#"animation1"];
and then when I want it to stop :
[self removeActionForKey:#"animation1"];
but it keeps running the action, how can I stop the action, then? Thank you!
The method is supposed to be called on the node which the SKAction is running on.
Change
[self removeActionForKey:#"animation1"];
to
[sprite removeActionForKey:#"animation1"];
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 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:
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];
I have a problem in which my Sprite Kit game lags only when the first collision occurs between the main character and any other sprite. After the first collision occurs, every other collision is smooth and runs at 60.0 fps. The odd thing is that when the first collision, the fps only drops to 49-51, but the actual game freezes for a half second. This is also not an issue of setup lag as this occurs no matter how long I wait to start. Does anyone know what the issue is?
-(void)checkForCollisions {
if (_invincible) return;
[self enumerateChildNodesWithName:#"enemy"
usingBlock:^(SKNode *node, BOOL *stop){
SKSpriteNode *enemy = (SKSpriteNode *)node;
CGRect smallerFrame = CGRectInset(enemy.frame, 22, 22);
if (CGRectIntersectsRect(smallerFrame, _sloth.frame)) {
[enemy removeFromParent];
[self runAction:[SKAction playSoundFileNamed:#"eagle.wav" waitForCompletion:NO]];
NSString *burstPath =
[[NSBundle mainBundle] pathForResource:#"ExplosionParticle" ofType:#"sks"];
SKEmitterNode *burstEmitter =
[NSKeyedUnarchiver unarchiveObjectWithFile:burstPath];
burstEmitter.position = _sloth.position;
[self addChild:burstEmitter];
[self changeInLives:-1];
_invincible = YES;
float blinkTimes = 10;
float blinkDuration = 3.0;
SKAction *blinkAction =
[SKAction customActionWithDuration:blinkDuration
actionBlock:
^(SKNode *node, CGFloat elapsedTime) {
float slice = blinkDuration / blinkTimes;
float remainder = fmodf(elapsedTime, slice);
node.hidden = remainder > slice / 2;
}];
SKAction *sequence = [SKAction sequence:#[blinkAction, [SKAction runBlock:^{
_sloth.hidden = NO;
_invincible = NO;
}]]];
[_sloth runAction:sequence];
}
}];
}
The lag is not linked to the emitter node as the game still lags whenever it is commented out.
Let me know if you need any additional information. Thanks in advance!
Here is a link for my Instruments's trace file: https://www.dropbox.com/sh/xvd1xdti37d76au/ySL4UaHuOS
If you look at the trace file, note that the collision occurs when the Time Profiler reaches 118%.
As Cocos mentioned, it is probably the initial loading of your sound file which is causing the one time delay.
In your implementation add the sound action:
SKAction *eagleSound;
In your init method add this:
eagleSound = [SKAction playSoundFileNamed:#"eagle.wav" waitForCompletion:NO];
Then whenever you need to play the sound, use this:
[self runAction:eagleSound];
I'm currently using Sprite Kit and Xcode to design a game. My character is usually in the state of running which consists of two images - code below:
bobSKTexture* Texture1 = [SKTexture textureWithImageNamed:#"bob1"];
bobTexture1.filteringMode = SKTextureFilteringNearest;
SKTexture* bobTexture2 = [SKTexture textureWithImageNamed:#"bob2"];
bobTexture2.filteringMode = SKTextureFilteringNearest;
SKAction* run = [SKAction repeatActionForever:[SKAction animateWithTextures:#[birdTexture1, birdTexture2] timePerFrame:0.2]];
_bob = [SKSpriteNode spriteNodeWithTexture:birdTexture1];
[_bob setScale:2.0];
_bob.position = CGPointMake(self.frame.size.width / 4, CGRectGetMidY(self.frame));
[_bob runAction:run];
I want the image to change when a touch to the screen happens, only for a short amount of time and then I want it to return to the above code. How can I accomplish this?
As Jānis K said, it would be even easier to do like this when you want the image to change:
[_bob removeAllActions];
_bob.texture = [SKTexture textureWithImageNamed:NEW_TEXTURE];
[self performSelector:#selector(resetAnimation) withObject:nil afterDelay:2.0f];
NEW_TEXTURE is the name of the texture/image you want it to change to.
self would be the scene or wherever you call the code to make _bob
This is the resetAnimation method, defined in your scene or wherever you call the code to make _bob:
- (void)resetAnimation
{
SKAction* run = [SKAction repeatActionForever:[SKAction animateWithTextures:#[birdTexture1, birdTexture2] timePerFrame:0.2]];
[_bob runAction:run];
}