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

31 comments

  1. 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

    • 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.

    • 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 {

      }

  2. 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.

    • 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. 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?

    • 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. 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.

    • 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. 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?

  6. 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!!

    • 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. 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. 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. 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?

  10. I’m using it with a newer version of cocos2d and had to change all the class names prepending the ‘CC’. for example:

    Sprite -> CCSprite
    etc.

    Thanks!
    Manuel

  11. Hi,

    I have implemented your code with no errors or warnings when i build it. However, when I run it on my phone i get the following error

    “EXEC_BAD_ACCESS” which is apparently a memory error – trying to access something that does not exist.

    Any ideas how I can fix it?

    Thanks,
    Ben

      • Both of these errors are very common and basic errors that you will experience while working with Objective C. You can find plenty of information about the error in the first few google results for it.

        Based on the fact that you just copied the code and tried to run it…you are probably missing button.png and button_p.png.

  12. This class doesn’t work in the Mac port of Cocos2d (well it displays but it crashes when you click a button). After the click the function unselected() is called and it crashes at the line [self addChild:back];

  13. I got it to work fine. I was wondering why I can not at this menu to my sprite class? I have a sprite class that allows touch through the main class and it has subclasses that retain the touch. So when I place two of a different kind in the world I can touch both and they both register a touch with an nslog i have set up that says touched. So that works. My goal was to have the menu pop up from the sprite class so that each sprite placed can have access to its own menu but of the same menu(your button) class. It says something about not being able to add sprite as a child with a SpriteBatchNode.

    So when I use your line

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

    it crashes. ANy idea why I am new to cocos2d but I am a quick learner. If you maybe no what direction to start in? Not looking for a detailed coding on my problem. :)

  14. got it to work fine. I was wondering why I can not add this menu to my sprite class? I have a sprite class that allows touch through the main class and it has subclasses that retain the touch. So when I place two of a different kind(sprite) in the world I can touch both and they both register a touch with an nslog i have set up that says “touched.” So that works. My goal was to have the menu(your buttons) pop up from the sprite class so that each sprite placed can have access to its own menu but of the same menu(your button) class. It says something about not being able to add sprite as a child with a SpriteBatchNode.

    So when I use your line

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

    it crashes. Any idea why I am new to cocos2d but I am a quick learner. If you maybe no what direction to start in? Not looking for a detailed coding on my problem.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">