Search code examples
iosswiftsprite-kitskscene

Create Button in SpriteKit: Swift


I want to create a button in SpriteKit or in an SKScene that sends the view to another view controller.

I tried using the "performSegue with identifier ", however apparently an SKScene doesn't support this. How would I create a button that sends the view to another view with SpriteKit?

This is the code that I've tried using to perform this action.

The line with "HomeButton.prepareForSegueWithIdentifier()" is just an example. It won't actually let me add the "prepareForSegue" part, it doesn't support it <--- What I mean by that is when I go to add it, it is unrecognized.

class GameOverScene: SKScene {

     var HomeButton: SKNode! = nil


    init(size: CGSize, won: Bool) {
        super.init(size: size)

        backgroundColor = SKColor.whiteColor()

        HomeButton = SKSpriteNode(color: SKColor.blueColor(), size: CGSize(width: 100, height: 100))
        HomeButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
        HomeButton.userInteractionEnabled = true
        self.addChild(HomeButton)

        let message = won ? "You Won!" : "You Lose!"





        let label = SKLabelNode(fontNamed: "Title 1")
        label.text = message
        label.fontSize = 40
        label.fontColor = SKColor.blackColor()
        label.position = CGPoint(x: size.width/2, y: size.height/2)
        addChild(label)

        runAction(SKAction.sequence([SKAction.waitForDuration(3.0), SKAction.runBlock() {
            let reveal = SKTransition.flipHorizontalWithDuration(0.5)
            let scene = GameScene(size: size)
            self.view?.presentScene(scene, transition: reveal)
        }
        ]))


    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        for touch: AnyObject in touches {
            let location = touch.locationInNode(self)

            if HomeButton.containsPoint(location) {

                HomeButton.prepareForSegueWithIdentifier()

            }
        }
    }

Note: I've tried using a button, but they don't work in and SKScene.

I'll be on to respond if there is any confusion.


Solution

  • If you need to create a button in SpriteKit, I think this button must have all or some of the available actions to do whatever you want (exactly as UIButton did)

    Here you can find a simple class that build a SpriteKit button, called FTButtonNode:

    class FTButtonNode: SKSpriteNode {
    
        enum FTButtonActionType: Int {
            case TouchUpInside = 1,
            TouchDown, TouchUp
        }
    
        var isEnabled: Bool = true {
            didSet {
                if (disabledTexture != nil) {
                    texture = isEnabled ? defaultTexture : disabledTexture
                }
            }
        }
        var isSelected: Bool = false {
            didSet {
                texture = isSelected ? selectedTexture : defaultTexture
            }
        }
    
        var defaultTexture: SKTexture
        var selectedTexture: SKTexture
        var label: SKLabelNode
    
        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");
    
            super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size())
            userInteractionEnabled = true
    
            //Creating and adding a blank label, centered on the button
            self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center;
            self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center;
            addChild(self.label)
    
            // Adding this node as an empty layer. Without it the touch functions are not being called
            // The reason for this is unknown when this was implemented...?
            let bugFixLayerNode = SKSpriteNode(texture: nil, color: UIColor.clearColor(), size: defaultTexture.size())
            bugFixLayerNode.position = self.position
            addChild(bugFixLayerNode)
    
        }
    
        /**
         * Taking a target object and adding an action that is triggered by a button event.
         */
        func setButtonAction(target: AnyObject, triggerEvent event:FTButtonActionType, action:Selector) {
    
            switch (event) {
            case .TouchUpInside:
                targetTouchUpInside = target
                actionTouchUpInside = action
            case .TouchDown:
                targetTouchDown = target
                actionTouchDown = action
            case .TouchUp:
                targetTouchUp = target
                actionTouchUp = action
            }
    
        }
    
        /*
        New function for setting text. Calling function multiple times does
        not create a ton of new labels, just updates existing label.
        You can set the title, font type and font size with this function
        */
    
        func setButtonLabel(title: NSString, font: String, fontSize: CGFloat) {
            self.label.text = title as String
            self.label.fontSize = fontSize
            self.label.fontName = font
        }
    
        var disabledTexture: SKTexture?
        var actionTouchUpInside: Selector?
        var actionTouchUp: Selector?
        var actionTouchDown: Selector?
        weak var targetTouchUpInside: AnyObject?
        weak var targetTouchUp: AnyObject?
        weak var targetTouchDown: AnyObject?
    
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            if (!isEnabled) {
                return
            }
            isSelected = true
            if (targetTouchDown != nil && targetTouchDown!.respondsToSelector(actionTouchDown!)) {
                UIApplication.sharedApplication().sendAction(actionTouchDown!, to: targetTouchDown, from: self, forEvent: nil)
            }
        }
    
        override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    
            if (!isEnabled) {
                return
            }
    
            let touch: AnyObject! = touches.first
            let touchLocation = touch.locationInNode(parent!)
    
            if (CGRectContainsPoint(frame, touchLocation)) {
                isSelected = true
            } else {
                isSelected = false
            }
    
        }
    
        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
            if (!isEnabled) {
                return
            }
    
            isSelected = false
    
            if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) {
                let touch: AnyObject! = touches.first
                let touchLocation = touch.locationInNode(parent!)
    
                if (CGRectContainsPoint(frame, touchLocation) ) {
                    UIApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, forEvent: nil)
                }
    
            }
    
            if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) {
                UIApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self, forEvent: nil)
            }
        }
    
    }
    

    The source is available in this Gist

    Usage:

    let backTexture: SKTexture! = SKTexture(image:"backBtn.png")
    let backTextureSelected: SKTexture! = SKTexture(image:"backSelBtn.png")  
    let backBtn = FTButtonNode(normalTexture: backTexture, selectedTexture: backTextureSelected, disabledTexture: backTexture,size:backTexture.size())
    backBtn.setButtonAction(self, triggerEvent: .TouchUpInside, action: #selector(GameScene.backBtnTap))
    backBtn.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
    backBtn.zPosition = 1
    backBtn.name = "backBtn"
    self.addChild(backBtn)
    
    func backBtnTap() {
        print("backBtnTap tapped")
        // Here for example you can do:
        let transition = SKTransition.fadeWithDuration(0.5)
        let nextScene = MenuScene(size: self.scene!.size)
        nextScene.scaleMode = .ResizeFill
        self.scene?.view?.presentScene(nextScene, transition: transition)
    }