Search code examples
iphonecocos2d-iphonemulti-touchdifferentiation

Multi-touch detecting & differentiating - Cocos2d for iPhone


I would like to know how to detect and differentiate between touches in a multi-touch view. I have read about a "hash" code, but I don't understand how to use it. I want to know when two of my Sprites are touched at the same time, like as if pressing a chord on two keys of a piano.

[EDIT] Here is an example of what I have for my ccTouchesBegan:

- (void) ccTouchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {

  NSSet *allTouches = [event allTouches];
  int validTouchCount = 0;
  for (UITouch* touch in allTouches) {

    BOOL touchIsValid = FALSE;

    CGPoint location = [touch locationInView: [touch view]];
    CGPoint convertedLocation = [[CCDirector sharedDirector] convertToGL:location];

    if (CGRectContainsPoint(_fourButtonsRect, convertedLocation)) {
        NSLog(@"Touch is within four buttons");
        touchIsValid = TRUE;
    }


    _playerDidAction = 0;
    NSLog(@"before the loop");
    if (touchIsValid) {

        validTouchCount++;
        NSLog(@"Within ValidTouches loop");
        CGPoint validLocation = [touch locationInView: [touch view]];
        CGPoint convertedValidLocation = [[CCDirector sharedDirector] convertToGL:validLocation];

        if (CGRectContainsPoint(_redButtonSprite.boundingBox, convertedValidLocation)) {
            _redButtonStatus = TRUE;
            [_redButtonSprite setTexture:_redButtonLit];
            if (validTouchCount == 1) {
                _playerDidAction = 1;
            }
        }
            else if (CGRectContainsPoint(_blueButtonSprite.boundingBox, convertedValidLocation)) {  
                _blueButtonStatus = TRUE;
                [_blueButtonSprite setTexture:_blueButtonLit];
                if (validTouchCount == 1) {
                    _playerDidAction = 2;
                }
            }
                else if (CGRectContainsPoint(_greenButtonSprite.boundingBox, convertedValidLocation)) { 
                    _greenButtonStatus = TRUE;
                    [_greenButtonSprite setTexture:_greenButtonLit];
                    if (validTouchCount == 1) {
                        _playerDidAction = 3;
                    }
                }
                    else if (CGRectContainsPoint(_yellowButtonSprite.boundingBox, convertedValidLocation)) {    
                        _yellowButtonStatus = TRUE;
                        [_yellowButtonSprite setTexture:_yellowButtonLit];
                        if (validTouchCount == 1) {
                            _playerDidAction = 4;
                        }
                    }

        if (validTouchCount > 1) {

            if (_redButtonStatus && _blueButtonStatus) {
                _comboRB = TRUE;
                _playerDidAction = 5;
            }
                else if (_redButtonStatus && _greenButtonStatus) {
                    _comboRG = TRUE;
                    _playerDidAction = 6;
                }
                    else if (_redButtonStatus && _yellowButtonStatus) {
                        _comboRY = TRUE;
                        _playerDidAction = 7;
                    }
                        else if (_blueButtonStatus && _greenButtonStatus) {
                            _comboBG = TRUE;
                            _playerDidAction = 8;
                        }
                            else if (_blueButtonStatus && _yellowButtonStatus) {
                                _comboBY = TRUE;
                                _playerDidAction = 9;
                            }
                                else if (_greenButtonStatus && _yellowButtonStatus) {
                                    _comboGY = TRUE;
                                    _playerDidAction = 10;
                                }

        }
    }
  }
}

And here is the beginning of my ccTouchesEnded:

