Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm creating an app and I'm trying to make it so I can drag a sprite (with a second sprite on top) around the screen, but if I just tap the sprite some other method should get called.

I got the dragging working fine when I touch the edge of my sprite, but when I try to drag from the middle of the sprite (where the second sprite is on top) the dragging doesn't work at all, but the tap gets called.

I know why is't doing this, tho, there's conflicting touches from both the sprits because the one on top is swallowing the touch before it hits the second one below it.

How would I be able to implement this where I want the sprite to drag if the finger moves, but I want the tap to register when only a tap is given (IE the finger didn't move on the screen)?

A visual of the sprites I'm working with (if it helps): enter image description here

The yellow rune is a separate sprite from the stone underneath it (because there's animations involved).

--------Touch for the top sprite----------
-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event{
    lastTouchLocation = [RuneScene locationFromTouch:touch];
    BOOL isTouchHandled = CGRectContainsPoint([charSprite boundingBox], lastTouchLocation);

    return isTouchHandled;
}
-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent *)event{
    NSLog(@"Tap received!");

}

------Touch for the bottom sprite--------

-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event{
    lastTouchLocation = [RuneScene locationFromTouch:touch];
    BOOL isTouchHandled = NO;

    // Check if this touch is on the Spider's sprite.
    if (CGRectContainsPoint([current.runeSprite boundingBox], lastTouchLocation)){
        mover = current;
        isTouchHandled = YES;
    }
    else if(CGRectContainsPoint([rune1.runeSprite boundingBox], lastTouchLocation)){
        mover = rune1;
        isTouchHandled = YES;
    }
    else if(CGRectContainsPoint([rune2.runeSprite boundingBox], lastTouchLocation)){
        mover = rune2;
        isTouchHandled = YES;
    }else if(CGRectContainsPoint([rune3.runeSprite boundingBox], lastTouchLocation)){
        mover = rune3;
        isTouchHandled = YES;
    }else if(CGRectContainsPoint([rune4.runeSprite boundingBox], lastTouchLocation)){
        mover = rune4;
        isTouchHandled = YES;
    }else if(CGRectContainsPoint([rune5.runeSprite boundingBox], lastTouchLocation)){
        mover = rune5;
        isTouchHandled = YES;
    }else if(CGRectContainsPoint([rune6.runeSprite boundingBox], lastTouchLocation)){
        mover = rune6;
        isTouchHandled = YES;
    }else if(CGRectContainsPoint([rune7.runeSprite boundingBox], lastTouchLocation)){
        mover = rune7;
        isTouchHandled = YES;
    }else if(CGRectContainsPoint([rune0.runeSprite boundingBox], lastTouchLocation)){
        mover = rune0;
        isTouchHandled = YES;
    }

    // Stop the move action so it doesn't interfere with the user's scrolling.
    //[self stopActionByTag:ActionTagCastingLayerMovesBack];

    // Always swallow touches, GameLayer is the last layer to receive touches.
    return isTouchHandled;

}

-(void) ccTouchMoved:(UITouch*)touch withEvent:(UIEvent *)event{
    CGPoint currentTouchLocation = [RuneScene locationFromTouch:touch]; 

    // Take the difference of the current to the last touch location.
    CGPoint moveTo = ccpSub(lastTouchLocation, currentTouchLocation);

    // Then reverse it since the goal is not to give the impression of moving the camera over the background, 
    // but to touch and move the background.
    moveTo = ccpMult(moveTo, -1);

    lastTouchLocation = currentTouchLocation;

    [self moveActionWithLocation: moveTo];
}

-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent *)event{
    if (!current.isPlaced && mover == current && currentR < Rune6) {
        // Move the game layer back to its designated position.
        CCMoveTo* move = [CCMoveTo actionWithDuration:1 position:curPt];
        CCEaseIn* ease = [CCEaseIn actionWithAction:move rate:0.5f];
        //ease.tag = ActionTagCastingLayerMovesBack;
        [current.runeSprite runAction:ease];

        [current setIsPlaced:YES];
        current.charSprite = [characters objectAtIndex:currentR];
        current.charSprite.position = curPt;
        //charSprite.visible = YES;
        [current performSelector:@selector(fade:) withObject:current.charSprite afterDelay:1];
        [current reorderChild:current.charSprite z:10];

        [self updateCurrentRune:currentR];
        [self updateCurrentCastNum:currentP];
        [self reorderChild:current z:10];
    }
}

I tried looking into the UITapGestureRecognizer, but everything I do to try to implement that never works. My layers/sprites won't let me add them as gestures. I also read something about CCGrstureRecognizer or something on the cocos2d forums, but I can't find any documentation on that class, nor can I find out how to actually use it...

Does anyone here know of a way to help me with my issue?

share|improve this question
add comment

2 Answers

up vote 5 down vote accepted

I can't tell you about cocos2d specifically, but in general, tap vs. drag is the same input recognition problem as mouse click vs. drag.

The standard way to handle this is to note when a mousedown (or touch) happens, and store where it happened, for later reference. Watch for mousemoved (or touchmoved) events to see if it moves around. If it moves to a position more than a certain distance from the original click/touch (typically 3 pixels for mouse operations -- I imagine that this threshold would be somewhat higher for touch controls), then classify the click/touch as a drag, and start treating it that way (moving objects, etc). If the mousedown/touch is released without ever being converted into a drag, then act on the stored click/touch position as having been a simple click/touch event.

You should be able to implement this simple recognition system entirely within the TouchBegan, TouchMoved, and TouchEnded objective C messages you already have, just by storing a little extra state about each touch.

share|improve this answer
    
Not too simple, you should also think about the delta between the press and the release. this should help you to understand if it's a longpress or a tap. For example, if you touch the screen, pass 1second and any release event has given, treat it as a longpress and ignore the next release event, if you get a release before 1 second has passed, treat the release as a tap. –  Gustavo Maciel Jan 9 '12 at 9:32
add comment

The iOS is already implementing the detection of tap/drag.

ccTouchMoved is called only when the finger has moved (with a tolerance). So just implement ccTouchMoved in the topSprite. If it's called, you can consider it's drag.

-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event{
    lastTouchLocation = [RuneScene locationFromTouch:touch];
    BOOL isTouchHandled = CGRectContainsPoint([charSprite boundingBox], lastTouchLocation);
    dragged=NO;

    return isTouchHandled;
}
-(void) ccTouchMoved:(UITouch*)touch withEvent:(UIEvent *)event{
    NSLog(@"Drag !");

    dragged=YES;
}
-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent *)event{
    NSLog(@"Tap received!");
    if (!dragged) {
        //Follow tap event to the mainSprite
        //...TODO
    }
    dragged=NO;
}
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.