Randomizing SKActions - sprite-kit

I'm trying to move a Sprite over the screen from left to right. The sprite should start at a random y position on the right side offscreen. With a repeatActionForever and an integer with a random Y number i want the sprite to repeat the action starting from different y Positions. Any ideas on how to achieve this besides putting the random int in an update method? Is it anyhow achieveable through actions?
I use this method on the Sprite:
int randomY = (arc4random()%121;
SKAction *pos = [SKAction moveTo:CGPointMake((STAGESIZE.width+(self.size.width/2)),randomY) duration:0];
SKAction *move = [SKAction moveToX:0-self.size.width/2 duration:3];
SKAction *wait = [SKAction waitForDuration:1 withRange:5];
SKAction *sequence = [SKAction sequence:#[pos,move,wait]];
SKAction *repeater = [SKAction repeatActionForever:sequence];
[self runAction:repeater];

There is no way to randomise standard actions once defined and running.
But there is a workaround you can use to achieve desired effect using [customActionWithDuration].1
SKAction* randomPositionAction = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node,CGFloat elapsedTime){
int randomY = arc4random_uniform(121);
//Set position instead of running action with duration 0
[node setPosition:CGPointMake((STAGESIZE.width+(self.size.width/2)),randomY)];
}];
RandomY is randomised every time action is run and position is set according to that.

Related

Drawing line starting from SKSpriteNode_01.pos - ending at SKSpriteNode_02.pos (and adding particles)