- (void)ccTouchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {

    for (UITouch *touch in touches) {


        CGPoint location = [touch locationInView: [touch view]];
        CGPoint convertedLocation = [[CCDirector sharedDirector] convertToGL:location];     
        if (CGRectContainsPoint(_redButtonSprite.boundingBox, convertedLocation)) { 
            _redButtonStatus = FALSE;
            [_redButtonSprite setTexture:_redButtonNormal];
        }
        if (CGRectContainsPoint(_blueButtonSprite.boundingBox, convertedLocation)) {    
            _blueButtonStatus = FALSE;
            [_blueButtonSprite setTexture:_blueButtonNormal];
        }
        if (CGRectContainsPoint(_greenButtonSprite.boundingBox, convertedLocation)) {   
            _greenButtonStatus = FALSE;
            [_greenButtonSprite setTexture:_greenButtonNormal];
        }
        if (CGRectContainsPoint(_yellowButtonSprite.boundingBox, convertedLocation)) {  
            _yellowButtonStatus = FALSE;
            [_yellowButtonSprite setTexture:_yellowButtonNormal];
        }


    }
}

Could you maybe give me an example of how you would capture touches that began on a sprite and ended on a sprite? I have been struggling and can't get the hash code to work - just not understanding how the hash code can be used to reference a touch later. I guess what I'm trying to make would be called a hash tracker?

I am sure there is a much less convoluted way to do it using the hash codes and less state variables. I haven't fleshed out the ccTouchesEnded method with the other state variable effects because I was hoping to find a simpler way (I know I still need to make the ccTouchesMoved and Canceled methods too).


