iOS touches(touchesBegan, touchesMoved…) for Games

I have been working on a game called Flat Out Hockey for Android, iOS, Mac and OUYA.

GooglePlayFeatureFinal

For this game I needed both “Touch buttons”(which are buttons mimicking gamepad buttons) and a touch area where you would hold your finger and the character would follow:

Nexus7Screenshot

 

It worked fine for the most part, but if I pressed several buttons at once or tried to do crazy tapping and swiping my touch area would stop responding.

All the touches in the game’s iOS version are handled using the touches functions(touchesBegan, touchesMoved, touchesCancelled, touchesEnded).

I would go over all the UITouch of the UIEvent(using [event allTouches]) and track all the touches that have started, moved or ended(there could be multiple of them at once).

What I didn’t realize is that even when you are inside the touchesEnded function(for instance) you may get UITouch which did not actually end.

I am thinking that maybe UIEvent allTouches give all the current touches regardless which touches function has been called, which means you always need to check for the UITouchPhase of every UITouch!

This is quite simple and makes sense but you might easily miss that…(like I did)

For the sake of completion here is the Objective C part of my touches code:

 

@interface TouchNode: NSObject
{
@public
    NSNumber * hash;
    unsigned int originalIndex;
}
@end
-(double)zoomFactor
{
    if (IS_ZOOMED_IPHONE_6_PLUS || IS_ZOOMED_IPHONE_6)
    {
        double s = [[UIScreen mainScreen] nativeScale]/[[UIScreen mainScreen] scale];
        return s;
    }
    return 1.0;
}


-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//    double retinaFactor = isRetina?2.0:1.0;
    for (UITouch *touch in [event allTouches]) {
        CGPoint touchLocation = [touch locationInView:self.view];
        touchLocation.x*=[self zoomFactor];
        touchLocation.y*=[self zoomFactor];
        if (IsInput())
        {
            
            if (touch.phase == UITouchPhaseBegan)
            {
                TouchNode * n = [TouchNode new];
                n->hash = @(touch.hash);
                n->originalIndex = (unsigned int)[touchHash count];
                [touchHash addObject:n];
            }
            else
                continue;
            unsigned int i=0;
            for (; i<[touchHash count]; i++)
                if (touchHash[i]!=[NSNull null] && [((TouchNode*)touchHash[i])->hash integerValue]==touch.hash)
                    break;
            if (i<[touchHash count])
            {
                if (touch.phase == UITouchPhaseEnded)
                {
                    ReleaseTouch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y, ((TouchNode*)touchHash[i])->originalIndex);
                    touchHash[i] = [NSNull null];
                    [touchHash removeObjectAtIndex:i];
                }
                else
                    Touch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y, ((TouchNode*)touchHash[i])->originalIndex);
            }
            //            NSLog(@"%d", i);
        }
    }
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
//    double retinaFactor = isRetina?2.0:1.0;
    for (UITouch *touch in [event allTouches]) {
        CGPoint touchLocation = [touch locationInView:self.view];
        touchLocation.x*=[self zoomFactor];
        touchLocation.y*=[self zoomFactor];
        if (IsInput())
        {
            if (touch.phase != UITouchPhaseMoved)
                continue;
            unsigned int i=0;
            for (; i<[touchHash count]; i++)
                if (touchHash[i]!=[NSNull null] && [((TouchNode*)touchHash[i])->hash integerValue]==touch.hash)
                    break;
            if (i<[touchHash count])
            {
                if (touch.phase == UITouchPhaseEnded)
                {
                    ReleaseTouch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y, ((TouchNode*)touchHash[i])->originalIndex);
                    touchHash[i] = [NSNull null];
                    [touchHash removeObjectAtIndex:i];
                }
                else
                    Touch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y, ((TouchNode*)touchHash[i])->originalIndex);
            }
            //            NSLog(@"%d", i);
        }
    }
}

-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    touchHash = [NSMutableArray new];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//    double retinaFactor = isRetina?2.0:1.0;
    for (UITouch *touch in [event allTouches]) {
        CGPoint touchLocation = [touch locationInView:self.view];
        touchLocation.x*=[self zoomFactor];
        touchLocation.y*=[self zoomFactor];
        if (IsInput())
        {
            if (touch.phase != UITouchPhaseEnded)
                continue;
            unsigned int i=0;
            for (; i<[touchHash count]; i++)
                if (touchHash[i]!=[NSNull null] && [((TouchNode*)touchHash[i])->hash integerValue]==touch.hash)
                    break;
            if (i<[touchHash count])
            {
                unsigned int Count = 0;
                for (unsigned int j=0; j<[touchHash count]; j++)
                    if (touchHash[i]!=[NSNull null])
                        Count++;
                if (touch.phase == UITouchPhaseEnded)
                {
                    if (Count==1)
                        ReleaseTouch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y);
                    else
                    {
                        ReleaseTouch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y, ((TouchNode*)touchHash[i])->originalIndex);
                    }
                    touchHash[i] = [NSNull null];
                    [touchHash removeObjectAtIndex:i];
                }
                else
                    Touch(retinaFactor*touchLocation.x, retinaFactor*touchLocation.y, ((TouchNode*)touchHash[i])->originalIndex);
            }
            //            NSLog(@"%d", i);
        }
    }
}