Search code examples
iosswiftsprite-kitorientationsklabelnode

SpriteKit device orientation change causes SKLabelNode distortion Swift


I am using SpriteKit to write a game for iOS in Swift. I am still fairly new to SpriteKit.

I would like to support both orientations for both iPhone and iPad, and have found this: multiple orientations in SpriteKit

This works as expected in the simulator and device, however on device I notice some SKSpriteNodes distort to their new size slightly before the device rotation animation.

This is very noticeable especially with SKLabelNodes where the text distorts either slightly squashed or stretched depending on the orientation change.

I have an idea why the distortion is happening, but confirmation and a fix would be fantastic.

This occurs on device with the code described in the link, but I have updated for swift 3

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = GameScene(size:self.view.bounds.size)
        scene.scaleMode = .resizeFill
        (self.view as! SKView).presentScene(scene)
    }

    override var shouldAutorotate: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }
}

class GameScene: SKScene {
    var currentNode: CustomNode!

    override func didMove(to view: SKView) {
        self.backgroundColor = SKColor.white
        transitionToScene(sceneType: .Menu)
    }
    override func didChangeSize(_ oldSize: CGSize) {
        currentNode?.layout()
    }
    func transitionToScene(sceneType: SceneTransition) {
        switch sceneType {
        case .Menu:
            currentNode?.dismissWithAnimation(animation: .Right)
            currentNode = MenuNode(gameScene: self)
            currentNode.presentWithAnimation(animation: .Right)

        case .Scores:
            currentNode?.dismissWithAnimation(animation: .Left)
            currentNode = ScoresNode(gameScene: self)
            currentNode.presentWithAnimation(animation: .Left)

        default: fatalError("Unknown scene transition.")
        }
    }
}

class CustomNode: SKNode {
    weak var gameScene: GameScene!

    init(gameScene: GameScene) {
        self.gameScene = gameScene
        super.init()
    }
    func layout() {}
    func presentWithAnimation(animation:Animation) {
        layout()
        let invert: CGFloat = animation == .Left ? 1 : -1
        self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
        gameScene.addChild(self)
        let action = SKAction.move(to: CGPoint(x: 0, y: 0), duration: 0.3)
        action.timingMode = SKActionTimingMode.easeInEaseOut
        self.run(action)
    }
    func dismissWithAnimation(animation:Animation) {
        let invert: CGFloat = animation == .Left ? 1 : -1
        self.position = CGPoint(x: 0, y: 0)
        let action = SKAction.move(to: CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
        action.timingMode = SKActionTimingMode.easeInEaseOut
        self.run(action, completion: {self.removeFromParent()})
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


class MenuNode: CustomNode {
    var label: SKLabelNode
    var container: SKSpriteNode

    override func layout() {
        container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
    }
    override init(gameScene: GameScene) {
        label = SKLabelNode(text: "Menu Scene")
        label.horizontalAlignmentMode = .center
        label.verticalAlignmentMode = .center
        container = SKSpriteNode(color: UIColor.black, size: CGSize(width: 200, height: 200))
        container.addChild(label)
        super.init(gameScene: gameScene)
        self.addChild(container)
        self.isUserInteractionEnabled = true
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.gameScene.transitionToScene(sceneType: .Scores)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class ScoresNode: CustomNode {
    var label: SKLabelNode
    var container: SKSpriteNode

    override func layout() {
        container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
    }
    override init(gameScene: GameScene) {
        label = SKLabelNode(text: "Scores Scene")
        label.horizontalAlignmentMode = .center
        label.verticalAlignmentMode = .center
        container = SKSpriteNode(color: UIColor.black, size: CGSize(width: 200, height: 200))
        container.addChild(label)
        super.init(gameScene: gameScene)
        self.addChild(container)
        self.isUserInteractionEnabled = true
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.gameScene.transitionToScene(sceneType: .Menu)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

enum SceneTransition{
    case Menu, Scores
}
enum Animation {
    case Left, Right, None
}

credit: Epic Byte

I have also tried using

viewWillTransitionToSize...

as this handles device orientation changes, I see that didChangeSize... is called multiple times in a device rotation therefore I prefer the viewWillTransitionToSize...

Thanks in advance.

Leon


Solution

  • Go into your Storyboard file, Select your ViewController's view and on the right hand bar, look for the slider image, it should be the 4th one from the left. This is called the attributes inspector. Change Content Mode to Center. This will give you black bars but stop the squishing.