Easy To Create Buttons with Cocos2D


Those of you who use cocos2d a lot might understand why I created this class as some point. Hopefully it may save some of you those nasty 5 line blobs that you normally need to create a simple button. Usage is simple, just do:

[self addChild:[Button buttonWithText:@"back" atPosition:ccp(80, 50) target:self selector:@selector(back:)]];
[self addChild:[Button buttonWithImage:@"openFeint.png" atPosition:ccp(400, 50) target:self selector:@selector(openOpenFeint:)]];

You’ll need to create your own button.png and button_p.png (the second one is the image shown when you are touching the button). Also you’ll need to choose your own font. Here is the code…

//
//  Button.h
//  StickWars - Siege
//
//  Created by EricH on 8/3/09.
//
 
@interface Button : Menu {
}
+ (id)buttonWithText:(NSString*)text atPosition:(CGPoint)position target:(id)target selector:(SEL)selector;
+ (id)buttonWithImage:(NSString*)file atPosition:(CGPoint)position target:(id)target selector:(SEL)selector;
@end
 
@interface ButtonItem : MenuItem {
	Sprite *back;
	Sprite *backPressed;
}
+ (id)buttonWithText:(NSString*)text target:(id)target selector:(SEL)selector;
+ (id)buttonWithImage:(NSString*)file target:(id)target selector:(SEL)selector;
- (id)initWithText:(NSString*)text target:(id)target selector:(SEL)selector;
- (id)initWithImage:(NSString*)file target:(id)target selector:(SEL)selector;
@end
//
//  Button.m
//  StickWars - Siege
//
//  Created by EricH on 8/3/09.
//
 
#import "Button.h"
 
 
@implementation Button
+ (id)buttonWithText:(NSString*)text atPosition:(CGPoint)position target:(id)target selector:(SEL)selector {
	Menu *menu = [Menu menuWithItems:[ButtonItem buttonWithText:text target:target selector:selector], nil];
	menu.position = position;
	return menu;
}
 
+ (id)buttonWithImage:(NSString*)file atPosition:(CGPoint)position target:(id)target selector:(SEL)selector {
	Menu *menu = [Menu menuWithItems:[ButtonItem buttonWithImage:file target:target selector:selector], nil];
	menu.position = position;
	return menu;
}
@end
 
@implementation ButtonItem
+ (id)buttonWithText:(NSString*)text target:(id)target selector:(SEL)selector {
	return [[[self alloc] initWithText:text target:target selector:selector] autorelease];
}
 
+ (id)buttonWithImage:(NSString*)file target:(id)target selector:(SEL)selector {
	return [[[self alloc] initWithImage:file target:target selector:selector] autorelease];
}
 
- (id)initWithText:(NSString*)text target:(id)target selector:(SEL)selector {
	if(self = [super initWithTarget:target selector:selector]) {
		back = [[Sprite spriteWithFile:@"button.png"] retain];
		back.anchorPoint = ccp(0,0);
		backPressed = [[Sprite spriteWithFile:@"button_p.png"] retain];
		backPressed.anchorPoint = ccp(0,0);
		[self addChild:back];
 
		self.contentSize = back.contentSize;
 
		Label* textLabel = [Label labelWithString:text fontName:@"take_out_the_garbage" fontSize:22];
		textLabel.position = ccp(self.contentSize.width / 2, self.contentSize.height / 2);
		textLabel.anchorPoint = ccp(0.5, 0.3);
		[self addChild:textLabel z:1];
	}
	return self;
}
 
- (id)initWithImage:(NSString*)file target:(id)target selector:(SEL)selector {
	if(self = [super initWithTarget:target selector:selector]) {
 
		back = [[Sprite spriteWithFile:@"button.png"] retain];
		back.anchorPoint = ccp(0,0);
		backPressed = [[Sprite spriteWithFile:@"button_p.png"] retain];
		backPressed.anchorPoint = ccp(0,0);
		[self addChild:back];
 
		self.contentSize = back.contentSize;
 
		Sprite* image = [Sprite spriteWithFile:file];
		[self addChild:image z:1];
		image.position = ccp(self.contentSize.width / 2, self.contentSize.height / 2);
	}
	return self;
}
 
-(void) selected {
	[self removeChild:back cleanup:NO];
	[self addChild:backPressed];
	[super selected];
}
 
-(void) unselected {
	[self removeChild:backPressed cleanup:NO];
	[self addChild:back];
	[super unselected];
}
 
// this prevents double taps
- (void)activate {
	[super activate];
	[self setIsEnabled:NO];
	[self schedule:@selector(resetButton:) interval:0.5];
}
 
- (void)resetButton:(ccTime)dt {
	[self unschedule:@selector(resetButton:)];
	[self setIsEnabled:YES];
}
 
- (void)dealloc {
	[back release];
	[backPressed release];
	[super dealloc];
}
 
