SpriteKit texture change according to sprite x-axis direction - objective-c

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];
}

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 SKSpriteNode slide

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.

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

How to set horizontal back and forth movements on a sprite?

I am writing a game where i'm spawning some sprites that move on the same horizontal plane, i.e., their y-coordinate is always the same. I'm not sure physics, but rather only SKActions.
Initially I wrote this to spawn the sprite at location Y, and move to a point off the screen in a randomly chosen direction. Now, I'd like this sprite to instead turn around and go backwards until it hits the opposite end of the screen, where I wan it to again turn around, etc.
Initially I have the action set up as such :
SKAction * actionMove = [SKAction moveTo:destination duration:duration];
where destination and duration are basically randomly generated. This obviously moves the sprites in one direction only.
what's an elegant way to I make this an un-ending loop of sprites turning around and repeating their path over and over again?
I figured out how to do this with action sequences. I initially tried setting this up in the
update
method of the scene, and that made everything terribly slow, as expected. So I set up the following sequence of actions (I have the sprites moving back and forth at the same y-coordinate, so they just need to turn around the continue their journey)
CGPoint destination1, destination2;
if (sprite.direction == LeftDirection)
{
destination1 =CGPointMake(-sprite.size.width/2, sprite.position.y);
destination2 = CGPointMake(self.frame.size.width + sprite.size.width/2, sprite.position.y);
}else {
destination1 =CGPointMake(self.frame.size.width + sprite.size.width/2
, sprite.position.y);
destination2 = CGPointMake(-sprite.size.width/2, sprite.position.y);
}
int duration = [sprite getDuration]; //this generates a random duration amount
// Create the actions
SKAction * actionMove1 = [SKAction moveTo:destination1 duration:duration];
//turn the sprite around
SKAction * rotateAction = [SKAction runBlock:^{
sprite.xScale = sprite.xScale * -1;
sprite.direction = (sprite.direction == LeftDirection) ? RightDirection : LeftDirection;
}];
//reverse the movement
SKAction * actionMove2 = [SKAction moveTo:destination2 duration:duration];
SKAction * actionMove = [SKAction sequence:#[actionMove1, rotateAction, actionMove2, rotateAction]];
SKAction * repeat = [SKAction repeatActionForever:actionMove];
once this is created, we run the repeat action on the sprite. And voila! continuous movement that changes direction.

Objective-C:How to pause during a loop so that the sequence can finish before moving onto the next iteration?

I am making a simon game in objective-c using sprite kit in Xcode. What I am trying to accomplish is to pick random number, add it to the sequence, fadeout, pick next random number...
what happens when I run this code is that all the tiles in the sequence fadeout then back in at exactly the same time.
Here is my code:
-(void) sequence
{
NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
int randomNumber;
SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:#[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
CFTimeInterval startTime;
float elapsedTime;
for(int i = 0; i<level+1; i++)
{
startTime = CFAbsoluteTimeGetCurrent();
randomNumber=[self getRandomNumber]; //random number is picked
if(randomNumber==0)
{
sequenceArray[i]=blueBox;
}
else if(randomNumber==1)
{
sequenceArray[i]=redBox;
}
else if(randomNumber==2)
{
sequenceArray[i]=yellowBox;
}
else if(randomNumber==3)
{
sequenceArray[i]=greenBox;
}
[sequenceArray[i] runAction:[SKAction repeatAction:pulse count:1]]; //for each tile in the sequence the color fades
//out then back in
elapsedTime = CFAbsoluteTimeGetCurrent()-startTime; //trying to pause the loop so the sequence can finish before the
//next random number is picked
while(elapsedTime<.5)
{
elapsedTime = CFAbsoluteTimeGetCurrent()-startTime;
NSLog(#"elapsed time %f", elapsedTime);
}
}
}
Ok I have tried to implement some of the suggestions below. I still am running into errors, for example now the sequenceArray[index] at index=0 is out of bounds.
Here is my new code:
-(void)doLoop:(int)index
limit:(int)limit
sprite:(SKSpriteNode*) sprite
body:(void(^)(int))body
{
body(index);
index++;
if(index<level+1)
{
SKAction *loopAction;
[self runAction:loopAction completion:^{
[self doLoop:index limit:limit sprite:sprite body:body];
}];
}
}
-(void) sequence
{
NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
int index = 0;
SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:#[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
[self doLoop:0 limit:level+1 sprite:sequenceArray[index] body:^(int index){
int randomNumber=[self getRandomNumber]; //random number is picked
if(randomNumber==0)
{
sequenceArray[index]=blueBox;
}
else if(randomNumber==1)
{
sequenceArray[index]=redBox;
}
else if(randomNumber==2)
{
sequenceArray[index]=yellowBox;
}
else if(randomNumber==3)
{
sequenceArray[index]=greenBox;
}
[sequenceArray[index] runAction:pulse]; //for each tile in the sequence the color fades
//out then back in
NSLog(#"fadeout");
}];
}
Your attempt to delay your loop with a while statement is busy waiting and if this code is executing on your main thread provides no opportunity for your application to process events during the wait. This is the root cause of your problem.
What you need to do is replace your loop with a sequence of actions, each of which performs the work of one iteration, followed by completion blocks, where each completion block performs the "rest" of the loop. One way to do this is shown in this answer - in that case the need was for a simple delay and so the continuation of the "loop" is scheduled using dispatch_after().
In you case you can replace your calls to runAction: with calls to runAction:completion: to schedule the continuation of your "loop".
HTH
Addendum
Your attempt is a bit mixed up. Here is some untested code type directly into the answer - expect errors!
- (void) doLoop:(int)index
limit:(int)limit
sequenceArray:(NSMutableArray *)sequenceArray
action:(SKAction *)pulse
{
switch ([self getRandomNumber])
{
case 0:
sequenceArray[index] = blueBox; break;
case 1:
sequenceArray[index] = redBox; break;
case 2:
sequenceArray[index] = yellowBox; break;
case 3:
sequenceArray[index] = greenBox; break;
}
[sequenceArray[index] runAction:[SKAction repeatAction:pulse count:1]
completion:^{
// do next "iteration" if there is one
int next = index + 1;
if (next < limit)
[self doLoop:next
limit:limit
sequenceArray:sequenceArray
action:pulse];
}];
}
- (void) chainedSequence
{
NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:#[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
[self doLoop:0 limit:(level+1) sequenceArray:sequenceArray action:pulse];
}
Make sure you understand what it is doing before using it! The previously referenced answer provides more detail.
The above code passes around and fills up your sequenceArray, however you never use this in the code you provide other than for temporary storage within the method. Do you really need this array? If so remember that it contents will not be completely valid until the after the last "iteration" even though the call to doLoop: in chainedSequence will return before even the first action is complete.
It looks like you're calling this method on the main thread (the same thread which manages all of your application's view drawing). That means that you are queuing up all of your actions and then running them all at one. Your while delay slows down the entire loop but never yields control for these animations to run. None of your view changes will appear until your sequence method returns and the current event loop is able to finish.
Instead of waiting for animations to finish that while is delaying them from starting.
Look at creating an SKAction sequence which exists for exactly this sort of scenario; when you want a number of SKActions to fire sequentially. By adding all of your actions to a sequence and then starting the sequence you save yourself from needing to either run a bunch of callback blocks on the main thread or synchronize some background thread selecting blocks with the main thread animating those selections.