Hi. I am stuck trying to figure out how I can perform a series of actions (moving) on a number of nodes one after another. In other words, I want one node to move; once the move is over, the second node will move; etc.
Having read a few sources, I ended up with something along these lines:
let dispatchGroup = DispatchGroup()
numberOfNodes = 6
var i=0
while numberOfNodes > 0
{
let positionToMove = CGPoint(x: 400, y: 400)
let moveAction = SKAction.move(to: positionToMove, duration: 1.0)
let waitAction = SKAction.wait(forDuration: 5)
let sequenceAction = SKAction.sequence([waitAction,moveAction])
dispatchGroup.enter()
arrayOfNodes[i].run(sequenceAction, completion: {
dispatchGroup.leave()
})
i+=1
numberOfNodes -= 1
}
dispatchGroup.notify(queue: DispatchQueue.main, execute: {
print("COMPLETED GROUP")
})
Still, I have all of my nodes moving at the same time, after which the notification is received. How can I make the nodes move one after another and then get a notification?
Thanks a lot!
I think you're overthinking this a bit. It's a relatively easy thing to accomplish, especially when the movement animation duration is the same for each node and you want each node to start animating right after its predecessor has finished moving. It would be slightly more difficult if you had specific node durations and delays. Though, even then it would only require two additional duration arrays to follow.
Just quickly, some things that should be corrected in your code:
- Using DispatchQueue with SpriteKit is a big no-no. SpriteKit is like a cardboard box inside another cardboard box, which is iOS. DispatchQueue kinda functions system-wide, so you're looking at timing outside of the SpriteKit universe. So, what you want is to tie timing in with SpriteKit's time, so whenever it does something (such as call a global isPaused variable), your game follows game time, not iOS time. In short, when controlling time in SpriteKit, always use SKActions. However, for frame updates, use the update() function.
- You might want to move your variables and initiations outside of your loop, so that it doesn't do all that work again and again within each loop cycle.
- You should always rely on the array to determine how many items, in this case nodes, there are, instead of having a separate variable for it.
- There are smarter and more secure ways in Swift to handle the loop iteration value than iterating one yourself in code. The same goes for controlling remaining nodes in the loop.
- There is a built in completion handler for SKActions, which you can use to determine when your sequence is done.
Here's my version of what you're trying to do:
// This assumes arrayOfNodes already exists.
let moveDuration = 1.0
let moveAction = SKAction.move(to: CGPoint(x: 400.0, y: 400.0), duration: moveDuration)
for (nodeIndex, node) in arrayOfNodes.enumerated() {
node.run(SKAction.sequence([SKAction.wait(forDuration: moveDuration * Double(nodeIndex)), moveAction]), completion: {
if nodeIndex == arrayOfNodes.endIndex - 1 {
print("COMPLETED GROUP")
}
})
}
So, first we set moveDuration outside of the loop, because it'll be used for moveAction's duration as well as to control delays for each node's movement inside the loop. We then set moveAction using your coordinates and moveDuration. The easiest, most readable and most secure loop to use is a For loop that cycles directly through the nodes in your array, and it's also enumerated, so you can extract the loop iteration value nodeIndex. For each cycle of the loop, the corresponding node in your array is given a run command with a sequence of a delay and then moveAction. The duration of the delay is determined by multiplying moveDuration with nodeIndex, which makes the node at index 0 have a 0.0 delay, then node at index 1 have 1.0 delay, ..., etc. At the end of each run command there's a completion handler, in which the completed message is print only if nodeIndex happens to be the endIndex of the array. This means the print is only done when the last node in the array is reached. In Swift, endIndex of an array means the last index after your last item, so you also need to decrement it back by 1.
I didn't test the code, but I don't see why it wouldn't work. Famous last words... 😀. Anyhow, try it out and ask away if you have any further questions.