1

Given the following class,

class Spaceship {

    var position: CGPoint! {
        didSet {
            node.position = position
        }
    }

    var node: SKSpriteNode!

    init(frame: CGRect) {

        node = SKSpriteNode(
            color: UIColor.red,
            size: CGSize(
                width: frame.size.width / 5,
                height: frame.size.width / 5
            )
        )

        self.position = CGPoint(x: frame.midX, y: frame.midY)

    } 

}

it looks as though the didSet observer of the position property does not get called.

Since the didSet observer should set the SpriteKit node's position when the position property gets modified, I thought that instead of using the same line of code contained within the didSet block, I could trigger the latter instead, but it doesn't seem to work; when the scene gets created (in the main GameScene, which simply creates a Spaceship objects and adds the spaceship.node to the scene's node children), the node's position seems to be 0; 0.

Any clue on why does this happen? Thank you.

Update: although the defer statement works, the assignment does not allow to make use of it.

Johnny Bueti
  • 637
  • 1
  • 8
  • 27

3 Answers3

3

Rather than using didSet to update your node, directly forward queries to it:

class Spaceship {

    let node: SKSpriteNode

    init(frame: CGRect) {
        self.node = SKSpriteNode(
            color: UIColor.red,
            size: CGSize(
                width: frame.size.width / 5,
                height: frame.size.width / 5
            )
        )

        self.position = CGPoint(x: frame.midX, y: frame.midY)
    }

    var position: CGPoint {
        get { return node.position }
        set { node.position = newValue }
    }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Brilliant! It works, but the setter needs the value to be assigned as a parameter: `set(newValue) { ... }`, otherwise it doesn't seem to work; maybe the compiler retrieves the old stored value and assigns it to `node.position`. Is this the case? – Johnny Bueti Mar 07 '19 at 18:15
  • 1
    @Fehniix It was a type on my part. `node.position = position` would be equivalent to `node.position = self.position`, thus equivalent to `node.position = node.position`, so it woudln't do anything. You can add a name to the value, but by default, it's implicitly `newValue`, so I just changed it to use that – Alexander Mar 07 '19 at 18:43
1

The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.

One possible solution could be to set the position property outside of the Spaceship's initialiser, or also set directly the node's position.

Johnny Bueti
  • 637
  • 1
  • 8
  • 27
  • 1
    "_The position property needs to be set either outside of the Spaceship class_" statement is technically incorrect. It can be set from within `Spaceship` too. Basically `didSet` will be called anytime it's value is changed, only not when within `init`. – staticVoidMan Mar 07 '19 at 17:58
  • [quote is ok](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID262), conclusion not. Anyway answer disserve my vote. – Marek R Mar 07 '19 at 18:02
0

I personally would just subclass Spaceship as an SKSpriteNode. Spaceship does not seem to be a complex object that involves composition over inheritance.

class Spaceship : SKSpriteNode {
    convenience init(frame: CGRect) {
        self.init(
            color: UIColor.red,
            size: CGSize(
                width: frame.size.width / 5, 
                height: frame.size.width / 5
            )
        )
        self.position = CGPoint(x: frame.midX, y: frame.midY)
   } 
}

Now I am not sure why you are passing in frame like this.... I am assuming frame is your scene? If this is the case, you are going to come across issues when anchor point is not (0,0).

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • I've chosen this pattern to be simply able to instantiate a Spaceship object in the MainScene and add the child node; instead of clogging the MainScene with thousands of lines of code, I'd prefer to adopt this pattern for tidiness' sake. Would you suggest a different pattern to achieve this result? Thank you. – Johnny Bueti Mar 08 '19 at 19:39
  • What you said has nothing to do with what i am asking. I am not seeing the purpose of passing in the frame. Is frame your scene size? You typically only want to design for 1 scene size – Knight0fDragon Mar 08 '19 at 19:42
  • Sorry. Yes, it is the frame size. – Johnny Bueti Mar 08 '19 at 19:49
  • Is your goal to have the space ship start at the center of the scene? Is there any reason as to why your scene anchor point is not 0.5 0.5 other than you may have been looking at a bad tutorial – Knight0fDragon Mar 08 '19 at 21:53