ButtonNode with enable

This is a follow-up to my previous ButtonNode post.  In this update I will be adding additional functionality to the button to allow the button to be enabled and disabled.

For this new functionality I want the ButtonNode to be able to do the following:

  • Maintain a boolean value indicating the current state of the button
  • Prevent the button from being clicked when it is disabled
  • Update the look of the button so that it appears to be disabled
  • Allow the button to toggled between the enabled and disabled state

To start we will simply add a new public property that represents the enable state of the button

@property (assign, nonatomic) BOOL enable;

Defining this property will automatically synthesize the getter and setter methods. But, since we want to change how the button looks and behaves when it is disabled we need to be able to execute code whenever the buttons’s enable property is set. The simplest way to do this is to create our own setter method which will be used instead of the auto-synthised setter.

// Setter for the enable property. Update the button based on the new state
– (void)setEnable:(BOOL)enable {

if (enable) {

self.userInteractionEnabled = YES;
self.alpha = 1.0;

} else {

self.userInteractionEnabled = NO;
self.alpha = 0.5;

}
_enable = enable;

}

In the setter method, when the button is being disabled, we set the userInteractionEnabled to NO and apply an alpha of 0.5 to dim the button. When the button is enable we simply reset the alpha to 1 and re-enable user interaction. Finally we update the underlying _enabled variable to track the state of the button.

The ButtonNode sample app, available on our github (ButtonNode) page, has been updated with this new functionality.

You can find me on twitter @mellowcoder

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

SpriteKit and iOS9

This is a short post discussing a few issues that we encountered with updating our SpritKit based game, Fillips Feast, to work with iOS 9.

For the most part everything just worked when running Fillip’s Feast on iOS 9.  But, we did encounter a few issues.

One of the first things we noticed was that our intro video was not being shown when running under iOS 9.  The video which played fine under prior OS versions was not visible in iOS 9.  But, it was playing so it was not an issue of the video.  For game loading intro we created a loading scene that is presented when the app first starts.  This includes a splash screen, which is an SKSpritNode that displays the first frame of the video and an SKVideoNode used to play the intro video.  We also add a message to the intro view once all the assets have finished loading to let the user know that they can tap the screen to skip the rest of the video.

After a bit of debugging we determined that the issues was related to the zPosition of the video.  In building the intro scene we had not explicitly set the zPosition for the elements.  Simply adding the video node to the scene after adding the splash screen sprite node had been working fine.  Explicitly setting the zPosition for the video node, now allowed the video to be shown in iOS 9.  But, this had an undesirable side effect in iOS 8 and prior.  Now the video was displaying a black screen that flashed before the video started playing.  The solution to this was to test for the availability of an iOS 9 feature and only set the zPosition on iOS 9.  To do this we simply tested to see that [SKCameraNode class] returned true before setting the zPostion.  We also found a few other places in the app where the zPosition needed to be set.

At this point things were looking good, except on iOS 9 the intro video was not playing smoothly.  It would play about a third of the way though, freeze, then jump forward and play the final third of the video.  This was not an issue on devices that where running an older version of iOS.  This problem was isolated to the SKLabelNode we where adding at the bottom of the page to let the user know that could “tap to continue” and skip the rest of the video.  We had actually had similar performance issue trying to use an SKLableNode to display the score you are awarded when you catch a bug.  The fix for this was to swap out the label node for an SKSpritNode with an image for the text message.  The downside of this is it makes it impractical to internationalize this text.

Fillip’s Feast Intro Video

 

fillip feast health dashboard

Game Health Indicator

This post is going to present a simple solution for building a dynamic health indicator for your game.  This is based on the health indicator we included in our Fillip’s Feast game, available in the Apple App Store.

The following are the features we wanted for our health meter.

  • Follow the overall look of the game
  • Provide level indicator based on size
  • Provide visual color indication of health

Step one was to create a sprite in which to display the health level.  For our game the health indicator shows as a bar within a wooden sign.

Health Status Bar

Next we need to create a sprite that can be scaled to represent the players health level and who’s color can be adjusted to give an additional visual indication of level.  This is done by creating sprite node with a color and size.  The initial color does not matter since we will be setting the color based on the players health level.  The width is set to match the length of the level indicator in our banner.  For our banner this is .875 of the full banner width.


// For the reserve indicator create a white sprite node the same size as the health banner
CGSize reserveSize = healthBanner.size;
reserveSize.width = reserveSize.width * 0.875;
self.reserveIndicator = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:reserveSize];

// Set the anchor point for the indicator to be the left side for horizontal and center for vertical
self.reserveIndicator.anchorPoint = CGPointMake(0.0,0.5);

// Posistion the indicator so the left side lines up with the indicator area in the banner
self.reserveIndicator.position = CGPointMake(-reserveSize.width/2,0);


The final setup step requires the creation of a SKCropNode and mask so that health indicator fits within the appropriate portion of the banner window.  For this we use another sprite image.

Health Bar Mask


// We will use a crop node to mask out health indicator so it only shows up in the healt bar area of the banner
SKCropNode *reserveCropNode = [[SKCropNode alloc]init];
reserveCropNode.maskNode = [SKSpriteNode spriteNodeWithTexture:[health textureNamed:@”HealthBar_Mask”]];

// Add the health level indicator as a child of the crop node
[reserveCropNode addChild:self.reserveIndicator];


The final piece is to add a method that will be called to update the health meter whenever the players health level changes.  When the player has full heath the meter will be green.  When he is almost out of health it will be red and the length of the indicator will scale accordingly.


– (void)updateReserveDisplay:(NSInteger)reserve {
self.reserveIndicator.color = [SKColor colorWithRed:((100.0-reserve)/100.0) green:(reserve/100.0) blue:0.0 alpha:1.0];
[self.reserveIndicator setXScale:(reserve/100.0)];
}

Here is how the banner looks with about 70% health.

fillip feast health dashboard

A copy of a sample app, health_level_indicator, that puts all of this together can be found on our github page.

You can find me on twitter @mellowcoder

Launching the News Stream

In anticipation of launching our website, this is officially our first “News” post! To kick things off here are a couple things we’ve been working on.

  • Fat Frog Feast, our first iOS game is coming along nicely! We have been working on coding the menu/UI systems and fly behavioral algorithms. Meanwhile on the art side of things, our look is finalized, the background daytime matt painting is done, and we are  putting together some test sprite sheets for the bugs.
  • Gellini’s Special Gift, our first children’s book is currently going through design refinement. We are ironing out the details of the characters costume to make them unique and iconic. That way they stand out, while still speaking to the classic circus setting of the story. We have a great new set of clown shoe concept paintings.