Rob provided a great Objective-C solution for subclassing NSOperation to achieve a serial queuing mechanism for SKAction objects. I implemented this successfully in my own Swift project.
import SpriteKit
class ActionOperation : NSOperation
{
let _node: SKNode // The sprite node on which an action is to be performed
let _action: SKAction // The action to perform on the sprite node
var _finished = false // Our read-write mirror of the super's read-only finished property
var _executing = false // Our read-write mirror of the super's read-only executing property
/// Override read-only superclass property as read-write.
override var executing: Bool {
get { return _executing }
set {
willChangeValueForKey("isExecuting")
_executing = newValue
didChangeValueForKey("isExecuting")
}
}
/// Override read-only superclass property as read-write.
override var finished: Bool {
get { return _finished }
set {
willChangeValueForKey("isFinished")
_finished = newValue
didChangeValueForKey("isFinished")
}
}
/// Save off node and associated action for when it's time to run the action via start().
init(node: SKNode, action: SKAction) {
// This is equiv to ObjC:
// - (instancetype)initWithNode(SKNode *)node (SKAction *)action
// See "Exposing Swift Interfaces in Objective-C" at https://developer.apple.com/library/mac/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_35
_node = node
_action = action
super.init()
}
/// Add the node action to the main operation queue.
override func start()
{
if cancelled {
finished = true
return
}
executing = true
NSOperationQueue.mainQueue().addOperationWithBlock {
self._node.runAction(self._action) {
self.executing = false
self.finished = true
}
}
}
}
To use the ActionOperation, instantiate an NSOperationQueue class member in your client class:
var operationQueue = NSOperationQueue()
Add this important line in your init method:
operationQueue.maxConcurrentOperationCount = 1; // disallow follow actions from overlapping one another
And then when you are ready to add SKActions to it such that they run serially:
operationQueue.addOperation(ActionOperation(node: mySKNode, action: mySKAction))
Should you need to terminate the actions at any point:
operationQueue.cancelAllOperations() // this renders the queue unusable; you will need to recreate it if needing to queue anymore actions
Hope that helps!
According to the document:
In your custom implementation, you must generate KVO notifications for the
isExecuting
key path whenever the execution state of your operation object changes.
In your custom implementation, you must generate KVO notifications for the
isFinished
key path whenever the finished state of your operation object changes.
So I think you have to:
override var executing:Bool {
get { return _executing }
set {
willChangeValueForKey("isExecuting")
_executing = newValue
didChangeValueForKey("isExecuting")
}
}
override var finished:Bool {
get { return _finished }
set {
willChangeValueForKey("isFinished")
_finished = newValue
didChangeValueForKey("isFinished")
}
}
I want to group animations for several nodes. I first tried the solution above by grouping all actions in one, using runAction(_:onChildWithName:)
to specify which actions have to been done by node.
Unfortunately there were synchronisation problems because in the case of runAction(_:onChildWithName:)
the duration for SKAction
is instantaneous. So I have to found another way to group animations for several nodes in one operation.
I then modified the code above by adding an array of tuples (SKNode,SKActions)
.
The modified code presented here add the feature to init the operation for several nodes, each one of them having it's own actions.
For each node action is run inside it's own block added to the operation using addExecutionBlock
.
When an action complete, a completion block is executed calling checkCompletion()
in order to join them all. When all actions have completed then the operation is marked as finished
.
class ActionOperation : NSOperation
{
let _theActions:[(SKNode,SKAction)]
// The list of tuples :
// - SKNode The sprite node on which an action is to be performed
// - SKAction The action to perform on the sprite node
var _finished = false // Our read-write mirror of the super's read-only finished property
var _executing = false // Our read-write mirror of the super's read-only executing property
var _numberOfOperationsFinished = 0 // The number of finished operations
override var executing:Bool {
get { return _executing }
set {
willChangeValueForKey("isExecuting")
_executing = newValue
didChangeValueForKey("isExecuting")
}
}
override var finished:Bool {
get { return _finished }
set {
willChangeValueForKey("isFinished")
_finished = newValue
didChangeValueForKey("isFinished")
}
}
// Initialisation with one action for one node
//
// For backwards compatibility
//
init(node:SKNode, action:SKAction) {
_theActions = [(node,action)]
super.init()
}
init (theActions:[(SKNode,SKAction)]) {
_theActions = theActions
super.init()
}
func checkCompletion() {
_numberOfOperationsFinished++
if _numberOfOperationsFinished == _theActions.count {
self.executing = false
self.finished = true
}
}
override func start()
{
if cancelled {
finished = true
return
}
executing = true
_numberOfOperationsFinished = 0
var operation = NSBlockOperation()
for (node,action) in _theActions {
operation.addExecutionBlock({
node.runAction(action,completion:{ self.checkCompletion() })
})
}
NSOperationQueue.mainQueue().addOperation(operation)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With