SpriteKit crash on run action on other SKScene - ios

I have a crash in interesting form. I setUp SKAction like
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor colorWithWhite:255 alpha:1];
[self createNinja];
[self setUpJump];
}
return self;
}
- (void)setUpJump
{
SKTextureAtlas *jumpAtlas = [SKTextureAtlas atlasNamed:#"Ninja_jump"];
SKTexture *jump1 = [jumpAtlas textureNamed:#"Ninja_jump_1"];
SKTexture *jump2 = [jumpAtlas textureNamed:#"Ninja_jump_2"];
SKTexture *jump3 = [jumpAtlas textureNamed:#"Ninja_jump_3"];
SKTexture *jump4 = [jumpAtlas textureNamed:#"Ninja_jump_4"];
SKAction *jumpUpAnimation = [SKAction animateWithTextures:#[jump1, jump2, jump3, jump4]
timePerFrame:0.07];
SKAction *jumpDownAnimation = [SKAction animateWithTextures:#[jump3, jump2, jump1, [SKTexture textureWithImageNamed:#"Ninja"]]
timePerFrame:0.07];
SKAction *wait = [SKAction waitForDuration:0.3];
self.jumpAction = [SKAction sequence:#[jumpUpAnimation, wait, jumpDownAnimation]];
}
But when I don't run this action on first SKScene and go to other SKScene, when I set up the same action, I have a crash
But if I run this action on first SKScene, everything is OK on next SKScene.
Is there problem in SpriteKit in SKTexture?
Run my action like that
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
CGPoint location = [touch locationInNode:self];
SKSpriteNode *ninja = (SKSpriteNode *)[self childNodeWithName:#"ninja"];
if (location.x > 230 &&
(location.x < 419 && location.y > 500))
{
[ninja runAction:self.jumpAction];
}
}
}

Not 100% sure what you are saying but if I read you right, you are setting up an SKAction property jumpAction in the first scene and are referring to that action in the other scene's touchesBegan: method(?):
[ninja runAction:self.jumpAction];
If you are not setting up the self.jumpAction for your second scene, then you are essentially saying [ninja runAction:nil];
I personally find it a good idea to tie actions as close as possible to the node that is supposed to perform it (in this case the sprite). If you are reusing a lot of actions for different nodes creating an ActionFactory class could be another way to go about it...

Related

Can't update spriteNode location in spritekit?

Basically when the user taps the screen they can add a sprite to the screen, which is contained within a wrapper class called Planet that contains a couple more variables. I stored all the planets in an NSMutableArray, so when the screen is tapped a new planet is created and I add that planet's spriteNode to the view as a child. I want to be able to update values such as the sprite's size and position, but the issue is that I can't seem to be able to do that by accessing the sprite from the planet class in the planets NSMutableArray
basically when the screen is tapped, a planet is added, and until the tap ends, the "update mass" method is called, increasing the size of the sprite a little bit.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
mass = 10;
massTimer = [NSTimer scheduledTimerWithTimeInterval:.2 target:self selector:#selector(increaseMass) userInfo:nil repeats:YES];
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
Planet *planet = [[Planet alloc] init];
[planet planetWithImage:#"Earth"];
planet.mass = mass;
planet.spriteNode.xScale = planet.spriteNode.yScale = planet.mass * .004;
planet.spriteNode.position = location;
SKAction *action = [SKAction rotateByAngle:M_PI_2 duration:5];
[planet.spriteNode runAction:[SKAction repeatActionForever:action]];
[self addChild:planet.spriteNode];
[planets addObject:planet];
touchBeginX = location.x;
touchBeginY = location.y;
break; //only get one :p
}
}
-(void)increaseMass {
NSLog(#"increase mass");
((Planet *) [planets objectAtIndex:([planets count] - 1)]).mass++;
((Planet *) [planets objectAtIndex:([planets count] - 1)]).spriteNode.xScale = ((Planet *) [planets objectAtIndex:([planets count] - 1)]).spriteNode.yScale = .002 * ((Planet *) [planets objectAtIndex:([planets count] - 1)]).mass;
}
The planet with image class just create a new planet class with a couple variables and a spriteNode object within it that uses the image called, in this case, #"Planet".
I would do this differently. Have you considered saving the currentPlanet in an instance variable, running an action on that, and cancelling that action in touchesEnded:withEvent:?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
mass = 10;
// grab only the first touch
self.currentTouch = touches.firstObject;
CGPoint location = [self.currentTouch locationInNode:self];
self.currentPlanet = [[Planet alloc] init];
[self.currentPlanet planetWithImage:#"Earth"];
self.currentPlanet.mass = mass;
self.currentPlanet.spriteNode.xScale = self.currentPlanet.spriteNode.yScale = self.currentPlanet.mass * .004;
self.currentPlanet.spriteNode.position = location;
SKAction *spinAction = [SKAction rotateByAngle:M_PI_2 duration:5];
[self.currentPlanet.spriteNode runAction:[SKAction repeatActionForever:spinAction]];
SKAction *growAction = [SKAction sequence:#[[SKAction waitForDuration:0.2], [SKAction performSelector:#selector(grow) onTarget:self.currentPlanet]];
[self.currentPlanet.spriteNode runAction:[SKAction repeatActionForever:growAction] withKey:#"growAction"];
[self addChild:self.currentPlanet.spriteNode];
[planets addObject:self.currentPlanet];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches containsObject:self.currentTouch]) {
[self.currentPlanet removeActionForKey:#"growAction"];
}
}
Then in your Planet class:
#implementation Planet ()
- (void)grow
{
self.mass++;
self.spriteNode.xScale = self.spriteNode.yScale = 0.002 * self.mass;
}
#end
This way you are accessing the sprite you care about directly, rather than trying to find it in the array of planets. If this is your only use for that planets array, you could get rid of it if you do it this way.

How to move sprite from right to left of screen using CGPathAddArc

my sprite position is (screenWidth, screenHeigh/2). i have tried a lot of code which using CGPathAddArc to move sprite from right to left but not success. apple document is so hard to understand. so i post here with a hope someone can help me.
this is one of the code i had used.
if (iPad) {
CGPathAddArc(thePath, NULL, 384.0f , 768, 384.0f , 0.f, -M_PI*3/2, NO);
}
SKAction *moveAction = [SKAction followPath:thePath asOffset:YES orientToPath:NO duration:(5.0f/200.0f)*(iPad ? 384.0f :200.f)];
many thank in advance
If you create a blank SpriteKit project, this slightly modified default code for GameScene.m will make the spaceship fly through from right to left following a half-circle path.
Pls comment if you need further clarification!
Key difference from your code snippet that for [SKAction followPath...] the asOffset parameter is NO, and the orientToPath is YES.
#import "GameScene.h"
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 65;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:myLabel];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
sprite.position = location;
// HERE COMES THE MODIFICATION
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathAddArc(thePath, NULL, self.frame.size.width/2 , 200, self.frame.size.width/2 , 0.f, -M_PI, NO);
SKAction *moveAction = [SKAction followPath:thePath asOffset:NO orientToPath:YES duration:5.0];
[sprite runAction:[SKAction repeatActionForever:moveAction]];
// HERE ENDS THE MODIFICATION
[self addChild:sprite];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
Hope this helps!

Sprite kit - Dealing with overlapping animations in fast sequence

I'm building an app in which when a sprite is pressed it performs a very simple animation in which the initial image is replaced with a new image and then after a short pause the initial image is applied again. There are 2 of such sprites.
It works all fine unless the buttons are pressed in an extremely rapid sequence.
Let's say for example that the user presses button 1 and then very quickly afterwards button 2 (before this had the time to revert back to the initial image): in this case button 1 remains stuck with the new image!
Even if I disable the interaction with the sprite (by for example renaming it while the animation is in progress or inserting different flags), still a touchesBegan event is registered and I believe this is enough to get sprite 1 stuck with the new image and not reverting back to the initial one!
I've ran out of ideas... Any suggestions please? Cheers.
- (void)SwitchButtonImage
{
touchedNode.texture = [SKTexture textureWithImageNamed:ImageNew];
//After a pause change sprite image to the initial image
SKAction *wait = [SKAction waitForDuration: 0.3];
[touchedNode runAction: wait completion:^
{
touchedNode.texture = [SKTexture textureWithImageNamed:ImageInitial];
}];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self selectNodeForTouch:positionInScene];
}
- (void)selectNodeForTouch:(CGPoint)touchLocation
{
touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
_selectedNode = touchedNode;
if([[touchedNode name] isEqualToString:kButtonNodeName_01])
{[self SwitchButtonImage];}
else if([[touchedNode name] isEqualToString:kButtonNodeName_02])
{[self SwitchButtonImage];}
}
EDIT: I edited my answer to handle more than one button and to work with your original code.
- (void)SwitchButtonImage
{
touchedNode.texture = [SKTexture textureWithImageNamed:#"NewImage"];
//After a pause change sprite image to the initial image
SKAction *wait = [SKAction waitForDuration: 0.3];
SKTexture *texture = [SKTexture textureWithImageNamed:#"InitialImage"];
SKAction *resetImage = [SKAction setTexture:texture];
// Add action with a unique identifier
[touchedNode runAction:[SKAction sequence:#[wait, resetImage]] withKey:#"ResetImage"];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self selectNodeForTouch:positionInScene];
}
- (void)selectNodeForTouch:(CGPoint)touchLocation {
touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
_selectedNode = touchedNode;
if([[touchedNode name] isEqualToString:kButtonNodeName_01]) {
// Change image only if previous action is done
if (![touchedNode actionForKey:#"ResetImage"]) {
[self SwitchButtonImage];
}
}
else if([[touchedNode name] isEqualToString:kButtonNodeName_02]) {
// Change image only if previous action is done
if (![touchedNode actionForKey:#"ResetImage"]) {
[self SwitchButtonImage];
}
}
}
EDIT 2: You can add a key to an SKAction that has a completion block, but you can add a runBlock to the end of an SKAction sequence to do that same. Here's an example:
SKAction *action1 = [SKAction rotateByAngle:M_PI duration:1];
SKAction *action2 = [SKAction rotateByAngle:-M_PI duration:1];
SKAction *completed = [SKAction runBlock:^{
NSLog(#"I am done");
}];
SKAction *action = [SKAction sequence:#[action1, action2, completed]];
[sprite runAction:action withKey:#"actionKey"];
You can do this by using blocks and a BOOL ivar.
Create an ivar:
BOOL readyForNextAnimation;
Set up your blocks something like this:
SKAction *block0 = [SKAction runBlock:^{
// your animation code here
}];
SKAction *wait0 = [SKAction waitForDuration:0.3]; // your animation time length
SKAction *block1 = [SKAction runBlock:^{
readyForNextAnimation = true;
}];
[self runAction:[SKAction sequence:#[block0, wait0, block1]]];
The above code would then be used something like this:
// some trigger just occurred
if(readyForNextAnimation == true)
{
readyForNextAnimation = false;
// create blocks 0,1,2 and run them as above
}
If you need to do other things, just create more blocks and add them into the run sequence.

stop Impulse on sprite touch [duplicate]

This question already has an answer here:
stop impulse on SKSpriteKit click
(1 answer)
Closed 8 years ago.
i'm creating an game where there is a pause button. For checking wether its touched i'm checking the location of the touch and if its equal to the paused button name.
When the pausedButton is clicked its call a method which pause the scene.
The problem is that in the touchBegan method whenever you touch the screen it apply an impulse, so when i press the pauseButton and unpause it the applyforce will come after. This is not ideal for the game. I've tried with a bool like shouldImpulse, but havent got it to work. here is my touchedBegan method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"home"]) {
SKTransition *reveal = [SKTransition fadeWithDuration:2.0 ];
Menu *newScene = [[Menu alloc] initWithSize: CGSizeMake(self.size.width,self.size.height)];
// Optionally, insert code to configure the new scene.
[self.scene.view presentScene: newScene transition: reveal];
}
if ([node.name isEqualToString:#"pause"]) {
[self pausedMenu];
}
if ([node.name isEqualToString:#"start"]) {
[self startMenu];
}
showpipes = showpipes + 1;
if (showpipes == 1) {
self.physicsWorld.gravity = CGVectorMake( 0.0, -5.0 );
SKAction* spawn = [SKAction performSelector:#selector(spawnPipes) onTarget:self];
SKAction* delay = [SKAction waitForDuration:2.0];
SKAction* spawnThenDelay = [SKAction sequence:#[spawn, delay]];
SKAction* spawnThenDelayForever = [SKAction repeatActionForever:spawnThenDelay];
[self runAction:spawnThenDelayForever];
}
started = 1;
if (started == 1) {
mover.physicsBody.restitution = 0.0;
mover.physicsBody.velocity = CGVectorMake(0, 0);
[mover.physicsBody applyImpulse:CGVectorMake(0, 15)];
}
}
You can do that with a simple IF, ELSE IF, ELSE:
if([node.name isEqualToString:#"home"])
{
// do stuff...
} else if ([node.name isEqualToString:#"pause"])
{
// do stuff...
} else if ([node.name isEqualToString:#"start"])
{
// do stuff...
} else
{
// do whatever else here...
}

SpriteKit Clicker Game Select Spawn Area for Clickable things?

I'm New to SpriteKit and build a simple CLickerGame in SpriteKit. Now i have the game finish but the thing which should spawn to click on it, spawns from time to time outside the screen. Where can i select the arc4random area where the thing should spawn?
#implementation MyScene
-(void)CreateTestObject
{
SKSpriteNode *TestObject = [SKSpriteNode spriteNodeWithImageNamed:#"TestObject"];
int maxX = CGRectGetMaxX(self.frame);
float ranX = (arc4random()%maxX) + 1;
int maxY = CGRectGetMaxY(self.frame);
float ranY = (arc4random()%maxY) + 1;
TestObject.position = CGPointMake(ranX, ranY);
TestObject = CGSizeMake(75, 75);
TestObject = #"Object";
[self addChild:TestObject];
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg.png"];
bg.position = CGPointMake(160, 284);
bg.size = CGSizeMake(self.frame.size.width, self.frame.size.height);
[self addChild:bg];
[self CreateTestObject];
}
return self;
}
-(void)selectNodeForTouch:(CGPoint)touchLocation
{
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
if([[touchedNode name] isEqualToString:#"TestObject"])
{
SKSpriteNode *TestObject = (SKSpriteNode *)[self childNodeWithName:#"TestObject"];
TestObject.name = #"DisabledTestObject";
SKAction *grow = [SKAction scaleTo:1.2 duration:0.1];
SKAction*shrink = [SKAction scaleTo:0 duration:0.07];
SKAction *removeNode = [SKAction removeFromParent];
SKAction *seq = [SKAction sequence:#[grow, shrink, removeNode]];
[TestObject runAction:seq];
[self CreateTestObject];
[self runAction:[SKAction playSoundFileNamed:#"Sound.aif" waitForCompletion:NO]];
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
//CGPoint location = [touch locationInNode:self];
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self selectNodeForTouch:positionInScene];
}
}
#end
Modify the max values for your (x,y) coordinates of any object being added to your view.
For example, on an iPhone 5 display in landscape mode and the spawned object being of size width=100, height=100, the max x value should be no more than 518. Width of screen being 568 less half the width of your object (50).
The same logic applies for the y value. The max y value should be 270 given the same object dimensions.
The above assumes that you have not changed your object's anchor point from (0.5,0.5) to another value.

Resources