@end
  1. #1 by Thom on October 19th, 2009

    Hello,
    I’ve tried your class and my app breaks at if(self = [super initWithTarget:target selector:selector]) in initWithImage.
    I stepped though and it seems that my target has something wrong.
    I add the buttonitem in a class which derived from Layer. Do I have to add sth special to this class?
    Thanks
    Thom

    • #2 by Eric on October 19th, 2009

      Look at Button.h, these lines in particular:

      @interface Button : Menu {
      @interface ButtonItem : MenuItem {

      You need to subclass the Menu and MenuItem classes, not Layer.

      • #3 by Thom on October 19th, 2009

        Yeah I know that but the button you had them to a layer no? when you do self addchild

    • #4 by Eric on October 19th, 2009

      Oh, I see what you mean now. All you do is add the Button class to your class which derived from layer, and make sure you implement the callback method in that class.

      So for example, the first line of code:
      [self addChild:[Button buttonWithText:@"back" atPosition:ccp(80, 50) target:self selector:@selector(back:)]];

      put that in your gameLayer, and make sure to have a method in that layer

      - (void)back:(id)sender {

      }

      • #5 by Thom on October 19th, 2009

        Oh Brillant it’s working now
        Thanks a lot for the help

  2. #6 by Avery on October 27th, 2009

    This seems like a very helpful class, but I can’t seem to get it to work. I believe that I am having the same problem, the person above had. Unfortanetely, I am still a little hairy on what exactly to do based on what you told him. Should I just extract the specific functionality code and add it to my existing layer class and implementation?

    Thanks for this page.

    • #7 by Eric on October 28th, 2009

      Look at the comments at the top of each ‘block’ of code…just create a Button.h and Button.m file, drop all the code in them from the correct box above, and then import Button.h in your own existing layer and use the top two lines in your layer to add a button.

  3. #8 by Matt Rix on November 2nd, 2009

    Hey, I’m a little late to this, but just out of curiosity: why wouldn’t you just switch the texture of one sprite between the button and button_p textures, instead of actually making a different Sprite for each state?

    I made a similar button class a little while ago, but I thought that it’d be more efficient to just swap the texture, but now I’m not sure which is faster? any idea?

    • #9 by Eric on November 2nd, 2009

      I don’t know which would be faster (probably just switching the textures) but I really doubt it makes any big performance difference. I use this code only in menus, where I’m not really concerned about it much. And one more sprite wont make it that much worse.

  4. #10 by xuzhao on November 2nd, 2009

    Hey,I just import your code to my cocos2d project,after compile, I have 17 warning and 7 error,I dont know why? I ‘m not use that button instance now.

    • #11 by Eric on November 3rd, 2009

      You have the change the name of your font and button images. As far as warnings and errors, you shouldn’t get those if you import correctly. What are the errors?

  5. #12 by xuzhao on November 3rd, 2009

    Thank you for your reply! Now I compiled success.

    But when I run it, I got a error and break.
    the error is:
    _CFGetHostUUIDString: unable to determine UUID for host. Error: 35

    I dont know why?

    • #13 by Eric on November 4th, 2009

      I doubt that bug is coming from the code listed above. I’m not sure what is causing is though.

  6. #14 by Tushar on November 7th, 2009

    I am able to see the button on the layer when I start the application…But there is no effect after I click on it…What I understand and correct me if I am wrong …is that the onSelect function should get called…but when I put a break point here it is not coming to this function. I am a newbie at COCOS2D so please help me!!

    • #15 by Eric on November 7th, 2009

      You need to use two different images, button.png and button_p.png. The second image is the one that shows up when you are pressing the button. If you set the two images to the same file, you won’t get the click animation.

  7. #16 by Karl on November 12th, 2009

    OmgZ ur code doesn’t woooork! Show me how now! I demand an answer!!!

    Ok, all joking aside, you do need to import the cocos headers for this to work, so if you’re having troubles compiling, try adding this to the top of button.h:

    #import “cocos2d.h”

    Cheers for some nifty code!

  8. #17 by Cory on January 10th, 2010

    John,
    Thanks so much for the class. Something like this should really be included in cocos2d already. It only took a moment to make it compatible with cocos2d 0.9 (hint: add CC before all the class names).

    All the best,
    Cory

  9. #18 by Cory on January 10th, 2010

    I created a quick addition so that I can have a button that toggles between two images. In my case this is a play/pause button.

    http://gist.github.com/273888

    (note: uses cocos2d 0.9 namespace, so CCMenuItem instead of MenuItem, you’ll have to change it back if you’re using 0.8 or earlier)

  10. #19 by hilanderjeff on January 23rd, 2010

    Hi,

    It is working. I changed [self addChild:image z:1] to [self addChild:image z:0]. The z value was set to 0.

    Good Job!

  11. #20 by Stu on March 1st, 2010

    John, Thanks for the button class. I do have a question however. If you have say 4 or 5 buttons and use the same selector method, how would I determine which button was pressed?

    • #21 by Eric on March 1st, 2010

      I’m sure there is a better, clever way to do this, but in my code I just make a different private method for each button.

(will not be published)

  1. No trackbacks yet.