var action = SKAction.sequence([
SKAction.waitForDuration(1),
SKAction.runBlock({
//Some code
})
])
I want the above action to keep repeating while some condition is true. How can I do this?
I know I can run the action once with runAction(action) or repeat forever using runAction(SKAction.repeatActionForever(action)). How do I only repeat it while some condition is true though?
If this isn't possible using actions, is there an alternative to how I can repeat these steps while some condition is true (obviously, on a separate thread, I don't want this to freeze my app):
1) Wait for a second
2) Execute a block of code
3) Check if the condition is true. If it is stop repeating. Else, repeat.
I'm hesitant to use sleep() because that sounds like a bad solution, and something Apple wouldn't allow for apps in their store.
Alternative Solution using Swift:
runAction(
SKAction.repeatActionForever (
SKAction.sequence([
SKAction.waitForDuration(1),
SKAction.runBlock({
//Code you want to execute
if conditionIsTrue {
self.removeActionForKey("New Thread")
}
})
])
),
withKey: "New Thread"
)
You can use SKAction's
(SKAction *)runBlock:(dispatch_block_t)block
queue:(dispatch_queue_t)queue
You would want to create a block. Inside just check for the variable and then apply your logic accordingly, once the condition is no longer true you can remove it from the object.
If you want to modify the variable add __block in front of its declaration.
SKAction *logic = [SKAction runBlock:^{
if (myvar){
NSLog(#"hello world!");}
else
{
//remove actions
}} queue : dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
SKAction *delay = [SKAction waitForDuration: 0.5];
SKAction *mySequence = [SKAction sequence:#[logic,delay,nil]];
[myNode runAction:mySequence];
easy as apple pie my boy
Related
I am building a simple dice roll game.
The game cycle is like this:
1) Roll dice and move piece
2) Check if there is extra bonus moves, if Yes then move the piece
3) Check if piece has reached its destination, if Yes then Game ends
4) End turn
I built my Game loop using SKAction.run statements and append them into an array and then run them.
The actual code is very long, so I am providing only the basic flow here.
func runDiceRoll() {
// some SKActions
}
func checkExtraMoves() {
// run some SKActions depending on runDiceRoll() outcome
}
func checkStatus() {
// code to check if Game ends, depends on checkExtraMoves() outcome
}
func endTurn() {
// code to end the player's turn and give it to the other player
}
My game loop looks something like this
func runGameCycle() {
var actions = [SKAction]()
let actionRun = SKAction.run {runDiceRoll()}
let actionCheckExtraMoves = SKAction.run {checkExtraMoves()}
let actionCheckStatus = SKAction.run {checkStatus()}
let actionEndTurn = SKAction.run {endTurn()}
let actionWait = SKAction.wait(forDuration:2.0)
actions.append(actionRun)
actions.append(actionWait)
actions.append(actionCheckExtraMoves)
actions.append(actionCheckStatus)
actions.append(actionEndTurn)
piece.run(SKAction.sequence(actions))
}
If I remove the Wait action, then the game play is not correct.
I would prefer not to use a Wait duration because it is just an estimate that the preceeding actions will be completed in 2 seconds or less.
I think it would be better if all the actions waits for the preceeding action to complete before firing. I am not sure how to use a completion handler or DispatchQueue for such purposes.
Is there a better way, more foolproof way to write the game loop?
I am creating a game where the user can move a SKShapeNode around. Now, I am trying to write a utility function that will perform back-to-back animations sequentially.
Description of what I'm Trying
I first dispatch_async to a serial thread. This thread then calls a dispatch_sync on the main thread to perform an animation. Now, to make the animations run sequentially, I would like to block the GlobalSerialAnimationQueue until the animation is completed on the main thread. By doing this, I (theoretically) would be able to run animations sequentially. My code is pasted below for more description
func moveToCurrentPosition() {
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: 1.0)
dispatch_async(GlobalSerialAnimateQueue) {
//this just creates an action to move to a point
dispatch_sync(GlobalMainQueue, {
self.userNode!.runAction(action) {
//inside the completion block now want to continue
//WOULD WANT TO TRIGGER THREAD TO CONTINUE HERE
}
})
//WOULD LIKE TO PAUSE HERE, THIS BLOCK FINISHING ONLY WHEN THE ANIMATION IS COMPLETE
}
}
So, my question is, how would I write a function that can take in requests for animations, and then perform them sequentially? What grand-central-dispatch tools should I use, or should I be trying a completely different approach?
I figured out how to do this using a grand-central-dispatch semaphore. My updated code is here.
func moveToCurrentPosition() {
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: Animation.USER_MOVE_DURATION)
dispatch_async(GlobalSerialAnimateQueue) {
let semaphore = dispatch_semaphore_create(0)
dispatch_sync(GlobalMainQueue, {
self.userNode!.runAction(action) {
//signal done
dispatch_semaphore_signal(semaphore)
}
})
//wait here...
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
}
SpriteKit provides a much simpler and intuitive API for running actions in sequence. Have a look at the documentation here.
You can simply perform actions as a sequence of events, with blocks in between or as completion:
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: Animation.USER_MOVE_DURATION)
let otherAction = SKAction.runBlock({
//Perform completion here.
})
self.userNode!.runAction(SKAction.sequence([action, otherAction]))
Is there a way to pause certain action in an SKSpriteNode while running other actions on the same sprite ?
You can run action with key, like this:
Objective-C
[yourNode runAction:yourAction withKey:#"aKey"];
Then you can access that particular action like this:
SKAction *action = [yourNode actionForKey:#"aKey"];
if(action){
action.speed = 0; //pause action
}
Swift
To run an action with key:
yourNode.runAction(yourAction , withKey: "aKey")
To pause the action:
if let action = ball.actionForKey("aKey"){
action.speed = 0
}
In my SpriteKit project, I have a spawnEnemy method. I want this method to be called over and over again so that enemies continue to spawn throughout the game.
What I did to achieve this is to put the spawnEnemy method in an SKAction runBlock and have my SKScene run that action forever with a delay in between calls (to prevent overloading the app).
Below is a snippet of the relevant code:
var _spawnSpeedSeconds: Double = 2.0
func startSpawning()
{
let waitForXSeconds = SKAction.waitForDuration(self._spawnSpeedSeconds)
let spawn = SKAction.runBlock({ () -> Void in
self.spawnEnemy()
})
self.runAction(SKAction.repeatActionForever(SKAction.sequence([spawn, waitForXSeconds])), withKey: "spawnEnemy")
}
func spawnEnemy()
{
...
}
Now after a certain time (for example when the player gets 5 points or something), I decrease the _spawnSpeedSeconds to make the enemies spawn more in a shorter amount of time to increase difficulty.
The problem is, even if I decrease my _spawnSpeedSeconds variable, the spawn action's delay being ran by the SKScene is still the same.
The only way I can think of resolving this issue is to remove the action, and then re-add the action with a new delay/spawn rate. Is there a better way to approach this issue?
Thanks
don't use an action in this case. They don't really work in a dynamic way. Once you set them, they're stuck. just use your update method.
I'll show you how I'm periodically launching missles:
first set two timers in your class
var missleTimer:NSTimeInterval = NSTimeInterval(2)
var missleInterval:NSTimeInterval = NSTimeInterval(2)
now in our update method we count down the time and spawn missles
// subtract time from our timer
self.missleTimer -= self.delta
// when our timer reaches zero
if self.missleTimer <= 0 {
// run your spawning code
self.launchMissle()
// reset timer
self.missleTimer = self.missleInterval
}
this is better than using an action in this case because I can set missleInterval anywhere in my code and the change will always be reflected.
-(void)recursiveMethod
{
if(shouldSpawnEnemy)
{
_spawnSpeedSeconds -= 0.01;
SKAction *wait = [SKAction waitForDuration: 0.5];
SKAction *action = [SKAction performSelector:#selector(recursiveMethod) onTarget:self];
SKAction *sequence = [SKAction sequence:#[wait,action]];
[self repeatActionForever:sequence];
}
}
Remember to call the recursiveMethod when you want to start spawning enemies.
Hope that helps.
Providing swift solution without the need for timers, using recursion with a circuit breaker flag:
class YourScene: SKScene {
var gameOver: Bool = false
func spawnEnemies() {
let waitAction = SKAction.wait(forDuration: yourDynamicDurationHere)
self.run(SKAction.sequence([SKAction.run(self.spawnEnemy), waitAction])) {
if !gameOver {
spawnEnemies()
}
}
}
}
This solution leverages the completion handler of run method to recursively schedule another execution of the sequence. Since each individual SKSequence is created just before it is executed, we can pass in a different wait time for each iteration.
To stop the recursive loop, just set gameOver = true.
I have a SpriteKit game where I have a number of different Sprites Lasers, Planes, etc. A number of these Sprites have a sounds that will play whenever they are visible. Right now I play the sound in the SKSpriteNode itself. This probably isn't what I want because if I have I have 50 of the same sprites, they'll all be playing the same sound and gobbing resources. However, I'm not really sure where would be the best place to play the the sound, because for some of these sprites I would need to check if it should be playing or not. I suppose I could put the sounds in the the GameScene and loop through all sprites during every update to determine what sounds should and should not still be playing, that way there would only be one instance of every sound. I was hoping to keep all the sprite code within the sprite itself, but this would solve the issue with multiple instances of the same sound playing. Is this the correct way to handle sounds?
It is hard to give you a proper code example without seeing your code. You can however get the gist of what you can try with what I have below.
In MyScene add a property BOOL propSound.
Whenever you create a new plane and wonder about adding the prop sound do something like this:
if(propSound == false)
{
propSound = true;
// add your code to play prop sound
}
If you add every newly created plane sprite into a NSMutableArray like this [yourArrayName addObject:newPlane];, you can check the array count every time you remove a plane in order to know if the last plane was removed and the sound needs to be stopped.
if([yourArrayName count] == 0)
{
propSound = false;
// add your code to stop prop sound
}
Maybe you are looking for something like this?
NSMutableArray *coin = [[NSMutableArray alloc] initWithCapacity:1];
SKAction *_coinSound = [SKAction playSoundFileNamed:#"Pickup_Coin4.wav" waitForCompletion:NO];
[coin addObject:_coinSound];
SKAction *_playCoinSound = [SKAction sequence:coin];
Then I just check if the "character" in my case touched the coin. If so it will play the sound.
if([_adventurer intersectsNode:coin]){
[coin runAction:_playCoinSound];
}
Edit:
Saw your comment on your question. I guess you could check if there is at least 1 plane on the screen, if so then you could play a loop of the sound. If there is less than 1 no sound.
Do not play the same sound again if there already are playing. You could use a BOOL to see if the sound is already playing.
BOOL isPlaneSoundOn = false;
if(!isPlaneSoundOn && _planeNode.count > 0){
//play the sound
isPlaneSoundOn = true;
} else if(_plane.count == 0) {
//stop the sound
isPlaneSoundOn = false;
}
Something like that. Maybe you get the idea :)
I would personally play any community sounds off the scene using the SKAction playSoundWithFilename, and assign this sound a key.
Then I would check if the key exists, and if it does not, then play the sound