Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting preferred focus in TVOS on an SKSpriteNode?

Tags:

ios

swift

tvos

Since I'm working in SpriteKit, my buttons are SKSpriteNodes...however I find myself in a situation where I need to set focus in my viewController via overriding preferredFocusedView. Is there a way to downcast an SKSpriteNode to a UIView? If so I haven't been able to figure out yet...any alternative?

        let playButton = SKSpriteNode(imageNamed: "PlayButton")
        playButton.position = CGPoint(x: scene.size.width * 0.25, y: scene.size.height * 0.25)
        playButton.zPosition = Layer.UI.rawValue
        scene.worldNode.addChild(playButton)

override var preferredFocusedView: UIView? {
    get {
        return //playButton how? 
    }
}
like image 562
GarySabo Avatar asked Sep 26 '16 22:09

GarySabo


1 Answers

Focus navigation is only now supported with tvOS 10 and SpriteKit, prior to that you had to do it manually using your own focus system. For that reason preferred focus view is deprecated because it only supports UIViews. You should now use preferred focus environments instead.

First thing you do is in your GameViewController set the preferred focus environment to the currently presented SKScene. This essentially means that your SKScenes will handle the preferred focus instead of GameViewController. In a SpriteKit game the SKScenes should handle the UI such as buttons using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes etc. Therefore you need to pass the preferred focus to the SKScene.

class GameViewController: UIViewController {

      override func viewDidLoad() {
           super.viewDidLoad()

           // default code to present your 1st SKScene.
      }
}

#if os(tvOS)
extension GameViewController {

      /// Tell GameViewController that the currently presented SKScene should always be the preferred focus environment
      override var preferredFocusEnvironments: [UIFocusEnvironment] {
           if let scene = (view as? SKView)?.scene {
               return [scene]
           } 
           return []
      }
 }
 #endif

Your playButton should be a subclass of SKSpriteNode that you will use for all your buttons in your game. Use enums and give them different names/ identifiers to distinguish between them when they are pressed (checkout Apples sample game DemoBots).

 class Button: SKSpriteNode {

      var isFocusable = true // easy way to later turn off focus for your buttons e.g. when overlaying menus etc.

      /// Can become focused
      override var canBecomeFocused: Bool {
          return isFocusable
      }

      /// Did update focus
      override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

          if context.previouslyFocusedItem === self {
              // SKAction to reset focus animation for unfocused button
          }

          if context.nextFocusedItem === self {
               // SKAction to run focus animation for focused button
          }
      }
 }

Than in your SKScenes you can set the focus environment to your playButton or other UI.

e.g Start Scene

class StartScene: SKScene {
   ....
}

#if os(tvOS)
extension StartScene {
      override var preferredFocusEnvironments: [UIFocusEnvironment] {
          return [playButton]
     }
}
#endif

e.g GameScene (e.g transfer focus to game menu when needed)

class GameScene: SKScene {
   ....
}

#if os(tvOS)
extension GameScene {

      override var preferredFocusEnvironments: [UIFocusEnvironment] {
         if isGameMenuShowing { // add some check like this
             return [gameMenuNode]
         }
         return []
     }    
}
#endif

You will also have to tell your GameViewController to update its focus environment when you transition between SKScenes (e.g StartScene -> GameScene). This is especially important if you use SKTransitions, it took me a while to figure this out. If you use SKTransitions than the old and new scene are active during the transition, therefore the GameViewController will use the old scenes preferred focus environments instead of the new one which means the new scene will not focus correctly.

I do it like this every time I transition between scenes. You will have to use a slight delay or it will not work correctly.

 ...
 view?.presentScene(newScene, transition: ...)

 #if os(tvOS)
    newScene.run(SKAction.wait(forDuration: 0.1)) { // wont work without delay
               newScene.view?.window?.rootViewController?.setNeedsFocusUpdate()
            newScene.view?.window?.rootViewController?.updateFocusIfNeeded()
        }
    #endif

You should read this article

https://medium.com/folded-plane/tvos-10-getting-started-with-spritekit-and-focus-engine-53d8ef3b34f3#.x5zty39pc

and watch the 2016 apple keynote called "Whats New in SpriteKit" where they talk about it half way through.

Hope this helps

like image 187
crashoverride777 Avatar answered Sep 21 '22 13:09

crashoverride777