Buttons in Sprite Kit using Swift
For all the helpful things that Apple added to Sprite Kit, one of the most
glaring omissions is that most basic of user interface elements, the button. Not
really sure why that is, but fortunately it’s pretty easy to create something
that “just works.” The gist of the solution is to create a subclass of the
SKNode
class. Since each node in Sprite Kit can contain any number
of other nodes, we just have to create a button container which holds two
sprites: one for the default button state and one for the “active” button
state.
import SpriteKit
class GGButton: SKNode {
var defaultButton: SKSpriteNode
var activeButton: SKSpriteNode
var action: () -> Void
init(defaultButtonImage: String, activeButtonImage: String, buttonAction: () -> Void) {
defaultButton = SKSpriteNode(imageNamed: defaultButtonImage)
activeButton = SKSpriteNode(imageNamed: activeButtonImage)
activeButton.hidden = true
action = buttonAction
super.init()
userInteractionEnabled = true
addChild(defaultButton)
addChild(activeButton)
}
/**
Required so XCode doesn't throw warnings
*/
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here we create that subclass of SKNode
and give it three
properties: the default button image, the active button image, and the action
that the button should take if pressed. Passing in two strings to the
constructor handles initializing the button images, and we also store a
reference to the button action function. Note of course that we hide the
“active” button by default, waiting for user interaction to reveal it.
After the call to super.init()
, the button container has access to
methods inherited from SKNode
. We then set the
userInteractionEnabled
property to true, which lets this node
respond to input, and also add both buttons as children so they’ll be drawn to
the screen.
Now let’s deal with user input. We want to handle three cases: user touches the
button, user moves their finger around, user lifts their finger off the button.
Fortunately the user interaction methods provided by SKNode
give us
exactly that, if you add the following three methods to the button class
definition.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
activeButton.hidden = false
defaultButton.hidden = true
}
This first method is pretty obvious: if a user starts touching the button, show the “active” state.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touch: UITouch = touches.allObjects[0] as UITouch
var location: CGPoint = touch.locationInNode(self)
if defaultButton.containsPoint(location) {
activeButton.hidden = false
defaultButton.hidden = true
} else {
activeButton.hidden = true
defaultButton.hidden = false
}
}
This next method is a bit more complex. We have to determine if a user moved their finger on or off the button, so as to show the appropriate highlight state. Fortunately, SKNode
-derived objects have a method containsPoint()
, which lets us do some easy collision detection.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
var touch: UITouch = touches.allObjects[0] as UITouch
var location: CGPoint = touch.locationInNode(self)
if defaultButton.containsPoint(location) {
action()
}
activeButton.hidden = true
defaultButton.hidden = false
}
Finally, we re-use the containsPoint()
method described earlier in order to determine if the button was actually tapped. If it was, we call the “action” function that was provided to the constructor. Then we set the highlighted button state back to hidden, ready to be shown again on the next tap. Put it all together, and here’s how you’d use the button in a game:
var button: GGButton = GGButton(defaultButtonImage: "button", activeButtonImage: "button_active", buttonAction: goToGameScene)
button.position = CGPointMake(self.frame.width / 2, self.frame.height / 2)
addChild(button)
I hope this was helpful. Feel free to ask questions in the comments section, and I’ll do my best to answer.
Comments
falconlover wrote on :
Hello, I am having an issue with goToGameScene and what it needs to be. I have tried a literal like UIButton uses (i.e. "btnClick:") but get an error. I guess I am just not understanding the syntax of what:"() -> Void" is. Can you clarify? Thank you.
EW Parris wrote on :
This is a great basic button class. This helped me to create my own family of button classes in swift. Thanks for the starting point!
TPot wrote on :
Thanks. Just tried it and it works nicely - now expanding on it. @falconlover: ()->void is saying the argument is a function with no arguments and returns nothing. ie. you'd define "goToGameScene" as a function somewhere in your class.
André wrote on :
Thanks a lot, very good tutorial!
Jon wrote on :
@TPot Please explain why this works. I too do not understand. I just want my button to briefly change images following the tap. I don't know how to implement the goToGameScene. I don't know where it must go or what code must go underneath it
Moon wrote on :
When i create a function in the class as TPot suggested, i cannot pass this through as a parameter