Search code examples
swiftanimationsprite-kitimplementationskaction

How do SKActions change node properties without calling its setter in SpriteKit?


In the documentation for SpriteKit, I found this:

Generally, actions do not call public methods on nodes. For example, if you want to subclass SKNode to respond to a move(to:duration:) action, you might consider overriding its position property to add a didSet observer.

class MovingNode: SKSpriteNode {
    override var position: CGPoint {
        didSet {
            // code to react to position change
        }
    }
}

However, because a move action that's running on an instance of MovingNode (defined in the code listing above) doesn't set its position, the observer isn't invoked and thus, your code is never executed.

I find this to be very strange because if actions don't use someNode.position = newPosition, then how does the position of the node change? Isn't it impossible to change the value of a property without calling its setter?

However, if you print the value of someNode.position every frame, the new position is reflected. So, evidently, something is calling its setter. If it's not the SKAction then what is? And whatever is, how is it bypassing the property observer?

Does anyone know what behind the scenes work SKAction does to change the value of node properties without calling the setter?


Solution

  • You can achieve this behavior doing something like this:

    class Node { // aka SKNode
        private var _position = 0
        public var position: Int {
            get {
                return _position
            }
            set {
                _position = newValue
            }
        }
        
        public func runAction(_ value: Int) {
            _position = value
        }
    }
    
    class ObservedNode: Node { // aka MovingNode
        override var position: Int {
            didSet {
                print(position)
            }
        }
    }
    

    Then if you do this

    let node = ObservedNode()
    node.runAction(10)
    print(node.position)
    

    You can see the position changed, but you skip the didSet observer

    Remember run runs directly inside the SKNode with access to private values.