Search code examples
swiftsprite-kitpositionskspritenode

Zooming Weirdness in SpriteKit


I am building an app using Swift and SpriteKit. The app loads an SKSpriteNode that is exactly the size of the screen and places it to exactly cover the screen. However, the user can move and zoom this SKSpriteNode as much as they like. I care about the absolute position of the SKSpriteNode because if the node is moved too much I like to bring it back to center, or "snap" it to the edges of the screen. (The SKSpriteNode will eventually hold a map).

When the node has a scale of exactly 1.0, the position values work as I expect them to. However, when I zoom the node (using a pinch gesture) and the scale grows above 1.0, the position values do not make sense. Here are some images to illustrate what I mean.

Here, you can see that the un-zoomed node has the exact same dimensions and position as the scene it is placed in. X and Y are both 0 for each the Map as well as the scene:

enter image description here

However, as you can see in this next image, when I zoom the Map to be a larger SKSpriteNode, and move it so that the lower left corner of the Map is exactly placed in the lower left corner of the screen, the X and Y values of the zoomed node are off. I would expect the X to be 0 and the Y to be 0 but they are not.

enter image description here

What is happening here, and how do I calculate the absolute position of a zoomed node? And when I say "absolute" position, perhaps what I mean is the position relative to the scene?? Anyway, your thoughts are appreciated.


Solution

  • I figured this out, although it wasn't easy. It turned out to be a case of "AnchorPoint Dorkitis". The primary symptom of this condition is writing code based on a false assumption, and in my case, the assumption that did me in was this:

    The (X,Y) point of my node was the lower left corner of the node. (false)

    I knew all along that I set my AnchorPoints (of both the scene as well as the SKSpriteNode) to (x: 0.5, y: 0.5) but it had not sunk in for me yet that this also was altering where on the screen the (X,Y) coordinate actually lived (hint: the center).

    For those who are interested, I worked out a nice set of computed properties to return the X, Y, height, width, top, bottom, left, and right values for my scene and my SKSpriteNode. I share you them, here:

    var map_x: Int {
        get {
            return Int(self.position.x)
        }
    }
    
    var map_y: Int {
        get {
            return Int(self.position.y)
        }
    }
    
    var map_h: Int {
        get {
            return Int(self.frame.height)
        }
    }
    
    var map_w: Int {
        get {
            return Int(self.frame.width)
        }
    }
    
    var map_t: Int {
        get {
            return Int(self.position.y + self.frame.height/2)
        }
    }
    
    var map_b: Int {
        get {
            return Int(self.position.y - self.frame.height/2)
        }
    }
    
    var map_l: Int {
        get {
            return Int(self.position.x - self.frame.width/2)
        }
    }
    
    var map_r: Int {
        get {
            return Int(self.position.x + self.frame.width/2)
        }
    }
    
    var scene_x: Int {
        get {
            return Int((scene?.position.x)!)
        }
    }
    
    var scene_y: Int {
        get {
            return Int((scene?.position.y)!)
        }
    }
    
    var scene_h: Int {
        get {
            return Int((scene?.frame.height)!)
        }
    }
    
    var scene_w: Int {
        get {
            return Int((scene?.frame.width)!)
        }
    }
    
    var scene_t: Int {
        get {
            return Int((scene?.position.y)! + (scene?.frame.height)!/2)
        }
    }
    
    var scene_b: Int {
        get {
            return Int((scene?.position.y)! - (scene?.frame.height)!/2)
        }
    }
    
    var scene_l: Int {
        get {
            return Int((scene?.position.x)! - (scene?.frame.width)!/2)
        }
    }
    
    var scene_r: Int {
        get {
            return Int((scene?.position.x)! + (scene?.frame.width)!/2)
        }
    }
    

    I could have made them CGFloat values and not wrapped them in Int() but I needed ints elsewhere in the code so I figured may as well do that here. I think the next step for me is to figure out how to do this in fewer lines of code :)