2 Replies
      Latest reply on Oct 2, 2018 5:36 PM by igorland
      igorland Level 1 Level 1 (0 points)

        I am creating an SKScene with two buttons.

         

         

        import UIKit
            import SpriteKit
        
        
            class IntroScene: SKScene {
              override func didMove(to view: SKView) {
                // BUTTON PLAY GAME
                let btnPlay = BtnPlay(normalTexture: defaultBtnTexture,
                                      selectedTexture: selectedBtnTexture,
                                      disabledTexture: nil)
                btnPlay.size = CGSize(width: 250, height: 75)
                btnPlay.position = CGPoint(x: frame.midX,y: frame.height/3*1)
                btnPlay.name = "btnPlay"
                btnPlay.setButtonAction(target: self, triggerEvent: .TouchUpInside, action: #selector(IntroScene.goToGameScene))
                print("BUTTON PLAY FRAME 1 ",btnPlay.frame)
                addChild(btnPlay)
        
        
                // BUTTON 'FIND PLAYERS'
                let btnFindPlayers = BtnFindPlayers(normalTexture: defaultBtnTexture,
                                                    selectedTexture: selectedBtnTexture,
                                                    disabledTexture: nil)
               
                btnFindPlayers.size = CGSize(width: 250, height: 75)
                btnFindPlayers.position = CGPoint(x: self.frame.midX,y: self.frame.height/3*2)
                btnFindPlayers.name = "btnFindPlayers"
                btnFindPlayers.setButtonAction(target: self, triggerEvent: .TouchUpInside, action: #selector(IntroScene.goToConnectVC))
                print("BUTTON FIND FRAME 1 ",btnFindPlayers.frame)
                addChild(btnFindPlayers)
              }
           
              @objc func goToGameScene() {
                print("Go to Game Scene")
              }
           
              @objc func goToConnectVC() {
                print("Go to Connect VC")
              }
            }

         

         

        This is the custom class for one of the buttons. The other class uses similar methods:

         

         

         import SpriteKit
        
        
            class BtnPlay: SKSpriteNode {
              enum BtnPlayType: Int {
                case TouchUpInside = 1,TouchDown, TouchUp
              }
           
              var disabledTexture: SKTexture?
              var defaultTexture: SKTexture
              var selectedTexture: SKTexture
              var label: SKLabelNode
           
              var isEnabled: Bool = true {
                didSet {
                    if disabledTexture != nil {
                        texture = isEnabled ? defaultTexture : disabledTexture
                    }
                }
              }
           
              var isSelected: Bool = false {
                didSet {
                    texture = isSelected ? selectedTexture : defaultTexture
                }
              }
           
            required init(coder: NSCoder) {
                fatalError("NSCoding not supported")
            }
           
            init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) {
                self.defaultTexture = defaultTexture
                self.selectedTexture = selectedTexture
                self.disabledTexture = disabledTexture
                self.label = SKLabelNode(fontNamed: "Helvetica");
                self.label.text = "PLAY"
               
                super.init(texture: defaultTexture, color: UIColor.white, size: defaultTexture.size())
                isUserInteractionEnabled = true
               
                self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.center;
                self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center;
                addChild(self.label)
               
                let bugFixLayerNode = SKSpriteNode(texture: nil, color: UIColor.clear, size: defaultTexture.size())
                bugFixLayerNode.position = self.position
                addChild(bugFixLayerNode)
            }
           
            var actionTouchUpInside: Selector?
            var actionTouchUp: Selector?
            var actionTouchDown: Selector?
            weak var targetTouchUpInside: AnyObject?
            weak var targetTouchUp: AnyObject?
            weak var targetTouchDown: AnyObject?
           
            func setButtonAction(target: AnyObject, triggerEvent event:BtnPlayType, action:Selector) {
                switch (event) {
                case .TouchUpInside:
                    targetTouchUpInside = target
                    actionTouchUpInside = action
                case .TouchDown:
                    targetTouchDown = target
                    actionTouchDown = action
                case .TouchUp:
                    targetTouchUp = target
                    actionTouchUp = action
                }
            }
           
            func setButtonLabel(title: NSString, font: String, fontSize: CGFloat) {
                self.label.text = title as String
                self.label.fontSize = fontSize
                self.label.fontName = font
            }
           
            override func touchesBegan(_ touches: Set, with event: UIEvent?) {
                if !isEnabled { return }
               
                isSelected = true
               
                if (targetTouchDown != nil && targetTouchDown!.responds(to: actionTouchDown)) {
                    UIApplication.shared.sendAction(actionTouchDown!, to: targetTouchDown, from: self, for: nil)
                }
            }
        
        
            override func touchesMoved(_ touches: Set, with event: UIEvent?) {
               
                if !isEnabled { return }
               
                let touch: AnyObject! = touches.first
                let touchLocation = touch.location(in: parent!)
               
                if (frame.contains(touchLocation)) {
                    isSelected = true
                } else {
                    isSelected = false
                }
               
            }
           
            override func touchesEnded(_ touches: Set, with event: UIEvent?) {
                if !isEnabled { return }
               
                isSelected = false
               
                if (targetTouchUpInside != nil && targetTouchUpInside!.responds(to: actionTouchUpInside!)) {
                    let touch: AnyObject! = touches.first
                    let touchLocation = touch.location(in: parent!)
                   
                    if (frame.contains(touchLocation)) {
                        UIApplication.shared.sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, for: nil)
                    }
        
        
                    print("BUTTON PLAY FRAME ",frame)
                    print("BUTTON PLAY TOUCHES ",touchLocation)
                }
               
                if (targetTouchUp != nil && targetTouchUp!.responds(to: actionTouchUp!)) {
                    UIApplication.shared.sendAction(actionTouchUp!, to: targetTouchUp, from: self, for: nil)
                }
              }
            }

         

        The scene and the buttons are created just fine. Button "Find Players" is created on top. The problem is that when I tap on the lower button ("Play"), nothing happens. The culprit is tapping on the screen. Whenever I tap somewhere on the screen, except for the lowest part of it, the upper button gets selected. So, if I tap on the lower button, I still see on the console:

         

         

        BUTTON FIND FRAME  (387.0, 474.5, 250.0, 75.0)
            BUTTON FIND TOUCHES  (409.139404296875, 563.622131347656)

         

        Looks like the frame of the upper button is much greater than the button itself and superimposes the lower button not letting me tap on it.

         

        Have no idea why this is happening.

         

        I would greatly appreciate if someone can help.

        • Re: Adding two buttons on SKScene: no action for the lower button
          QuinceyMorris Level 8 Level 8 (6,010 points)

          You should carefully read the section titled "The Hit-Testing Order Is the Reverse of Drawing Order" in this document:

           

          https://developer.apple.com/documentation/spritekit/sknode

           

          Basically, a node that handles touches needs to reject the touch if it's not inside the node's bounds (or whatever "sensitive" area it wants). Your code assumes that the touch won't arrive if it's not inside the node, but that's not true for Sprite Kit event handling.

           

          It may be simpler to NOT enable interaction on the buttons, but leave it enabled for the scene. Then, use the technique described in the last paragraph of the document section mentioned above to find the correct button directly.

            • Re: Adding two buttons on SKScene: no action for the lower button
              igorland Level 1 Level 1 (0 points)

              Thank you, QuinceyMorris.

               

              I did not quite get your first comment, so I implemented what you suggested in your last sentence in the IntroScene class as follows:

               

              override func touchesBegan(_ touches: Set, with event: UIEvent?)
              {
                      let touch = touches.first
                      let positionInScene = touch!.location(in: self)
                      let allNodes = nodes(at: positionInScene)
                      for node in allNodes
                      {
                          let nodePositionConverted = self.convert(node.position, from: node)
                          let nodeFrameConverted = CGRect(origin: CGPoint(x:nodePositionConverted.x-node.frame.maxX,y:nodePositionConverted.y-node.frame.maxY),size:node.frame.size)
                          if nodeFrameConverted.contains(positionInScene)
                          {
                              if(node == btnPlay)
                              {
                                  btnPlay.isSelected = true
                              }
                              else if(node == btnFindPlayers)
                              {
                                  btnFindPlayers.isSelected = true
                              }
                          }
                      }
                  }

               

              Cheers.