Setting Up Buttons in Skscene

Setting up buttons in SKScene

you could use a SKSpriteNode as your button, and then when the user touches, check if that was the node touched. Use the SKSpriteNode's name property to identify the node:

//fire button
- (SKSpriteNode *)fireButtonNode
{
SKSpriteNode *fireNode = [SKSpriteNode spriteNodeWithImageNamed:@"fireButton.png"];
fireNode.position = CGPointMake(fireButtonX,fireButtonY);
fireNode.name = @"fireButtonNode";//how the node is identified later
fireNode.zPosition = 1.0;
return fireNode;
}

Add node to your scene:

[self addChild: [self fireButtonNode]];

Handle touches:

//handle touch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];

//if fire button touched, bring the rain
if ([node.name isEqualToString:@"fireButtonNode"]) {
//do whatever...
}
}

Create Button in SpriteKit: Swift

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)
}

Creation of buttons for SpriteKit

I totally agree with @Whirlwind here, create a separate class for your button that handles the work for you. I do not think the advice from @ElTomato is the right advice. If you create one image with buttons included you have no flexibility on placement, size, look and button state for those buttons.

Here is a very simple button class that is a subclass of SKSpriteNode. It uses delegation to send information back to the parent (such as which button has been pushed), and gives you a simple state change (gets smaller when you click it, back to normal size when released)

import Foundation
import SpriteKit

protocol ButtonDelegate: class {
func buttonClicked(sender: Button)
}

class Button: SKSpriteNode {

//weak so that you don't create a strong circular reference with the parent
weak var delegate: ButtonDelegate!

override init(texture: SKTexture?, color: SKColor, size: CGSize) {

super.init(texture: texture, color: color, size: size)

setup()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)

setup()
}

func setup() {
isUserInteractionEnabled = true
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(0.9)
self.delegate.buttonClicked(sender: self)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(1.0)
}
}

This button can be instantiated 2 ways. You can create an instance of it in the Scene editor, or create an instance in code.

creating the button in Scene editor

class MenuScene: SKScene, ButtonDelegate {

private var button = Button()

override func didMove(to view: SKView) {

if let button = self.childNode(withName: "button") as? Button {
self.button = button
button.delegate = self
}

let button2 = Button(texture: nil, color: .magenta, size: CGSize(width: 200, height: 100))
button2.name = "button2"
button2.position = CGPoint(x: 0, y: 300)
button2.delegate = self
addChild(button2)
}
}

func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}

You have to remember to make the scene conform to the delegate

class MenuScene: SKScene, ButtonDelegate

func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}

SpriteKit creating a button issues

Your main problem is you put all of your code inside the didMove function. You put the touchesBegan function inside the didMove function. When the didMove function finishes, touchesBegan goes away so none of your touches are going to be handled in the game.

You also declared the button and the player as local variables inside the didMove function.

override func didMove(to view: SKView) {
let button = SKSpriteNode(imageNamed: "playerOBJ")
let player = SKSpriteNode(imageNamed: "playerOBJ")
// Rest of function omitted
}

When the didMove function finishes, the button and player go away too. You also have the same image name for the player and button.

The fix is to make the button and player variables properties of the GameScene class. Move touchesBegan out of the didMove function too.

class GameScene: SKScene {
// Declare button and player here.
var button = SKSpriteNode()
var player = SKSpriteNode()

override func didMove(to view: SKView) {
// Initialize button, player, and everything else here.
}

func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Code in touchesBegan func goes here.
}
}

How create movement buttons left/right?

Give the buttons a unique name like:

leftMove.name = "Left"
rightMove.name = "Right"

Implement the logic in touchesBegan:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)

if (node.name == "Left") {
// Implement your logic for left button touch here:
player.position = CGPoint(x:player.position.x-1, y:player.position.y)
} else if (node.name == "Right") {
// Implement your logic for right button touch here:
player.position = CGPoint(x:player.position.x+1, y:player.position.y)
}
}
}

Changing Scenes In Spritekit, Detecting Touches on Buttons

The touches parameter inside of your touchesBegan(_: with:) method is of type Set<UITouch>, i.e. a set of touches. As stated in the Apple Documentation for Sets, a set is used when the order of the items in the collection are not a concern. Therefore, you cannot assume that the first item in touches is the first touch in the set. Knowing this, I recommend refactoring your code inside of HomeScene.swift to the following:

import SpriteKit

class HomeScene: SKScene {

private var playButton: SKSpriteNode?

override func sceneDidLoad() {
self.playButton = self.childNode(withName: "//starButton") as? SKSpriteNode
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}

func touchDown(atPoint pos: CGPoint) {
let nodes = self.nodes(at: pos)

if let button = playButton {
if nodes.contains(button) {
let gameScene = SKScene(fileNamed: "GameScene")
self.view?.presentScene(gameScene)
}
}
}

The touchDown(atPoint:) method does all the heavy lifting and touchesBegan(_: with:) is freed up to listen for touch events. Another thing to note when calling childNode(withName:):

