Create Button in SpriteKit: Swift
Solution 1
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)
}
Solution 2
I have translated Alessandro Ornano’s answer to Swift 3.1:
import SpriteKit
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.white, size: defaultTexture.size())
isUserInteractionEnabled = 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.clear, 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>, 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<UITouch>, 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<UITouch>, 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)
}
}
if (targetTouchUp != nil && targetTouchUp!.responds(to: actionTouchUp!)) {
UIApplication.shared.sendAction(actionTouchUp!, to: targetTouchUp, from: self, for: nil)
}
}
}
Usage:
@objc func buttonTap() {
print("Button pressed")
}
override func didMove(to view: SKView)
{
backgroundColor = SKColor.white
let buttonTexture: SKTexture! = SKTexture(imageNamed: "button")
let buttonTextureSelected: SKTexture! = SKTexture(imageNamed: "buttonSelected.png")
let button = FTButtonNode(normalTexture: buttonTexture, selectedTexture: buttonTextureSelected, disabledTexture: buttonTexture)
button.setButtonAction(target: self, triggerEvent: .TouchUpInside, action: #selector(GameScene.buttonTap))
button.setButtonLabel(title: "Button", font: "Arial", fontSize: 12)
button.position = CGPoint(x: self.frame.midX,y: self.frame.midY)
button.zPosition = 1
button.name = "Button"
self.addChild(button)
}
I have created two .png:
Solution 3
The simplest solution, but possibly not of the greatest quality, is to use a SpriteNode containing an image and name it. Later, using that scene you can easily program it to transfer the user to the next scene when tapped:
class GameScene: SKScene {
let button = SKSpriteNode(imageNamed: "yourImgName")
override func didMoveToView(view: SKView) {
button.name = "btn"
button.size.height = 100
button.size.width = 100
button.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) + 50)
self.addChild(button)
//Adjust button properties (above) as needed
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name {
if name == "btn" {
let yourNextScene = YourNextScene(fileNamed: "YourNextScene")
self.view?.presentScene(yourNextScene!)
}
}
}
}
Don't forget to replace "YourNextScene" with the actual name of your next scene.
Nick Griffith
Updated on June 06, 2022Comments
-
Nick Griffith almost 2 years
I want to create a button in
SpriteKit
or in anSKScene
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 withSpriteKit
?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.
-
Nick Griffith almost 8 yearsCould I do it to where it sends it to a viewController, rather than another SKScene?
-
Alessandro Ornano almost 8 yearsSure, my answer its just an example, if you dont know how look this link stackoverflow.com/a/30041939/1894067