ButtonNode

When we first started developing with SpritKit we quickly felt the need for some higher level abstractions.  One example of this is a button.  As a result we ended up creating a simple ButtonNode class that we could use throughout our game.  This post will present an example of our button node extracted from our Fillip’s Feast game, available in the Apple App Store.

The following features are included in this basic button node implementation.

  • Buttons would use images from textures or named image assets
  • Provide visual feedback when button is selected

The ButtonNode subclassed SKSpriteNode and provides 2 contractor methods.

+(instancetype)buttonWithImageNamed:(NSString*)image forTarget:(id)target andSelector:(SEL)selector;
+(instancetype)buttonWithTexture:(SKTexture*)texture forTarget:(id)target andSelector:(SEL)selector;

The implementation of each of these only varies based on the source of the image to be used for the button.  Here is what we have for the creating the button with a named image.

// Allocates and initializes a new button node from a named image
+(instancetype)buttonWithImageNamed:(NSString*)image forTarget:(id)target andSelector:(SEL)selector {

ButtonNode *button = [[ButtonNode alloc] initWithImageNamed:image forTarget:(id)target andSelector:selector];
return button;

}

// Init button with a named image
-(instancetype)initWithImageNamed:(NSString*)image forTarget:(id)target andSelector:(SEL)selector {

if (self = [super initWithImageNamed:image]) {

self.target = target;
self.selector = selector;
self.userInteractionEnabled = true;
self.color = [SKColor grayColor];

}
return self;

}

With this constructor we can now create our new button node and add it to our scene.  Since this is a subclass of SKSpriteNode you use the normal SKNode position property to set the location of the button within the scene.

-(void)addMyButton {

ButtonNode *myButton = [ButtonNode buttonWithImageNamed:@”myButtonImage” forTarget:self andSelector:@selector(myButtonAction)];
myButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:myButton];

}

Notice in the method above we are setting the target to self (our current scene) and the selector (the method that will be called) as myButtonAction.  In the scene you then need to add the myButtonAction method with the appropriate code to be executed when the button has been pressed and released.

So now we have to address what happens when the button is touched.  There are a few different scenarios we have to deal with. First what happens when the button is touched, then what happens when the user touches the button but then drags out of the button area, and then finally what happens if the user is within the button area and lifts their finger off the button.  The final case is what is commonly referred to a “touch up inside event”.  Lets go through the code for each of these.

First what happens when the user touches the button.  In this case we want to give the user a visual indication that they are touching the button.  For this we will respond to the “touches begin” event.  Here we are responding to this event and if the touch happens to be inside our button we will set the button to the selected state.

// When the button is touched blend the button color with the image to give it a selected appearance
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self.parent];
if (touches.count == 1 && CGRectContainsPoint(self.frame, touchPoint)) {

[self setSelectedState:YES];

}

}

Setting the button to the selected state will simply update the color blend factor so the buttons appearance changes.

// Helper method to set or unset the button selected state
– (void)setSelectedState:(BOOL)selected {

if (selected) {

self.isSelected = YES;
self.colorBlendFactor = 0.7;

} else {

self.isSelected = NO;
self.colorBlendFactor = 0.0;

}

}

So what happens if the user has touched the button but then drags their finger off of the button.  In this case we want to change the state of the button to be not selected.  But, we also need to handle the case of the user then dragging their finger back within the area of the button.  To handle both of these cases we need to handle the touches moved event.

– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self.parent];
if (CGRectContainsPoint(self.frame, touchPoint)) {

[self setSelectedState:YES];

} else {

[self setSelectedState:NO];

}

}

So when in the touches is moved we are checking to see if the touch is inside or outside of our button and setting the Selected state as appropriate.

Finally we need to handle the case where the user has touched the button and then releases it.  For this we need to respond to the touches ended event. If the button selected state is true we will call the activateButton method,

– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

if (self.isSelected) {

[self setSelectedState:NO];
[self activateButton];

}

}

Then finally we need to respond to the activateButton method.  In this case we are going to simply call the selector on the target that was passed in when the button was created.

// When activated the button will selector on the target to invode the buttons action
– (void)activateButton{

if (self.target != nil && [self.target respondsToSelector:self.selector]){

[self.target performSelectorOnMainThread:(self.selector) withObject:self waitUntilDone:NO];

}

}

In future posts we will expand on the button node class adding additional functionality.

A sample app, ButtonNode , with this ButtonNode class can be found on our github page.

You can find me on twitter @mellowcoder

Posted in Coding, Main and tagged , , , , , .