Hi All I am wondering what is the best approach to draw a particlesystem line between two sprites (which updates if the sprite positions moved nudged/gravity etc)
Ive tried this way (ps sorry for my newbie code ><)
drawing the line starting from obj01 - ending at obj02
that is working so far but I im not sure how to update the line coordinates if the sprites move for example
the other problem related probably because the pathToDraw is not updated the particle system seems to drift off
So I know I need to update it (if someone could write a sudo code/idea of where I would expect to init the path to draw, where to remove etc that would really be very helpful in figuring out)
with thanks N :)
-(void)drawLineFromObj2:(EnemyClass*)obj03 to:(EnemyClass*)obj04
{
if (UFO02IsDead == NO && UFO03IsDead == NO)
{
if (ufo_02ReadyToLink == YES && ufo_03ReadyToLink == YES) {
NSLog(#"Line from Obj2 to obj03");
// **CreateLine**
lineNode02.path = pathToDraw;
lineNode02 = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, obj03.position.x, obj03.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, obj04.position.x, obj04.position.y);
lineNode02.path = pathToDraw;
//Add Particles
NSString *myParticlePath = [[NSBundle mainBundle] pathForResource:#"ForceField" ofType:#"sks"];
SKEmitterNode *myParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticle];
//get particles to drop by adding physics?
(this no effect the particles don't follow the line and don't seem
attached to it, and if the sprites move they dot change position either)
//myParticle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
//myParticle.physicsBody.affectedByGravity = YES;
//myParticle.physicsBody.allowsRotation = NO;
pathToDraw
myParticle uses SKAction followPath:pathToDraw
How to update pathToDraw to draw smooth lines and remove last line properly
SKAction *followTrack =
[SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:.5];
SKAction *forever = [SKAction repeatActionForever:followTrack];
myParticle.particleAction = forever;
lineNode02.name = #"lineNode";
[self addChild:lineNode02];
//is there a way to link the movement with the crystal positions and animate the line length and angle (is there another way of doing this)
//stops line being redrawn (but how to update when sprite is moved, and how to delete the old lines?)
ufo_03ReadyToLink = NO;
}
}
}
Fixed it by moving the function down into
-(void)update
created an action block
SKAction *remove = [SKAction removeFromParent];
SKAction* blockAction = [SKAction runBlock:^
{
[self DrawLine]; //draws new line every update
//then removes line
}];
[self runAction:[SKAction sequence:#[blockAction,remove]]];
also used a texture rather than a particle but this is how I attached it
//Add Particles
NSString *myParticlePath =
[[NSBundle mainBundle] pathForResource:#"ForceField" ofType:#"sks"];
myParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticle];
SKAction *followTrack =
[SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:.5];
SKAction *forever = [SKAction repeatActionForever:followTrack];
myParticle.particleAction = forever;
lineNode01.name = #"lineNode";
[self addChild:lineNode01];
Hope it helps someone else :)

SpriteKit SKAction group is running, but the SKSpriteNode image isn't being affected

I have created an SKAction group that alters the SKSpriteNode image (alpha and rotation) whilst playing a sound effect. However, when I runAction: the sound effect is played, but the image remains fixed.
In my subclassed SKSpriteNode (The Sheep) I have the following code that adds another SKSpriteNode (The Food) as a child:
-(void)setupEatingAction
{
_grassEating = [SKSpriteNode spriteNodeWithImageNamed:#"SheepFood"];
_grassEating.size = self.size;
_grassEating.anchorPoint = CGPointMake(0.5, 0.5);
_grassEating.alpha = 0.5;
_grassEating.zPosition = -1;
SKAction *fadeIn = [SKAction fadeInWithDuration:0.01];
SKAction *soundFX = [SKAction playSoundFileNamed:#"Munching2s.mp3" waitForCompletion:NO];
SKAction *rotate = [SKAction sequence:#[[SKAction rotateToAngle:degsToRads(-20.0) duration:kMovementAnimationInterval/4.0],[SKAction rotateToAngle:degsToRads(0.0) duration:kMovementAnimationInterval/4.0]]];
SKAction *eatAction = [SKAction group:#[fadeIn,soundFX,rotate]];
_grassEating.userData = [NSMutableDictionary dictionaryWithObject:eatAction forKey:KEY_EatAction];
[self addChild:_grassEating];
}
Then when I want the action group to run, I do the following:
-(void)eat
{
if(![_grassEating actionForKey:KEY_EatAction])
{
[_grassEating runAction:[_grassEating.userData objectForKey:KEY_EatAction] withKey:KEY_EatAction];
}
}
_grassEating is a property of my parent node:
#property (nonatomic,retain) SKSpriteNode *grassEating;
As I said, I can hear the sound effect playing, but the image isn't changed?!? For the life of me I can't work out why, and I've spent a lot of time trying to work out what is going on... any ideas?
I should also point out (although I don't think is anything to do with it) that this problem appears in both simulator and on hardware - both running iOS7.
I don't know if this is related iOS Spritekit Cannot Run SKAction on a Subclassed SKSpriteNode
But, it's the closest I can find and doesn't appear to be the same problem (on the surface).
Found the problem. It was a logical issue. The SKSpriteNode was being added and the sound FX started playing, but in the next frame or so the sprite was being removed.
As a result, it appeared that the sprite wasn't being displayed, but didn't have time to move into view. The sound FX continued to play, hence the confusion.

SpriteKit iOS 7 Sknode not being removed

I am developing a game for iOS 7, and it uses Spritekit.
The main skspritenode is a helicopter, and when this helicopter makes contact with a shrink bonus pickup, the helicopter shrinks to make navigation easier for the user. Here is the code for shrinking the helicopter that works as expected (self refers to the helicopter skspritenode):
SKAction *scaleHeli = [SKAction scaleBy:.70 duration:1];
SKAction *setPhysicsBody = [SKAction runBlock:^{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
self.physicsBody.mass = 0.080944441;
self.physicsBody.restitution = 1.0f;
self.physicsBody.allowsRotation = FALSE;
self.physicsBody.categoryBitMask = APAColliderTypeShip;
self.physicsBody.collisionBitMask = APAColliderTypeCavePiece;
self.physicsBody.contactTestBitMask = APAColliderTypeCavePiece | APAColliderTypeBonusPickup;
}];
SKAction *seq = [SKAction sequence:#[scaleHeli, setPhysicsBody]];
[self runAction:seq];
_shrinkEnabled = true;
I have a timer setup for the shrink bonus that lasts for a predetermined amount of time:
-(void)startShrinkTimer
{
//Creating the timer for the shield
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countDownShrinkDuration) userInfo:Nil repeats:true];
_timerSeconds = 15;
}
-(void)countDownShrinkDuration
{
if(_timerSeconds > 0)
{
--_timerSeconds;
}
else if(_timerSeconds == 0)
{
[_sceneRef disableShrinkBonus];
[_timer invalidate];
}
if(_timerSeconds < 5)
{
[_sceneRef shrinkWearingOut];
}
}
If the timer gets to be less than 5 seconds then the shrinkWearingOut function is called, which adds a label to a node that fades in and out (self refers to the helicopter skspritenode):
SKLabelNode *downLbl = [[SKLabelNode alloc] init];
downLbl.text = #"↓";
downLbl.fontColor = [UIColor whiteColor];
downLbl.fontSize = 9.0f;
downLbl.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
downLbl.position = CGPointMake(0,-30);
downLbl.zPosition = 5;
SKAction *fadeOut = [SKAction fadeOutWithDuration: .2];
SKAction *fadeIn = [SKAction fadeInWithDuration: .2];
SKAction *pulse = [SKAction sequence:#[fadeOut,fadeIn]];
SKAction *pulseForever = [SKAction repeatActionForever:pulse];
SKSpriteNode *wearingOut = [[SKSpriteNode alloc] init];
wearingOut.name = #"wearingOut";
[wearingOut addChild:downLbl];
[wearingOut runAction:pulseForever];
[self addChild:wearingOut];
Once the timer reaches zero, the disableShrink function is called, which attempts to remove the pulsating node and scale the helicopter to its original size (self refers to the helicopter skspritenode):
[[self childNodeWithName:#"wearingOut"] removeFromParent];
SKAction *scaleHeli = [SKAction scaleBy:1.3 duration:1];
SKAction *setPhysicsBody = [SKAction runBlock:^{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
self.physicsBody.mass = 0.080944441;
self.physicsBody.restitution = 0.0f;
self.physicsBody.allowsRotation = FALSE;
self.physicsBody.categoryBitMask = APAColliderTypeShip;
self.physicsBody.collisionBitMask = APAColliderTypeCavePiece;
self.physicsBody.contactTestBitMask = APAColliderTypeCavePiece | APAColliderTypeBonusPickup;
_shrinkEnabled = false;
}];
SKAction *seq = [SKAction sequence:#[scaleHeli, setPhysicsBody]];
[self runAction:seq];
Problem # 1: This line of code:
[[self childNodeWithName:#"wearingOut"] removeFromParent];
does not actually remove the pulsating node from the helicopter node, I have set a breakpoint here and verified that the node is not nil, yet it still seems to not remove the node. Very frustrating as I have used the exact same line of code with a different bonus pickup with a different name and it works just fine. The only difference is the other bonus pickup is using an skshapenode, either way they both inherit from sknode, so they should both function the same.
Problem # 2:The pulsating node is added successfully, but only pulses a couple of times, which I find strange because I use the exact set of skactions on a different bonus and that bonus pulses until it is removed from the helicopter node.
Observations:
You should not use NSTimer. Instead use the update: method of SKScene to perform timed actions and if needed forward it to child nodes that need it. Or use SKAction runBlock sequenced with waitForDuration. NSTimer may not run synchronous with Sprite Kit updates / render loop, and they won't stop firing if a node or the scene is paused.
Check that you don't have multiple child nodes with the name "wearingOut". Sounds like you may have added more than one. The use of NSTimer may actually cause strange behavior because Sprite Kit may delay adding/removing nodes from the scene and so timing of add/remove of nodes may be an issue. This is a wild guess though.
The pulsing issue may also indicate multiple sprites, perhaps they pulsate asynchronously so they appear as not pulsating. Alternatively try the fadeAlphaTo: actions.
In general try using the documented initializers. There may be issues if you do just [[SKLabelNode alloc] init]. I ran into some issues with [SKSpriteNode node], not drawing for instance even after setting a texture. Instead do [SKLabelNode labelNodeWithFontNamed:#"Arial"] or any of the other designated initializers. Refer to the Sprite Kit Framework reference.

Using SpriteKit how can you move sprite nodes to a point with a random but consistent speed

I'm currently making my first sprite game and one of the things i have implemented into my game is objects coming in from the side, move all the way across the screen, and then be removed from the parent. Here's what that looks like.
MyScene.m
-(void)createObstacle0 {
CGPoint startPoint = CGPointMake(-20, 100);
SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithImageNamed:#"obstacle"];
obstacle.position = CGPointMake(startPoint.x, startPoint.y);
obstacle.name = #"obstacle0";
[self addChild:obstacle];
float randomNum = arc4random_uniform(2) + 1.92;
[self performSelector:#selector(createObstacle0) withObject:nil afterDelay:randomNum];
}
excerpt from update method
[self enumerateChildNodesWithName:#"obstacle0" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x > 360) {
[node removeFromParent];
} else {
node.position = CGPointMake(node.position.x + 5.2, node.position.y);
}
}];
At the moment, my objects only move across the screen at one speed. I tried using arc4random inside the currentTime method code, but because thats evaluated every frame, my object would not stay at one speed. It would speed up, slow down, and look very erratic. How can i change my code in CurrentTime to allow my objects crossing the screen to be set a random speed as soon as its made and to keep it until it is destroyed?
Second question:
int yMin = 90;
int yMax = 110;
CGPoint startPoint = CGPointMake(-20, yMin + arc4random_uniform(yMax - yMin));
I would suggest that instead of manually updating the child nodes position to move it from one side to another you create your obstacle and then call runAction: on it using an SKAction created with the moveTo:duration: method. You specify the final point you want your obstacle to end up at as well as how long you want this move to take. Pass in a random number with in the minimum and maximum time you want it to take to duration.
This should allow you to create consistent but randomised object movement.
int MAX_TIME = 20;
int MIN_TIME = 10;
[obstacle runAction:[SKAction moveTo:CGPointMake( 10, 100 ) duration:MIN_TIME + arc4random_uniform( MAX_TIME - MIN_TIME )]];

Run completed SKAction in sequence

I am using this code as a base to create a container, that consists of a number of sprites so I can move them around as one entity. All is working except if i start to pan to early, before the container process is completed. If i do that the container node on top will drag the other sprites after. I want all sprites to be connected and move as connected.
If I wait until the whole process is completed there is no problems meaning a pan is moving the whole container node.
I wonder if someone could guide me how to stop interaction with the selected sprites that should be included in the container until the process is completed?
I have tried '_background.userInteractionEnabled = NO;' as well as adding to the nodes themselves but with no success. I have been trying to use runBlock with the same result.
I need to get 1.) all interaction with the nodes in _selectedNodeArray should be inhibited 2.) run all the code and create the container 3.) enable interaction to the container/node.
Here is the code:
if ([_selectedNodeArray count] > 2) {
//////CLEAN THE ARRAY//////
_selectedNodeArray = [[NSArray alloc]initWithArray:[self cleanSelectedNodeArray:_selectedNodeArray]];
NSLog(#"currentNode:%# position:%#", _currentNode.name, NSStringFromCGPoint(_currentNode.position));
SKNode *theSelectedNode = [_background childNodeWithName:_currentNode.name];
SKAction *pulseCard = [SKAction sequence:#[
[SKAction playSoundFileNamed:#"beep-7.wav" waitForCompletion:YES],
[SKAction colorizeWithColor:[SKColor yellowColor] colorBlendFactor:1.0 duration:0.15],
[SKAction waitForDuration:0.1],
[SKAction colorizeWithColorBlendFactor:1.0 duration:0.15],
[SKAction waitForDuration:0.1]
]];
SKAction *sequence = [SKAction sequence:#[
[SKAction repeatAction:pulseCard count:3],
[SKAction performSelector:#selector(kickStartContainerMode) onTarget:self]]];
[theSelectedNode runAction: sequence];
_isThisPressedAlready = YES;
}
The problem turned out to be this line:
[SKAction repeatAction:pulseCard count:3],
If the move of the sprite happened before the "count:3" was completed it seems like the container was not 100% packed.
It doesn't solve the real problem but the problem i had in this code.