miércoles, febrero 04, 2009

Cocos2d Iphone Dynamically Touch Detection

After searching on the web, on how to detect touches in sprites in Cocos2d, I found a Blog, that helped me a lot on how to implement the touching detection system of sprites in Cocos 2d.

The Simple but usefull Input system didnt fit my needs, because I needed a System that could handle the Touch Detection Selectively and dinamically, So I took the system proposed (the top-down global input management.) if you checked the Blog, and modified it.

Generally the aproach allows a very high level of control over handling input, but is prone to creating a monolithic method that handles all input management for the application.

First, it requires that you have references to all Sprite objects that you are interested in detecting input for.

So we can setup a subclass of Sprite to track all instances, I'm gonna name it TSprite from now on (as Touch Sprite).

The idea of this method is having a Subclass of Sprite, wich has an array of all the Sprites that we want to handle Input Detection, and aditionally we would have a boolean flag that would tell us if each Sprite in the Array can be tracked or not in a certain moment.

In more or less words, here's the class:


@interface TSprite : Sprite {
BOOL canTrack; //tell us if a Sprite in the Array can be tracked.
}

+(NSMutableArray *)allMySprites;
+(void)track: (TSprite *)aSprite;
+(void)untrack: (TSprite *)aSprite;
+(BOOL)SomethingWasTouched:(CGPoint) pos;
+ (TSprite *) FindByTag:(int) tagVar;
- (CGRect) rect;
- (void) SetCanTrack:(BOOL) val;
- (BOOL) GetCanTrack;

@end


and the implementation:


@implementation TSprite


static NSMutableArray * allMySprites = nil;

+(NSMutableArray *)allMySprites {
@synchronized(allMySprites) {
if (allMySprites == nil)
allMySprites = [[NSMutableArray alloc] init];
return allMySprites;
}
return nil;
}

- (CGRect) rect {
float x = [self absolutePosition].x - [self contentSize].width/2;
float y = [self absolutePosition].y - [self contentSize].height/2;
float h = [self contentSize].height;
float w = [self contentSize].width;
return CGRectMake(x,y,w,h);
}

+(void)track: (TSprite *)aSprite {
@synchronized(allMySprites) {
NSUInteger i, count = [allMySprites count];
for(i = 0; i < count ; i++){
TSprite * obj = (TSprite *)[allMySprites objectAtIndex:i];
if(obj == aSprite){
return;
}
}
[[TSprite allMySprites] addObject:aSprite];
}
}

+(void)untrack: (TSprite *)aSprite {
@synchronized(allMySprites) {
[[TSprite allMySprites] removeObject:aSprite];
}
}

+(BOOL)SomethingWasTouched:(CGPoint) pos {
NSUInteger i, count = [allMySprites count];
for (i = 0; i < count; i++) {
TSprite * obj = (TSprite *)[allMySprites objectAtIndex:i];
if (CGRectContainsPoint([obj rect], pos) && [obj GetCanTrack]) {
return YES;
}
}
return NO;
}

+ (TSprite *) FindByTag:(int) tagVar{
NSUInteger i, count = [allMySprites count];
for(i = 0; i < count ; i++){
TSprite * obj = (TSprite *)[allMySprites objectAtIndex:i];
if([obj tag] == tagVar){
return obj;
}
}
return nil;

}
-(id)init {
self = [super init];
if (self){
[TSprite track:self];
[self SetCanTrack:YES];
}
return self;
}

- (void) SetCanTrack:(BOOL) val{
canTrack = val;
}
- (BOOL) GetCanTrack{
return canTrack;
}

-(void)dealloc {
[TSprite untrack:self];
[super dealloc];
}
@end


Then in the scene you implement the touch detection.


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];

NSArray * mySprites = [MySprite allMySprites];
NSUInteger i, count = [mySprites count];
for (i = 0; i < count; i++) {
MySprite * obj = (MySprite *)[mySprites objectAtIndex:i];
if (CGRectContainsPoint([obj rect], location) && [obj GetCanTrack]) {
// code here is only executed if obj has been touched
}
}
}


Now, a simple way of initiating this class can be:


TSprite *ez = [TSprite spriteWithFile:@"Sprite.png"];
[ez SetCanTrack:YES];//The sprite can be tracked.
[TSprite track:ez];

TSprite *asd= [TSprite spriteWithFile:@"AnotherSprite.png"];
[asd SetCanTrack:NO];
[TSprite track:asd];


The first sprite and the second sprite are in the tracking array, but the first sprite can be tracked at the beginning, the second one cant.

This Input System is usefull if you are prototyping, and have a simple application, the problem again, is that is prone to be a monolithic Input Management System, and if you are not careful enough with the architecture of your app, the code can be extremely hard to understand.

If you want a XCode project that shows how this Input System works, you can found it here.

Any comments are welcome.

4 comentarios:

Anónimo dijo...

You have one small error in your source code... you have @synthesize opacity; listed twice in your menuitem.m file. remove that and you can run the source code successfully.

gonzobrains dijo...

I tried to compile your sample. It didn't work. opacity was defined twice. So I commented one out.

What are you supposed to be able to do with this? I clicked on an icon. the other two disappeared. Are you supposed to be able to drag them around?

Chirag dijo...

Hey, I just wanted to thank you for this brilliant post. Its really really helped me out. Thanks a lot :-)

Unknown dijo...

As of 08/23/2010, subclassing Sprite no longer seems to work. I just started with cocos2d/Obj-C, so I don't know the history, but I do know that to get it to work with v0.99.4, you use CCSprite instead.

So, instead of doing

@interface TSprite : Sprite{
BOOL canTrack;
}

you do...

@interface TSprite: CCSprite{
BOOL canTrack;
}

Also, instead of using absolutePosition, just use position. Hope that helps a few people here and there...