Best practices for handling touches to a CCSprite with cocos2d
Hey all. I just started looking into the cocos2d library. I've heard that it's an easy library to get into if you're used to programming in ActionScript and I've found that a lot of the concepts are indeed similar.
I started looking through sample projects (the sample games link开发者_JS百科ed here were especially helpful) and I saw that handling of touches usually isn't done in a CCSprite. Rather, the CCLayer that instantiates CCSprites reacts to a touch event and iterates through the sprites it created to detect which CCSprite was touched (if any).
I want CCSprites to handle whether they've been touched themselves, and call up to notify that it's been touched (if needed). The Paddle
class found under /tests/TouchesTest does just this - it handles touches by itself.
So, the question I have is: What's considered best practice for this? Is it better to have touches handled in a central location and iterate through children to see what's been touched? Or should each child handle its own touch events? Or does it not matter?
I'd prefer each child handling its own touch events but I'd like to follow best practices on this (if they exist). Thanks!
I think it is a matter of preference but I like to have the sprite detect if it was touched by subclassing CCSprite. I make a getter method in my CCSprite subclass that retrieves the state variable from the subclass and then the main program can act accordingly.
Here is an example header file for my CCSprite subclass "spuButton":
#import "cocos2d.h"
typedef enum tagButtonState {
kButtonStatePressed,
kButtonStateNotPressed
} ButtonState;
typedef enum tagButtonStatus {
kButtonStatusEnabled,
kButtonStatusDisabled
} ButtonStatus;
@interface spuButton : CCSprite <CCTargetedTouchDelegate> {
@private
ButtonState state;
CCTexture2D *buttonNormal;
CCTexture2D *buttonLit;
ButtonStatus buttonStatus;
}
@property(nonatomic, readonly) CGRect rect;
+ (id)spuButtonWithTexture:(CCTexture2D *)normalTexture;
- (void)setNormalTexture:(CCTexture2D *)normalTexture;
- (void)setLitTexture:(CCTexture2D *)litTexture;
- (BOOL)isPressed;
- (BOOL)isNotPressed;
@end
and here is an example of the .m file:
#import "spuButton.h"
#import "cocos2d.h"
@implementation spuButton
- (CGRect)rect
{
CGSize s = [self.texture contentSize];
return CGRectMake(-s.width / 2, -s.height / 2, s.width, s.height);
}
+ (id)spuButtonWithTexture:(CCTexture2D *)normalTexture
{
return [[[self alloc] initWithTexture:normalTexture] autorelease];
}
- (void)setNormalTexture:(CCTexture2D *)normalTexture {
buttonNormal = normalTexture;
}
- (void)setLitTexture:(CCTexture2D *)litTexture {
buttonLit = litTexture;
}
- (BOOL)isPressed {
if (state == kButtonStateNotPressed) return NO;
if (state == kButtonStatePressed) return YES;
return NO;
}
- (BOOL)isNotPressed {
if (state == kButtonStateNotPressed) return YES;
if (state == kButtonStatePressed) return NO;
return YES;
}
- (id)initWithTexture:(CCTexture2D *)aTexture
{
if ((self = [super initWithTexture:aTexture]) ) {
state = kButtonStateNotPressed;
}
return self;
}
- (void)onEnter
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[super onEnter];
}
- (void)onExit
{
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
[super onExit];
}
- (BOOL)containsTouchLocation:(UITouch *)touch
{
return CGRectContainsPoint(self.rect, [self convertTouchToNodeSpaceAR:touch]);
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
if (state == kButtonStatePressed) return NO;
if ( ![self containsTouchLocation:touch] ) return NO;
if (buttonStatus == kButtonStatusDisabled) return NO;
state = kButtonStatePressed;
[self setTexture:buttonLit];
return YES;
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
// If it weren't for the TouchDispatcher, you would need to keep a reference
// to the touch from touchBegan and check that the current touch is the same
// as that one.
// Actually, it would be even more complicated since in the Cocos dispatcher
// you get NSSets instead of 1 UITouch, so you'd need to loop through the set
// in each touchXXX method.
if ([self containsTouchLocation:touch]) return;
//if (buttonStatus == kButtonStatusDisabled) return NO;
state = kButtonStateNotPressed;
[self setTexture:buttonNormal];
}
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
state = kButtonStateNotPressed;
[self setTexture:buttonNormal];
}
@end
Hope this helps and Happy coding!
ADDITION of tick method and explanation (for Stephan's question below):
To check the status of the buttons I have a tick: method that basically fires every frame and checks the status of all my buttons.
-(void)tick:(ccTime)dt {
do my button checks here....
}
I check the status of my buttons by calling a isPressed or isNotPressed function that is part of my spuButton class.
for (spuButton *aButton in _fourButtonsArray) {
if ([aButton isNotPressed]) continue; //this button is not pressed
.....otherwise record that it is pressed.....
}
Then I do the same kind of check to see if it has been released and respond accordingly. I do it this way because I want to be able to react to multiple button press combos plus I want to do something when it gets pressed down and then something else when it gets released. I use the ccTouchBegan and ccTouchEnded to change the textures(the sprite image) and to change the state variable accordingly.
just adding to this thread. Mark also provides an examples how to instantiate the spuButton which is helpful:
Problem with cocos2d and orientation changes, textures are deformed
you can also go in modify this example to pass in both normal and lit button images like so:
+ (id)spuButtonWithTexture:(CCTexture2D *)normalTexture lit:(CCTexture2D *)litTexture
and then do the same to:
- (id)initWithTexture:(CCTexture2D *)normalTexture lit:(CCTexture2D *)litTexture
and within this method, you can set both textures:
[self setNormalTexture:normalTexture];
[self setLitTexture:litTexture];
Here is my solution, based on CCSprite hope it will be useful for somebody
this is protocol for controlled object (like player or something):
@class AGSensitiveButton;
@protocol AGSensitiveButtonControlledObjectProtocol <NSObject>
@required
- (void)sensitiveButtonTouchDown:(AGSensitiveButton *)sButton;
- (void)sensitiveButtonTouchUp:(AGSensitiveButton *)sButton;
@optional
- (void)sensitiveTouchButtonKeepPressed:(AGSensitiveButton *)sButton forTime:(ccTime)pressTime;
@end
.h file:
#import "CCSprite.h"
#import "cocos2d.h"
#import "AGSensitiveButtonControlledObjectProtocol.h"
typedef enum {
AGSensitiveButtonStateNormal = 0,
AGSensitiveButtonStateHighlighted,
AGSensitiveButtonStateDisabled
} AGSensitiveButtonState;
@interface AGSensitiveButton : CCSprite <CCTargetedTouchDelegate>
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
@property (nonatomic, assign) ccTime maximumTouchDuration;
@property (nonatomic, weak) id <AGSensitiveButtonControlledObjectProtocol> controlledObject;
@property (nonatomic, copy) void (^touchDownHandler)();
@property (nonatomic, copy) void (^touchUpHandler)();
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture;
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
disabledtexture:(CCTexture2D *)disabledTexture;
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
controllerObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject;
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
disabledtexture:(CCTexture2D *)disabledTexture
controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject;
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
disabledtexture:(CCTexture2D *)disabledTexture
controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject
touchDownHandler:(void(^)(void))touchDownHandler
touchUpHandler:(void(^)(void))touchUpHandler;
- (void)setTexture:(CCTexture2D *)texture forState:(AGSensitiveButtonState)state;
- (BOOL)isHighlighted;
@end
implementation .m file:
#import "AGSensitiveButton.h"
@interface AGSensitiveButton ()
@property (nonatomic, assign) AGSensitiveButtonState state;
@property (nonatomic, strong) NSDictionary *stateTextures;
@property (nonatomic, assign) ccTime currentTouchTime;
@end
@implementation AGSensitiveButton
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture {
return [self buttonWithNormalTexture:normalTexture
highlightedTexture:highTexture
controllerObject:nil];
}
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
disabledtexture:(CCTexture2D *)disabledTexture {
return [self buttonWithNormalTexture:normalTexture
highlightedTexture:highTexture
disabledtexture:disabledTexture
controlledObject:nil];
}
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
controllerObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject {
return [self buttonWithNormalTexture:normalTexture
highlightedTexture:highTexture
disabledtexture:nil
controlledObject:controlledObject];
}
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
disabledtexture:(CCTexture2D *)disabledTexture
controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject {
return [self buttonWithNormalTexture:normalTexture
highlightedTexture:highTexture
disabledtexture:disabledTexture
controlledObject:controlledObject
touchDownHandler:NULL
touchUpHandler:NULL];
}
+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
highlightedTexture:(CCTexture2D *)highTexture
disabledtexture:(CCTexture2D *)disabledTexture
controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject
touchDownHandler:(void(^)(void))touchDownHandler
touchUpHandler:(void(^)(void))touchUpHandler {
AGSensitiveButton *button = [[self alloc] initWithTexture:normalTexture
rect:CGRectMake(0.0, 0.0, normalTexture.contentSize.width, normalTexture.contentSize.height)];
[button setTexture:normalTexture forState:AGSensitiveButtonStateNormal];
[button setTexture:highTexture forState:AGSensitiveButtonStateHighlighted];
[button setTexture:disabledTexture forState:AGSensitiveButtonStateDisabled];
button.controlledObject = controlledObject;
button.touchDownHandler = touchDownHandler;
button.touchUpHandler = touchUpHandler;
return button;
}
- (void)setEnabled:(BOOL)enabled {
[self setupNewState:enabled ? AGSensitiveButtonStateNormal : AGSensitiveButtonStateDisabled];
}
- (BOOL)isEnabled {
return (self.state != AGSensitiveButtonStateDisabled);
}
- (BOOL)isHighlighted {
return (self.state == AGSensitiveButtonStateHighlighted);
}
- (void)toggleTextureForCurrentState {
CCTexture2D *textureToSet = [self.stateTextures objectForKey:[NSNumber numberWithInteger:self.state]];
if (textureToSet) {
self.texture = textureToSet;
self.textureRect = CGRectMake(0.0, 0.0, textureToSet.contentSize.width, textureToSet.contentSize.height);
}
}
- (void)setTexture:(CCTexture2D *)texture forState:(AGSensitiveButtonState)state {
NSMutableDictionary *newStates = self.stateTextures.mutableCopy;
if (texture) {
[newStates setObject:texture forKey:[NSNumber numberWithInteger:state]];
} else {
[newStates removeObjectForKey:[NSNumber numberWithInteger:state]];
}
self.stateTextures = newStates.copy;
}
- (NSDictionary *)stateTextures {
if (!_stateTextures) {
_stateTextures = [[NSDictionary alloc] init];
}
return _stateTextures;
}
- (void)onEnter {
[super onEnter];
[self toggleTextureForCurrentState];
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[self scheduleUpdate];
}
- (void)onExit {
[super onExit];
[self unscheduleUpdate];
[[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self];
}
- (void)update:(ccTime)dt {
if ((self.state == AGSensitiveButtonStateHighlighted) && (self.maximumTouchDuration)) {
self.currentTouchTime+=dt;
if (self.currentTouchTime >= self.maximumTouchDuration) {
[self ccTouchEnded:nil withEvent:nil];
} else {
if ([self.controlledObject respondsToSelector:@selector(sensitiveTouchButtonKeepPressed:forTime:)]) {
[self.controlledObject sensitiveTouchButtonKeepPressed:self forTime:self.currentTouchTime];
}
}
}
}
- (CGRect)rectForTouches {
return CGRectMake(-self.contentSize.width/2, -self.contentSize.height/2,
self.contentSize.width, self.contentSize.height);
}
- (void)forwardTouchDownEventIntoHandlers {
if ([self.controlledObject respondsToSelector:@selector(sensitiveButtonTouchDown:)]) {
[self.controlledObject sensitiveButtonTouchDown:self];
}
if (self.touchDownHandler) {
self.touchDownHandler();
}
}
- (void)forwardTouchUpEventIntoHandlers {
if ([self.controlledObject respondsToSelector:@selector(sensitiveButtonTouchUp:)]) {
[self.controlledObject sensitiveButtonTouchUp:self];
}
if (self.touchUpHandler) {
self.touchUpHandler();
}
}
- (void)setupNewState:(AGSensitiveButtonState)state {
if (self.state != state) {
switch (state) {
case AGSensitiveButtonStateHighlighted: {
[self forwardTouchDownEventIntoHandlers];
break;
}
default: {
if (self.state == AGSensitiveButtonStateHighlighted) {
[self forwardTouchUpEventIntoHandlers];
}
break;
}
}
self.state = state;
[self toggleTextureForCurrentState];
}
}
#pragma mark - CCTargetedTouchDelegate
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
if ((self.state != AGSensitiveButtonStateNormal) || (!CGRectContainsPoint([self rectForTouches], [self convertTouchToNodeSpaceAR:touch]))) {
return NO;
}
self.currentTouchTime = 0.0;
[self setupNewState:AGSensitiveButtonStateHighlighted];
return YES;
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
if (self.state == AGSensitiveButtonStateHighlighted) {
if (!CGRectContainsPoint([self rectForTouches], [self convertTouchToNodeSpaceAR:touch])) {
[self ccTouchEnded:touch withEvent:event];
}
}
}
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
if (self.state == AGSensitiveButtonStateHighlighted) {
[self setupNewState:AGSensitiveButtonStateNormal];
}
}
@end
精彩评论