  • Adding / before the name of the file starts searching for nodes beginning at the root of your file hierarchy.
  • Adding // before the name of the file starts searching for nodes beginning at the root of your file hierarchy, then recursively down the hierarchy.
  • Adding * before the name of the file acts as a character wildcard and allows you to search for file names that are wholly or partially unknown.

Correct way to create button in Sprite Kit?

I found this online a while ago, courtesy of a member who goes by the name of Graf, this works nicely for my problems.

#import <SpriteKit/SpriteKit.h>
@interface SKBButtonNode : SKSpriteNode

@property (nonatomic, readonly) SEL actionTouchUpInside;
@property (nonatomic, readonly) SEL actionTouchDown;
@property (nonatomic, readonly) SEL actionTouchUp;
@property (nonatomic, readonly, weak) id targetTouchUpInside;
@property (nonatomic, readonly, weak) id targetTouchDown;
@property (nonatomic, readonly, weak) id targetTouchUp;

@property (nonatomic) BOOL isEnabled;
@property (nonatomic) BOOL isSelected;
@property (nonatomic, readonly, strong) SKLabelNode *title;
@property (nonatomic, readwrite, strong) SKTexture *normalTexture;
@property (nonatomic, readwrite, strong) SKTexture *selectedTexture;
@property (nonatomic, readwrite, strong) SKTexture *disabledTexture;

- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected;
- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected disabled:(SKTexture *)disabled; // Designated Initializer

- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected;
- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected disabled:(NSString *)disabled;

/** Sets the target-action pair, that is called when the Button is tapped.
"target" won't be retained.
*/
- (void)setTouchUpInsideTarget:(id)target action:(SEL)action;
- (void)setTouchDownTarget:(id)target action:(SEL)action;
- (void)setTouchUpTarget:(id)target action:(SEL)action;

@end

And the implementation

//
//
// Courtesy of Graf on Stack Overflow
//
//
//
#import "SKBButtonNode.h"



@implementation SKBButtonNode

#pragma mark Texture Initializer

/**
* Override the super-classes designated initializer, to get a properly set SKButton in every case
*/
- (instancetype)initWithTexture:(SKTexture *)texture color:(UIColor *)color size:(CGSize)size {
return [self initWithTextureNormal:texture selected:nil disabled:nil];
}

- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected {
return [self initWithTextureNormal:normal selected:selected disabled:nil];
}

/**
* This is the designated Initializer
*/
- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected disabled:(SKTexture *)disabled {
self = [super initWithTexture:normal color:[UIColor whiteColor] size:normal.size];
if (self) {
[self setNormalTexture:normal];
[self setSelectedTexture:selected];
[self setDisabledTexture:disabled];
[self setIsEnabled:YES];
[self setIsSelected:NO];

_title = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
[_title setVerticalAlignmentMode:SKLabelVerticalAlignmentModeCenter];
[_title setHorizontalAlignmentMode:SKLabelHorizontalAlignmentModeCenter];

[self addChild:_title];
[self setUserInteractionEnabled:YES];
}
return self;
}

#pragma mark Image Initializer

- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected {
return [self initWithImageNamedNormal:normal selected:selected disabled:nil];
}

- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected disabled:(NSString *)disabled {
SKTexture *textureNormal = nil;
if (normal) {
textureNormal = [SKTexture textureWithImageNamed:normal];
}

SKTexture *textureSelected = nil;
if (selected) {
textureSelected = [SKTexture textureWithImageNamed:selected];
}

SKTexture *textureDisabled = nil;
if (disabled) {
textureDisabled = [SKTexture textureWithImageNamed:disabled];
}

return [self initWithTextureNormal:textureNormal selected:textureSelected disabled:textureDisabled];
}




#pragma -
#pragma mark Setting Target-Action pairs

- (void)setTouchUpInsideTarget:(id)target action:(SEL)action {
_targetTouchUpInside = target;
_actionTouchUpInside = action;
}

- (void)setTouchDownTarget:(id)target action:(SEL)action {
_targetTouchDown = target;
_actionTouchDown = action;
}

- (void)setTouchUpTarget:(id)target action:(SEL)action {
_targetTouchUp = target;
_actionTouchUp = action;
}

#pragma -
#pragma mark Setter overrides

- (void)setIsEnabled:(BOOL)isEnabled {
_isEnabled = isEnabled;
if ([self disabledTexture]) {
if (!_isEnabled) {
[self setTexture:_disabledTexture];
} else {
[self setTexture:_normalTexture];
}
}
}

- (void)setIsSelected:(BOOL)isSelected {
_isSelected = isSelected;
if ([self selectedTexture] && [self isEnabled]) {
if (_isSelected) {
[self setTexture:_selectedTexture];
} else {
[self setTexture:_normalTexture];
}
}
}

#pragma -
#pragma mark Touch Handling

/**
* This method only occurs, if the touch was inside this node. Furthermore if
* the Button is enabled, the texture should change to "selectedTexture".
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self isEnabled]) {
if (_actionTouchDown){
[self.parent performSelectorOnMainThread:_actionTouchDown withObject:_targetTouchDown waitUntilDone:YES];
[self setIsSelected:YES];
}
}
}

/**
* If the Button is enabled: This method looks, where the touch was moved to.
* If the touch moves outside of the button, the isSelected property is restored
* to NO and the texture changes to "normalTexture".
*/
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self isEnabled]) {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self.parent];

if (CGRectContainsPoint(self.frame, touchPoint)) {
[self setIsSelected:YES];
} else {
[self setIsSelected:NO];
}
}
}

/**
* If the Button is enabled AND the touch ended in the buttons frame, the
* selector of the target is run.
*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self.parent];

if ([self isEnabled] && CGRectContainsPoint(self.frame, touchPoint)) {
if (_actionTouchUpInside){
[self.parent performSelectorOnMainThread:_actionTouchUpInside withObject:_targetTouchUpInside waitUntilDone:YES];
}
}
[self setIsSelected:NO];
if (_actionTouchUp){
[self.parent performSelectorOnMainThread:_actionTouchUp withObject:_targetTouchUp waitUntilDone:YES];
}
}
@end


Related Topics



Leave a reply



Submit