SpriteKit SKSpriteNode slide - objective-c

I'm trying determine why slide is happened. I have created 2 nodes, and one of them is moving using SKAction. But when one of them above on other, it does not moving with node under them.
It's a little bit hard to explain, so I have created the video:
https://www.youtube.com/watch?v=F4OuTBVd5sM&feature=youtube_gdata_player
Thanks for your replies!
Parts of code:
CGPoint positionPoint = [valuePoint CGPointValue];
SKSpriteNode *slab = [SKSpriteNode spriteNodeWithTexture:plitaTexture];
slab.name = #"plita";
slab.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:plitaTexture.size];
slab.position = positionPoint;
slab.physicsBody.dynamic = NO;
CGFloat duration = 3.0;
CGFloat firstDuration = duration/(self.size.width/slab.position.x);
SKAction *moveLeftFirstTime = [SKAction moveTo:CGPointMake(0.0 + plitaTexture.size.width/2, positionPoint.y) duration:firstDuration];
SKAction *moveLeft = [SKAction moveTo:CGPointMake(0.0 + plitaTexture.size.width/2, positionPoint.y) duration:5];
SKAction *moveRight = [SKAction moveTo:CGPointMake(self.size.width - plitaTexture.size.width/2, positionPoint.y) duration:5];
SKAction *movingRightLeft = [SKAction sequence:#[moveRight, moveLeft]];
SKAction *moving = [SKAction sequence:#[moveLeftFirstTime,[SKAction repeatActionForever:movingRightLeft]]];
[slab runAction:moving];
[self addChild:slab];
Rabbit init code:
-(instancetype)init {
if (self = [super initWithImageNamed:#"eating-rabbit1"]) {
self.physicsBody = [SKPhysicsBody bodyWithTexture:self.texture size:self.texture.size];
self.physicsBody.allowsRotation = NO;
self.physicsBody.dynamic = YES;
self.physicsBody.friction = 1;
}
return self;
}

The main character is sliding because you are moving the slab by changing its position over time. Imagine you are standing on top of a train that is moving, friction will cause you to move along in the direction of the train. However, if the train magically moved from point A to point B by disappearing and reappearing 1 cm at a time, you will remain at point A. That is what's happening to your main character.
To correct this issue, you will need to move the slabs using physics. Here is an example of how to do that:
slab.physicsBody.affectedByGravity = NO;
slab.physicsBody.dynamic = YES;
// These will help prevent the slab from twisting due to the weight of the main character
slab.physicsBody.angularDamping = 1.0;
slab.physicsBody.mass = 1e6;
// Air resistance will slow slab's movement, set the damping to 0
slab.physicsBody.linearDamping = 0;
Define a set of SKActions that will move the slabs left and right. Adjust the speed and duration variables to achieve the desired motion.
SKAction *moveLeft = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
node.physicsBody.velocity = CGVectorMake(-speed, 0);
}];
SKAction *moveRight = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
node.physicsBody.velocity = CGVectorMake(speed, 0);
}];
SKAction *wait = [SKAction waitForDuration:duration];
SKAction *action = [SKAction sequence:#[moveRight, wait, moveLeft, wait]];
[slab runAction:[SKAction repeatActionForever:action]];

You need to set the slab's physicsBody to be dynamic.
slab.physicsBody.dynamic = YES;
slab.physicsBody.affectedByGravity = NO;
A non-dynamic physicsBody is rendered static and ignores any forces and impulses applied on it. Look up the property in the documentation here.

Related

Label not changing countdown timer SpriteKit Objective C?

Edit no.2 , Ok, I think I have boiled this right down to the point now. I have used all your advice, and tested with breakpoints, so thank you.
The last bit I need to do, is run this wait action.
if (timerStarted == YES) {
[countDown runAction:[SKAction waitForDuration:1]];
if (countDownInt > 0) {
countDown.text = [NSString stringWithFormat:#"%i", countDownInt];
countDownInt = countDownInt - 1.0;
[self Timer];
}else{
countDown.text = [NSString stringWithFormat:#"Time Up!"];
}
The runAction: section doesn't seem to work. I am guessing this is because I have selected the wrong node to put in the place of the (SKLabelNode "countDown"). Which node could I use to run this code?
Thank you everyone who has helped so far
Here's an example of how to implement a countdown timer in SpriteKit.
First, declare a method that creates 1) a label node to display the time left and 2) the appropriate actions to update the label, wait for one second, and rinse/lather/repeat
- (void) createTimerWithDuration:(NSInteger)seconds position:(CGPoint)position andSize:(CGFloat)size {
// Allocate/initialize the label node
countDown = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
countDown.position = position;
countDown.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
countDown.fontSize = size;
[self addChild: countDown];
// Initialize the countdown variable
countDownInt = seconds;
// Define the actions
SKAction *updateLabel = [SKAction runBlock:^{
countDown.text = [NSString stringWithFormat:#"Time Left: %ld", countDownInt];
--countDownInt;
}];
SKAction *wait = [SKAction waitForDuration:1.0];
// Create a combined action
SKAction *updateLabelAndWait = [SKAction sequence:#[updateLabel, wait]];
// Run action "seconds" number of times and then set the label to indicate
// the countdown has ended
[self runAction:[SKAction repeatAction:updateLabelAndWait count:seconds] completion:^{
countDown.text = #"Time's Up";
}];
}
and then call the method with the duration (in seconds) and the label's position/size.
CGPoint location = CGPointMake (CGRectGetMidX(self.view.frame),CGRectGetMidY(self.view.frame));
[self createTimerWithDuration:20 position:location andSize:24.0];
I would not use the update method. Use SKActions to make a timer. For an example
id wait = [SKAction waitForDuration:1];
id run = [SKAction runBlock:^{
// After a second this is called
}];
[node runAction:[SKAction sequence:#[wait, run]]];
Even though this will only run once, you can always embed this in an SKActionRepeatForever if you want to be called every second or whatever time interval.
Source:
SpriteKit - Creating a timer

SpriteKit texture change according to sprite x-axis direction

This seems like a simple problem to solve, yet I can't. As the title suggests, I need my sprite to change to a different texture depending on which way it's going - left or right. Here's what I tried doing:
if (self.sprite.position.x > 0) //also tried "self.sprite.position.x" instead of 0.
{
[self.sprite setTexture:[SKTexture textureWithImageNamed:#"X"]];
}
if (self.sprite.position.x < 0)
{
[self.sprite setTexture:[SKTexture textureWithImageNamed:#"XX"]];
}
Neither worked (Except when the sprite's x-axis == 0 -_-).
I've read this: SpriteKit: Change texture based on direction
It didn't help.
EDIT:
Pardon, I forgot to mention the movement of the sprite is completely random in 2D space. I use arc4random() for that. I don't need any texture changes when it's going up or down. But left & right are essential.
I must be writing something wrong:
if (self.sprite.physicsBody.velocity.dx > 0)
if (self.sprite.physicsBody.velocity.dx < 0)
isn't working for me. Neither is
if (self.sprite.physicsBody.velocity.dx > 5)
if (self.sprite.physicsBody.velocity.dx < -5)
Just to be clear about this sprite's movement one more time: it is randomly moving with the help of the
-(void)update:(CFTimeInterval)currentTime
method. This is achieved like so:
-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
if (!spritehMoving)
{
CGFloat fX = [self getRandomNumberBetweenMin:-5 andMax:5];
CGFloat fY = [self getRandomNumberBetweenMin:-5 andMax:7];
int waitDuration = arc4random() %3;
SKAction *moveXTo = [SKAction moveBy:CGVectorMake(fX, fY) duration:1.0];
SKAction *wait = [SKAction waitForDuration:waitDuration];
SKAction *sequence = [SKAction sequence:#[moveXTo, wait]];
[self.sprite runAction:sequence];
}
}
You could just check to see if fX is greater or lower than 0, meaning the 'force' on the x-axis is either positive (right movement) or negative (left movement).
Here is a modified code that should do the trick-
BTW- I've also set the SpritehMoving BOOL at the start and at the end of the movement, otherwise, this method will be called even if the sprite is moving (which there is nothing wrong with it, but it seems to beat the purpose of checking the BOOL value.
if (!spritehMoving)
{
spritehMoving = YES;
CGFloat fX = [self getRandomNumberBetweenMin:-5 andMax:5];
CGFloat fY = [self getRandomNumberBetweenMin:-5 andMax:7];
int waitDuration = arc4random() %3;
SKAction *changeTexture;
if(fX > 0) { // Sprite will move to the right
changeTexture = [SKAction setTexture:[SKTexture textureWithImageNamed:#"RightTexture.png"]];
} else if (fX < 0) { // Sprite will move to the left
changeTexture = [SKAction setTexture:[SKTexture textureWithImageNamed:#"LeftTexture.png"]];
} else {
// Here I'm creating an action that doesn't do anything, so in case
// fX == 0 the texture won't change direction, and the app won't crash
// because of nil action
changeTexture = [SKAction runBlock:(dispatch_block_t)^(){}];
}
SKAction *moveXTo = [SKAction moveBy:CGVectorMake(fX, fY) duration:1.0];
SKAction *wait = [SKAction waitForDuration:waitDuration];
SKAction *completion = [SKAction runBlock:(dispatch_block_t)^(){ SpritehMoving = NO; }];
SKAction *sequence = [SKAction sequence:#[changeTexture, moveXTo, wait, completion]];
[self.sprite runAction:sequence];
}

Spritekit how to run a method for a certain amount of time, then start another one

I'm making a game and i'm currently stumped on how to have a method run for a certain amount of time, turn off, then to have another method start running. Currently i have:
[self spawn];
when the scene sets up. Here is the spawn method:
-(void)spawn {
int xMin = 0;
int xMax = 460;
CGPoint startPoint = CGPointMake(xMin + arc4random_uniform(xMax - xMin),320);
[self performSelector:#selector(spawn) withObject:nil afterDelay:.20];
SKSpriteNode *blue = [SKSpriteNode spriteNodeWithImageNamed:#"whitecircle"];
blue.position = CGPointMake(startPoint.x,startPoint.y);
blue.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:21.5];
blue.physicsBody.usesPreciseCollisionDetection = NO;
blue.physicsBody.categoryBitMask = gainCategory;
blue.physicsBody.contactTestBitMask = playerCategory;
blue.physicsBody.dynamic = NO;
[self addChild:blue];
SKAction *runBlue = [SKAction moveToY:0 duration:1.5];
SKAction *remove = [SKAction removeFromParent];
[blue runAction:[SKAction sequence:#[runBlue,remove]]];
}
What i want to know is how can i make this method run for a certain amount of time, then how can i start another method after this one finishes. Thanks
I think this is what you are looking to do :
// set duration to seconds you want to wait between spawns
float duration = 1;
SKAction *actionWait = [SKAction waitForDuration:duration];
SKAction *actionSpawn = [SKAction runBlock:^(void)
{
[self spawn];
}];
SKAction *actionSequence = [SKAction sequence:#[actionWait, actionSpawn]];
SKAction *actionRepeat = [SKAction repeatActionForever:actionSequence];
[self runAction:actionRepeat];
It's worthwhile to give the Class Reference for SKAction a good reading at least once, so you know it's capabilities.
SKAction Class Reference

Child Sprite appearing under the parent

I am simply trying to add a sprite "light" on top of "spaceship". So as you can see below that I added light as the child of the spaceship, however, the light is appearing below the spaceship, as seen in the picture below. Can anyone tell me why this is happening, and how can I fix it?
- (void)newSpaceshipAtLocation:(CGPoint)location{
SKSpriteNode *hull = [[SKSpriteNode alloc]initWithImageNamed:#"Spaceship"];
hull.position = location;
hull.name = #"Spaceship";
hull.scale = 0.5;
SKSpriteNode *light = [self lights];
light.position = CGPointMake(hull.size.width / 5.0, hull.size.height/5.0);
[hull addChild:light];
[self addChild:hull];
}
- (SKSpriteNode *)lights{
SKSpriteNode *light = [[SKSpriteNode alloc]initWithColor:[NSColor yellowColor] size:CGSizeMake(50.0, 50.0)];
SKAction *blink = [SKAction sequence:#[
[SKAction fadeOutWithDuration:0.5],
[SKAction fadeInWithDuration:0.5],
]];
[light runAction:[SKAction repeatActionForever:blink]];
light.name = #"light";
return light;
}
Try setting the zPosition property on light. If you set it to anything higher than the hull sprite, it should work.

Run two SKActions at once

I'm using a sequence to run a list of SKActions. What I want to do however, is run an SKAction, then run two at once, then run one in sequence.
Here is my code:
SKNode *ballNode = [self childNodeWithName:#"ball"];
if (ballNode != Nil){
ballNode.name = nil;
SKAction *delay = [SKAction waitForDuration:3];
SKAction *scale = [SKAction scaleTo:0 duration:1];
SKAction *fadeOut = [SKAction fadeOutWithDuration:1];
SKAction *remove = [SKAction removeFromParent];
//put actions in sequence
SKAction *moveSequence = [SKAction sequence:#[delay, (run scale and fadeout at the same time), remove]];
//run action from node (child of SKLabelNode)
[ballNode runAction:moveSequence];
}
How can I accomplish this? I'm assuming I can't use a sequence?
Use a group action.
From sprite kit programming guide:
A group action is a collection of actions that all start executing as soon as the group is executed. You use groups when you want actions to be synchronized
SKSpriteNode *wheel = (SKSpriteNode*)[self childNodeWithName:#"wheel"];
CGFloat circumference = wheel.size.height * M_PI;
SKAction *oneRevolution = [SKAction rotateByAngle:-M_PI*2 duration:2.0];
SKAction *moveRight = [SKAction moveByX:circumference y:0 duration:2.0];
SKAction *group = [SKAction group:#[oneRevolution, moveRight]];
[wheel runAction:group];
A example in Swift would be:
let textLabel = SKLabelNode(text: "Some Text")
let moveTo = CGPointMake(600, 20)
let big = SKAction.scaleTo(3.0, duration: 0.1)
let med = SKAction.scaleTo(1.0, duration: 0.3)
let reduce = SKAction.scaleTo(0.2, duration: 1.0)
let move = SKAction.moveTo(moveTo, duration: 1.0)
let fade = SKAction.fadeOutWithDuration(2.0)
let removeNode = SKAction.removeFromParent()
let group = SKAction.group([fade, reduce])
let sequence = SKAction.sequence([big, med, move, group, removeNode])
self.addChild(textLabel)
textLabel.runAction(sequence)