Solution

  • Here is how I implemented this in case anyone else is trying to do this (I have limited it to 2-button combos but I could easily extend the logic to 3 & 4-button combos as well). I chose to handle each touch individually using the ccTouchBegan/Ended/Moved instead of using the ccTouchesBegan/Ended/Moved because I just couldn't get it to work with the hash code. Any alternative ideas would be welcome.

    spuButton.h (a CCSprite Subclass)

    #import <Foundation/Foundation.h>
    #import "cocos2d.h"
    
    typedef enum tagButtonState {
        kButtonStatePressed,
        kButtonStateNotPressed
    } ButtonState;
    
    typedef enum tagButtonStatus {
        kButtonStatusEnabled,
        kButtonStatusDisabled
    } ButtonStatus;
    
    @interface spuButton : CCSprite <CCTargetedTouchDelegate> {
    @private
        ButtonState buttonState;
        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;
    - (void)makeDisabled;
    - (void)makeEnabled;
    - (BOOL)isEnabled;
    - (BOOL)isDisabled;
    - (void)makeLit;
    - (void)makeNormal;
    - (void)dealloc;
    
    @end
    

    spuButton.m

    #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 (buttonState== kButtonStateNotPressed) return NO;
        if (buttonState== kButtonStatePressed) return YES;
        return NO;
    }
    
    - (BOOL)isNotPressed {
        if (buttonState== kButtonStateNotPressed) return YES;
        if (buttonState== kButtonStatePressed) return NO;
        return YES;
    }
    
    - (void)makeDisabled {
        buttonStatus = kButtonStatusDisabled;
        buttonState= kButtonStateNotPressed;
        [self makeNormal];
    }
    - (void)makeEnabled {
        buttonStatus = kButtonStatusEnabled;
        buttonState= kButtonStateNotPressed;
        [self makeNormal];
    }
    
    - (BOOL)isEnabled {
        if (buttonStatus== kButtonStatusDisabled) return NO;
        if (buttonStatus== kButtonStatusEnabled) return YES;
        return NO;
    }
    
    - (BOOL)isDisabled {
        if (buttonStatus== kButtonStatusEnabled) return NO;
        if (buttonStatus== kButtonStatusDisabled) return YES;
        return YES;
    }
    
    - (void)makeLit {
        [self setTexture:buttonLit];
    }
    
    - (void)makeNormal {
        [self setTexture:buttonNormal];
    }
    
    - (id)initWithTexture:(CCTexture2D *)aTexture {
        if ((self = [super initWithTexture:aTexture]) ) {       
            buttonState = kButtonStateNotPressed;
            buttonStatus = kButtonStatusEnabled;
        }
        return self;
    }
    
    - (void)onEnter {
        if (buttonStatus == kButtonStatusDisabled) return;
        [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:NO];
        [super onEnter];
    }
    
    - (void)onExit {
        if (buttonStatus == kButtonStatusDisabled) return;
        [[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 (buttonStatus == kButtonStatusDisabled) return NO;
        if (buttonState== kButtonStatePressed) return NO;
        if ( ![self containsTouchLocation:touch] ) return NO;
    
        buttonState= kButtonStatePressed;
        [self makeLit];
    
        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 (buttonStatus == kButtonStatusDisabled) return;
        if ([self containsTouchLocation:touch]) return;
    
        buttonState= kButtonStateNotPressed;
        [self makeNormal];
    }
    
    - (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
        if (buttonStatus == kButtonStatusDisabled) return;
    
        buttonState= kButtonStateNotPressed;
        [self makeNormal];
    }
    
    - (void)dealloc {
        [buttonNormal release];
        [buttonLit release];
        [super dealloc];
    }
    
    @end
    

    HelloWorldScene.m (Just my tick: method to keep my other functions from confusing the example)

    -(void)tick:(ccTime)dt {
        if ([[_otherControlsArray objectAtIndex:0] wasPressed]) {
            [[_otherControlsArray objectAtIndex:0] setWasPressed:NO];
            [self removeChild:[_otherControlsArray objectAtIndex:0] cleanup:YES];
            [self addChild:[_otherControlsArray objectAtIndex:1]];
            NSLog(@"Play");
    
            _gameHasNotBeenPlayedYet = NO;
            Snarfle_s_PowerUPAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
            [delegate makeNotPaused];
            [self gameLogic];
        }
    
        if (_gameHasNotBeenPlayedYet) {
            return;
        }
    
        if (_buttonsPressedAndReleased > 0) {  //respond to button(s) released and reset
            NSLog(@"Buttons Pressed and Released-->%d",_buttonsPressedAndReleased);
            if ([self checkButtons:_buttonsPressedAndReleased]);
            _buttonsPressed = 0;
            _buttonsPressedAndReleased = 0;
    
            return;
        }
        if (_buttonsPressed <= 4) { // two buttons have not already been pressed
            for (spuButton *aButton in _fourButtonsArray) {
                if ([aButton isNotPressed]) continue; //this button is not pressed
                if (_buttonsPressed == 0) { //this button is pressed and no other buttons have been pressed
                    _buttonsPressed = aButton.tag;
                    continue;
                }
                //this button is pressed while another has been pressed
                //figure out which two buttons have been pressed
                if (_buttonsPressed == 1) {  //red plus another
                    switch (aButton.tag) {
                        case 2:   //blue
                            _buttonsPressed = 5;
                            [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                            break;
                        case 3:  //green
                            _buttonsPressed = 6;
                            [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                            break;
                        case 4:  //yellow
                            _buttonsPressed = 7;
                            [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                            break;
                        default:
                            _buttonsPressed = 1;
                            break;
                    }
                }
                if (_buttonsPressed == 2) {  //blue plus another
                    switch (aButton.tag) {
                        case 1:   //red
                            _buttonsPressed = 5;
                            [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                            break;
                        case 3:  //green
                            _buttonsPressed = 8;
                            [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                            break;
                        case 4:  //yellow
                            _buttonsPressed = 9;
                            [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                            break;
                        default:
                            _buttonsPressed = 2;
                            break;
                    }
                }
                if (_buttonsPressed == 3) {  //green plus another
                    switch (aButton.tag) {
                        case 1:   //red
                            _buttonsPressed = 6;
                            [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                            break;
                        case 2:  //blue
                            _buttonsPressed = 8;
                            [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                            break;
                        case 4:  //yellow
                            _buttonsPressed = 10;
                            [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                            break;
                        default:
                            _buttonsPressed = 3;
                            break;
                    }
                }
                if (_buttonsPressed == 4) {  //yellow plus another
                    switch (aButton.tag) {
                        case 1:   //red
                            _buttonsPressed = 7;
                            [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                            break;
                        case 2:  //blue
                            _buttonsPressed = 9;
                            [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                            break;
                        case 3:  //green
                            _buttonsPressed = 10;
                            [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                            [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                            break;
                        default:
                            _buttonsPressed = 4;
                            break;
                    }
                }
                if (_buttonsPressed > 4) break;  //more than one has been pressed and identified
            }
        }
        //now we know what buttons have been pressed now check to see if they have been released
        //if more than one has been pressed disable the other two
        //also if more than one has been pressed and one of them gets released disable the released one but keep it lit
        switch (_buttonsPressed) {
            case 1:  //red
                if ([[_fourButtonsArray objectAtIndex:0] isNotPressed]) _buttonsPressedAndReleased = 1;
                break;
            case 2:  //blue
                if ([[_fourButtonsArray objectAtIndex:1] isNotPressed]) _buttonsPressedAndReleased = 2;
                break;
            case 3:  //green
                if ([[_fourButtonsArray objectAtIndex:2] isNotPressed]) _buttonsPressedAndReleased = 3;
                break;
            case 4:  //yellow
                if ([[_fourButtonsArray objectAtIndex:3] isNotPressed]) _buttonsPressedAndReleased = 4;
                break;
            case 5:  //red & blue
                if (([[_fourButtonsArray objectAtIndex:0] isNotPressed]) && ([[_fourButtonsArray objectAtIndex:1] isNotPressed])) _buttonsPressedAndReleased = 5;
                else {
                    if ([[_fourButtonsArray objectAtIndex:0] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:0] makeLit];
                    }
                    if ([[_fourButtonsArray objectAtIndex:1] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:1] makeLit];
                    }
                }
                break;
            case 6:  //red & green
                if (([[_fourButtonsArray objectAtIndex:0] isNotPressed]) && ([[_fourButtonsArray objectAtIndex:2] isNotPressed])) _buttonsPressedAndReleased = 6;
                else {
                    if ([[_fourButtonsArray objectAtIndex:0] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:0] makeLit];
                    }
                    if ([[_fourButtonsArray objectAtIndex:2] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:2] makeLit];
                    }
                }
                break;
            case 7:  //red & yellow
                if (([[_fourButtonsArray objectAtIndex:0] isNotPressed]) && ([[_fourButtonsArray objectAtIndex:3] isNotPressed])) _buttonsPressedAndReleased = 7;
                else {
                    if ([[_fourButtonsArray objectAtIndex:0] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:0] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:0] makeLit];
                    }
                    if ([[_fourButtonsArray objectAtIndex:3] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:3] makeLit];
                    }
                }
                break;
            case 8:  //blue & green
                if (([[_fourButtonsArray objectAtIndex:1] isNotPressed]) && ([[_fourButtonsArray objectAtIndex:2] isNotPressed])) _buttonsPressedAndReleased = 8;
                else {
                    if ([[_fourButtonsArray objectAtIndex:1] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:1] makeLit];
                    }
                    if ([[_fourButtonsArray objectAtIndex:2] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:2] makeLit];
                    }
                }
                break;
            case 9:  //blue & yellow
                if (([[_fourButtonsArray objectAtIndex:1] isNotPressed]) && ([[_fourButtonsArray objectAtIndex:3] isNotPressed])) _buttonsPressedAndReleased = 9;
                else {
                    if ([[_fourButtonsArray objectAtIndex:1] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:1] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:1] makeLit];
                    }
                    if ([[_fourButtonsArray objectAtIndex:3] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:3] makeLit];
                    }
                }
                break;
            case 10:  //green & yellow
                if (([[_fourButtonsArray objectAtIndex:2] isNotPressed]) && ([[_fourButtonsArray objectAtIndex:3] isNotPressed])) _buttonsPressedAndReleased = 10;
                else {
                    if ([[_fourButtonsArray objectAtIndex:2] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:2] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:2] makeLit];
                    }
                    if ([[_fourButtonsArray objectAtIndex:3] isNotPressed]) {
                        [[_fourButtonsArray objectAtIndex:3] makeDisabled];
                        [[_fourButtonsArray objectAtIndex:3] makeLit];
                    }
                }
                break;
            default:
                _buttonsPressedAndReleased = 0;
                break;
        }
    }