2

I have a simple subclass of SKCameraNode that I called InteractiveCameraNode. For now it's very simple: I need things to happen when my camera's position changes. Here is what I did:

class InteractiveCameraNode: SKCameraNode {
   // MARK: - Properties
   var enableInteraction = true
   var positionResponders = [(CGPoint, CGPoint) -> Void]()


   /// Calls every closure in the `positionResponders` array
   override var position: CGPoint {
      didSet {
         if enableInteraction {
            for responder in positionResponders {
               responder(oldValue, position)
            }
         }
      }
   }
}

Since I might have multiple things happening when the camera moves, I have an array of closures that are called when the camera's position is changed. So far everything works perfectly except the didSet observer does not get called if I move the camera using an action. If I use a constraint on the camera to make it track a node and then move that node with an action, it works. If I move the camera by hand, it works. Why won't it work with actions?

Brian
  • 14,610
  • 7
  • 35
  • 43
BadgerBadger
  • 696
  • 3
  • 12

2 Answers2

1

my previous answer was incorrect 100%.. Whatever lead me to that answer was some other mistake on my part and I apologize.. I learned something useful today as well :)

It looks like that behavior you're seeing is either A) a bug or B) some deeper mechanism of Swift / SK that I don't understand.

Here is a workaround that worked for me in playground:

class Noder: SKSpriteNode {

  var myPosition: CGPoint = CGPoint.zero { didSet { print(position) } }

  func initialize(scene: SKScene) {
    isUserInteractionEnabled = true
    scene.addChild(self)
  }

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    position = touches.first!.location(in: self.scene!)
  }

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    print(self.position)
    run(.moveBy(x: -35, y: 0, duration: 0.5), completion: {
      self.myPosition = self.position
    })
  }
}

to use:

// In didmovetoview:
let node = Noder(color: .black, size: CGSize(width: 25, height: 25))
node.initialize(scene: self)

output, showing that the actual position has been updated from an action and didSet:

(50.0, -118.0)
(14.9999971389771, -118.0)


But this will probably be too slow for what you want, so you will be better off manually setting position or myPosition in update() every frame (whichever one will work for didSet lol..)

(Credit to Confused for this paragraph).


Also, I wonder if a KVO would work here.. I haven't used them, but CameraNode is already an NSObject... something to look into maybe since this is likely a bug of some sort...

Fluidity
  • 3,985
  • 1
  • 13
  • 34
  • Dorry for not knowing but... what is a KVO? – BadgerBadger Jan 13 '17 at 01:21
  • 1
    @BadgerBadger https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177-BCICJDHA – Fluidity Jan 13 '17 at 01:24
  • i generally dislike the whole delegate / kvo / messenger / signal patterns (i like doing things manually, top to bottom), but in this case I would explore this option since it's sounds like exactly what you are trying to do @BadgerBadger – Fluidity Jan 13 '17 at 01:27
  • Thanks! I'll have a look! – BadgerBadger Jan 13 '17 at 05:36
0

Running an action that mutates the position of the calling Node subclass doesn't set its position, the property observer (in your case the didSet) isn't invoked and thus, your code is never executed.

In this case, the solution is to use SKSceneDelegate and compare the node's position across two of its callbacks. You save the node's initial position in update(_:for:), which is called at the beginning of each frame, then calculate any change in position in didEvaluateActions(for:), which is called after actions have been evaluated.

Read more

Seivan
  • 668
  • 6
  • 13