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... }}
I've made my own Button-Class that I'm working with.SKButton.h:
#import <SpriteKit/SpriteKit.h>@interface SKButton : 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;- (id)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected;- (id)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected disabled:(SKTexture *)disabled; // Designated Initializer- (id)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected;- (id)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
SKButton.m:
#import "SKButton.h"#import <objc/message.h>@implementation SKButton#pragma mark Texture Initializer/** * Override the super-classes designated initializer, to get a properly set SKButton in every case */- (id)initWithTexture:(SKTexture *)texture color:(UIColor *)color size:(CGSize)size { return [self initWithTextureNormal:texture selected:nil disabled:nil];}- (id)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected { return [self initWithTextureNormal:normal selected:selected disabled:nil];}/** * This is the designated Initializer */- (id)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- (id)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected { return [self initWithImageNamedNormal:normal selected:selected disabled:nil];}- (id)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]) { objc_msgSend(_targetTouchDown, _actionTouchDown); [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)) { objc_msgSend(_targetTouchUpInside, _actionTouchUpInside); } [self setIsSelected:NO]; objc_msgSend(_targetTouchUp, _actionTouchUp);}
An example: To initialize a button, you write the following lines:
SKButton *backButton = [[SKButton alloc] initWithImageNamedNormal:@"buttonNormal" selected:@"buttonSelected"]; [backButton setPosition:CGPointMake(100, 100)]; [backButton.title setText:@"Button"]; [backButton.title setFontName:@"Chalkduster"]; [backButton.title setFontSize:20.0]; [backButton setTouchUpInsideTarget:self action:@selector(buttonAction)]; [self addChild:backButton];
Furthermore you need the 'buttonAction' method in your class.* No warranty that this class is working right in every case. I'm still quite new to objective-c. *
If you think having to do this is annoying and pointless you can disable the check in the build settings by setting 'Enable strict checking of objc_msgSend Calls'
to 'No
'
For people writing their games in Swift!I have rewritten the essential parts of Graf's solution to a swift class. Hope it helps:
import Foundationimport SpriteKitclass 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 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 super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size()) userInteractionEnabled = true // 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: nil, 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 } } 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: NSSet!, withEvent event: UIEvent!) { let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) 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: NSSet!, withEvent event: UIEvent!) { if (!isEnabled) { return } let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (CGRectContainsPoint(frame, touchLocation)) { isSelected = true } else { isSelected = false } } override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) { if (!isEnabled) { return } isSelected = false if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) { let touch: AnyObject! = touches.anyObject() 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) } }}