Just trying to enhance a part of this code, and I was wondering if I could include an if statement in a sequence? I want to slow down the spawning of debris when the debris count is greater than 10.
This is the current code:
-(void)spawnDebris {
//debris
SKSpriteNode * debris = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"debris1.png"] size:CGSizeMake(40, 40)];
debris.zPosition = 1.0;
debris.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:15];
debris.physicsBody.allowsRotation = NO;
debris.physicsBody.categoryBitMask = CollisionDebris;
RandomPosition = arc4random() %248;
RandomPosition = RandomPosition + 34;
debris.position = CGPointMake (RandomPosition, self.size.height + 40);
[_debris addObject:debris];
[self addChild:debris];
//next Spawn:
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:2],
[SKAction performSelector:#selector(spawnDebris) onTarget:self],
]]];
if (_dead == YES) {
[self removeAllActions];
}
}
Where _debris is an NSMutableArray. So essentially the SKSpriteNodes are being added every 2 seconds, but to change it up, I wanted to have their waitForDurations to change after the _debris count is over a certain number.
This is what I thought to do:
//next Spawn:
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:2],
[SKAction runBlock:^{
if (_debris.count > 10) {
[SKAction waitForDuration:7];
}
}],
[SKAction performSelector:#selector(spawnDebris) onTarget:self],
]]];
But this didn't work. Is it even possible to use an if statement in a sequence? How can I get this to work?
The code won't work because the [SKAction waitForDuration:7] is not executed.
Much easier would be:
float time = 2.0;
if (_debris.count > 10) { time = 9.0; }
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:time],
[SKAction performSelector:#selector(spawnDebris) onTarget:self],
]]];
The block declares a waitForDuration action but never runs it on anything.
Try
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:2],
[SKAction runBlock:^{
if (_debris.count > 10) {
[self runAction:[SKAction waitForDuration:7]];
}
}],
[SKAction performSelector:#selector(spawnDebris) onTarget:self],
]]];
Why don't you just use a random number generator to make it more interesting.
Instead of the if statement and all that, simply replace 2 with arc4random()% x (< choose a number) then it will spawn randomly every x seconds.
Related
I am working on a sprite kit game in which I have an action called playCountdown:
SKAction *playCountdown = [SKAction performSelector:#selector(startCountdown) onTarget:self];
playCountdown works fine but when it completes, the completion block never gets called.
[self runAction:playCountdown completion:^{
NSLog(#"anything goes");
}];
Here is startCountdown for completeness:
- (void)startCountdown{
SKLabelNode *countdownLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
__block short countdownValue = 3;
countdownLabel.position = CGPointMake(screenWidth/2, screenHeight/2 - countdownLabel.fontSize/2);
countdownLabel.zPosition = 100.0;
countdownLabel.text = [NSString stringWithFormat:#"%d", countdownValue];
countdownLabel.fontColor = [UIColor blackColor];
countdownLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeBaseline;
[self addChild:countdownLabel];
SKAction *zoomIn = [SKAction scaleTo:1.5f duration:0.9];
SKAction *fadeOut = [SKAction fadeAlphaTo:0.0f duration:0.1];
SKAction *updateCount = [SKAction customActionWithDuration:0.0 actionBlock:^(SKNode *node, CGFloat elapsedTime) {
SKLabelNode *textNode = (SKLabelNode *)node;
countdownValue --;
if (countdownValue == 0) {
textNode.text = #"PLAY!";
}
else{
textNode.text = [NSString stringWithFormat:#"%d", countdownValue];
}
textNode.alpha = 1.0f;
textNode.xScale = 1.0f;
textNode.yScale = 1.0f;
}];
SKAction *sequence = [SKAction sequence:#[zoomIn, fadeOut, updateCount]];
SKAction *repeat = [SKAction repeatAction:sequence count:4];
[countdownLabel runAction:repeat completion:^{
[countdownLabel removeFromParent];
}];
NSLog(#"completed!");
}
I have verified that startCountdown actually reaches its end. However the completion block never gets called. Any ideas?
Thanks
I would recommend you verify that your code is indeed being called by creating a very simple NSLog only block.
If you are still having problems, consider using a SKAction sequence instead. This will accomplish the same thing as you are trying to do with a completion block.
There must be an issue with a SKAction created using a selector... The code:
SKAction *playCountdown = [SKAction performSelector:#selector(startCountdown) onTarget:self];
would execute calling:
[self runAction:playCountdown completion:^{ ... }];
although the completion block would never get called.
I have instead created the playCountdown action using a block:
SKAction *playCountdown = [SKAction runBlock:^{
[self startCountdown];
}];
and the completion block now gets called. The problem now is that the end of the playCountdown action is not "understood" by the system and the 2 actions do not run serially but almost concurrently.
Ok I'm fairly new to coding with sprite kit so I don't know too much. I'm trying to make 5 bricks spawn above the scene and then slide down into the scene every 5 seconds. I thought this code would work but it does absolutely nothing to my scene.
I have another method that has bricks already loaded into the scene and that works so I don't think that would affect this much but not really sure.
-(void) spawnMoreBricks:(CGSize)size {
for (int i = 0; i < 5; i++) {
SKSpriteNode *brick = [SKSpriteNode spriteNodeWithImageNamed:#"brick"];
//resize bricks
brick.size = CGSizeMake(60, 30);
//position it
brick.position = CGPointMake(self.size.width/2, self.size.height);
SKAction *wait = [SKAction waitForDuration:5];
SKAction *spawn = [SKAction scaleTo:1 duration:0];
SKAction *move = [SKAction moveByX:0 y:-80 duration:3];
SKAction *spawnSequence = [SKAction sequence:#[wait, spawn, move]];
[self runAction:spawnSequence];
}
}
SKAction *wait = [SKAction waitForDuration:5];
SKAction *spawn = [SKAction runBlock:^{[self addChild: brick];
SKAction *move = [SKAction moveByX:0 y:-80 duration:3];
[brick runAction: move];
}];
SKAction *spawnSequence = [SKAction sequence:#[wait, spawn]];
[self runAction:spawnSequence];
Here is a modified set of actions to use. The problem was that you were running all the actions on the scene itself. The code provided runs a sequence on the scene that waits 5 seconds (acting as the delay), and then runs a spawn action. runBlock allows us to add the brick to the scene, create the move action, and apply that action to the brick (not the scene)
I use the following piece of code to generate SKNodes periodically. Is there a way to make the period of generation of these SKNodes random. Specifically, how do I make the "delayFish" in the following code an action with a random delay?
[self removeActionForKey:#"fishSpawn"];
SKAction* spawnFish = [SKAction performSelector:#selector(spawnLittleFishes) onTarget:self];
SKAction* delayFish = [SKAction waitForDuration:3.0/_moving.speed];
SKAction* spawnThenDelayFish = [SKAction sequence:#[spawnFish, delayFish]];
SKAction* spawnThenDelayFishForever = [SKAction repeatActionForever:spawnThenDelayFish];
[self runAction:spawnThenDelayFishForever withKey:#"fishSpawn"];
ObjC:
First set an average delay and range...
#define kAverageDelay 2.0
#define kDelayRange 1.0 // vary by plus or minus 0.5 seconds
and then change your delayFish action to this...
SKAction* delayFish = [SKAction waitForDuration:kAverageDelay withRange:kDelayRange];
Swift:
First set an average delay and range...
let averageDelay:TimeInterval = 2.0
let delayRange:TimeInterval = 1.0 // vary by plus or minus 0.5 seconds
and then change your delayFish action to this...
let delayFish = SKAction.wait(forDuration:averageDelay, withRange:delayRange)
Insert a random float instead of a fixed one.
In your case something like this:
double value = ((double)arc4random() / ARC4RANDOM_MAX)
* (maxValue - minValue)
+ minValue;
SKAction* delayFish = [SKAction waitForDuration:value/_moving.speed];
I see. This won't work in your case as repeatActionForever will run with the last created random value. Forever.
Maybe try this instead. I'm not sure if this works though:
SKAction* delayFish = [SKAction waitForDuration: (((double)arc4random() / ARC4RANDOM_MAX) * (maxValue - minValue)+ minValue)/_moving.speed];
I suggest making the random value an own method though.
-(double) getRandomValue(){
return (((double)arc4random() / ARC4RANDOM_MAX) * (maxValue - minValue)+ minValue);
}
EDIT:
Here is a link to a similar issue. Maybe that might help. Sorry!
SKAction *randomXMovement = [SKAction runBlock:^(void){
NSInteger xMovement = arc4random() % 20;
NSInteger leftOrRight = arc4random() % 2;
if (leftOrRight == 1) {
xMovement *= -1;
}
SKAction *moveX = [SKAction moveByX:xMovement y:0 duration:1.0];
[aSprite runAction:moveX];
}];
SKAction *wait = [SKAction waitForDuration:1.0];
SKAction *sequence = [SKAction sequence:#[randomXMovement, wait]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[aSprite runAction: repeat];
Source: SKAction: How to Animate Random Repeated Actions
I can not figure this out. I have a game where a player has to catch berries as they fall from the sky. When the player misses one, I want the screen to flash red and the berries to stop falling and for the game to pause for 3 seconds then resume.
#interface SpriteMyScene : SKScene{
SKAction *sceneUnPaused;
SKAction *scenePaused;
}
2 methods to pause the game.
-(SKAction*)unpaused {
sceneUnPaused = [SKAction runBlock:^{
self.scene.view.paused = NO;
}];
return sceneUnPaused;}
-(SKAction*)paused{
scenePaused = [SKAction runBlock:^{
self.scene.view.paused = YES;
}];
return scenePaused;
}
The method that creates the berries and their actions.
- (void)addBerry {
...
...
// Create the actions
//For each individual berry
SKAction * actionMove = ...
SKAction * gameWon = ...
SKAction * actionMoveDone = ...
SKAction *wait3Seconds = [SKAction waitForDuration:3];
SKAction * loseAction = [SKAction runBlock:^{
[self subtractLives];
NSLog(#"Lost a life");
[self vibrate];
[self runAction:scenePaused];
[self runAction:wait3Seconds];
[berry removeFromParent];
[self runAction:sceneUnPaused];
if (_playerLives == 0){
[[SoundManager sharedManager] stopMusic];
SKTransition *reveal = [SKTransition fadeWithDuration:1.0];
SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size andScore:_berriesKilled];
[self.view presentScene:gameOverScene transition: reveal];}}];
SKAction *berrie = [SKAction runBlock:^{
[berry runAction:actionMove];}];
[berry runAction:[SKAction sequence:#[berrie, gameWon, actionMove, loseAction, actionMoveDone]]];
}
I don't know if this is necessary or not for you guys to help me figure out why it won't work, but here is the update method that decides on when I will spawn the berries.
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > [self determineAmountOfBerries]) {
self.lastSpawnTimeInterval = 0;
[self addBerry];
}
}
When the player misses a berry, just do this:
[self.scene.view setPaused:YES];
Start an NSTimer that does the following after 3 seconds:
[berry removeFromParent];
[self.scene.view setPaused:NO];
I have two different SKSpriteNodes. The first is stationary and the second, I would like, to move around the first node. I have my second node is flying in from off screen with a zPosition of 11 (the first node has a zPosition of 10). When the second node flies to the opposite end of the screen, I have the node flip and fly back across. At this time I would like to change the zPosition to 9.
witch.position = CGPointMake(self.size.width + 200, self.size.height / 2 + 50);
witch.zPosition = 11;
[self addChild:witch];
SKAction *moveLeft = [SKAction moveToX:-200 duration:8];
SKAction *moveRight = [SKAction moveToX:self.size.width + 200 duration:8];
SKAction *moveDown = [SKAction moveToY:secondNode.position.y - 50 duration:0.3];
SKAction *moveUp = [SKAction moveToY:secondNode.position.y + 50 duration:0.3];
SKAction *flipRight = [SKAction scaleXTo:-1 duration:0];
SKAction *flipLeft = [SKAction scaleXTo:1 duration:0];
[secondNode runAction:
[SKAction repeatActionForever:
[SKAction sequence:
#[moveLeft, moveDown, flipRight, CHANGEZPOSITIONHERE, moveRight, moveUp, flipLeft, CHANGEZPOSITIONHERE]]]];
How would I go about changing the zPosition in the middle of an SKAction?
Thank you in advance for all suggestions and help.
This answer was found due to LearnCocos2D comment.
Adding a runBlock:action I was able to change the zPosition. The final code is:
[secondNode runAction:
[SKAction repeatActionForever:
[SKAction sequence:
#[moveLeft, moveDown, flipRight,
[SKAction runBlock:(dispatch_block_t)^(){
secondNode.zPosition = 9;
}], moveRight, moveUp, flipLeft,
[SKAction runBlock:(dispatch_block_t)^(){
secondNode.zPosition = 11;
}]]]]];