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];
}
Related
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.
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'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 recently came across an issue where I would increase the size of a node, but the physics body would remain the same size. I tried to look up solutions to this with no success. How can I make the body scale with the size of the node?
CGPoint location = CGPointMake(randX, -self.frame.size.height - expander.size.height);
SKAction *moveAction = [SKAction moveTo:location duration:randDuration];
SKAction *expandAction = [SKAction resizeToWidth:(expander.size.width * 1.4) height:(expander.size.width * 1.4) duration:1.0];
SKAction *collapseAction = [SKAction resizeToWidth:(expander.size.width) height: (expander.size.height) duration:1.0];
SKAction *doneAction = [SKAction runBlock:(dispatch_block_t)^() {
expander.hidden = YES;
}];
SKAction *expandCollapseAction = [SKAction repeatActionForever:[SKAction sequence:#[expandAction, collapseAction]]];
SKAction *moveExpandAction = [SKAction group:#[moveAction, expandCollapseAction]];
SKAction *moveExpanderActionWithDone = [SKAction sequence: #[moveExpandAction, doneAction ]];
[expander runAction:moveExpanderActionWithDone withKey: #"expanderMoving"];
I thought this won't work, but it looks like that physics body is scaled after all. Here is the code which can reproduce physics body scaling on iOS8 :
- (void)didMoveToView:(SKView *)view {
/* Setup your scene here */
SKSpriteNode *s = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:CGSizeMake(100,100)];
s.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame)+100);
s.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:s.size];
s.physicsBody.dynamic = YES;
s.physicsBody.affectedByGravity = NO;
[self addChild:s];
[s runAction:[SKAction scaleTo:0.3f duration:5]];
[s.physicsBody applyImpulse:CGVectorMake(0,30)];
}
If I recall correctly , this wasn't worked earlier, but I just tried it and it looks like it work. Still, this is not fully tested and I am not sure if this will mess up the physics simulation. Contact detection will probably work though. I just wanted to show that actual physics bodies are scaled when sprite is scaled . Here is the result:
From the gif above it can be seen that physics body is scaled along with sprite (without re-creating another different sized physics body). The blue bounding box around the sprite is visual representation of sprite's physics body (enabled in view controller with skView.showsPhysics = YES);
this is strange you can do it add physics body before adding it to the SKNode or SKScene for e.g.
SKTexture *texture=[_gameTexture textureNamed:#"mysprite"];
SKSpriteNode *bubble=[SKSpriteNode spriteNodeWithTexture:texture];
bubble.anchorPoint=CGPointMake(0.5,0.5);
//_bubble.name=[#"bubble" stringByAppendingFormat:#"%d ",index];
bubble.name=newcolor;
// _bubble.userInteractionEnabled=YES;
_bubble.position=CGPointMake(newPosition.x, newPosition.y);
//
bubble.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:60];
bubble.physicsBody.dynamic=NO;
bubble.physicsBody.linearDamping=0;
bubble.physicsBody.restitution=0;
bubble.physicsBody.density=1;
bubble.physicsBody.allowsRotation=NO;
bubble.physicsBody.affectedByGravity=NO;
//_bubble.physicsBody.velocity=CGVectorMake([self getRandomNumberBetween:-10 to:7], [self getRandomNumberBetween:-10 to:7]);
// _bubble.physicsBody.usesPreciseCollisionDetection=YES;
bubble.physicsBody.categoryBitMask=ballCategory;
bubble.physicsBody.collisionBitMask=ballCategory | pathCategory ;
bubble.physicsBody.contactTestBitMask=ballCategory | pathCategory ;
[self addChild:_bubble];
bubble.xScale=_bubble.yScale=2;
bubble.alpha=0.5;
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];