Search code examples
objective-ciosmulti-touchtouchesbegan

multitouch results in methods firing twice


I'm working on a little game that divides the ipad screen in a lower half and upper half. each player has his own section to play with (one finger play). I had to enable multitouch on the view because, when two players would hit the screen at the same time, only the methods for 1 player would be executed or none at all.

however this results in weird behavior of methods firing twice when 2 players do exactly the same action. this is how it works now: it checks if the players touched an object in the view. if they have, a method fires. if a player hasen't touched an object but the view itself, another method fires. it checks whether the view is touched in the upper or lower half to distinct the touches from player one or player two.

I've been thinking of a solution but I'm not sure what is a good way to solve this. maybe I should have to separate views (transparent) for each player so I can more easily distinct the touches? here's my touchesBegan method.

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

    NSMutableSet *touchedCirclesPlayerOne = [NSMutableSet set];
    NSMutableSet *touchedCirclesPlayerTwo = [NSMutableSet set];

    for (UITouch *touch in touches) {
        CGPoint touchLocation = [touch locationInView:self.view];
        for (Circle *circle in playerOneCircles) {
            if ([circle.layer.presentationLayer hitTest:touchLocation]) {
                [touchedCirclesPlayerOne addObject:circle];

            } 
        }

    for (UITouch *touch in touches) {
        CGPoint touchLocation = [touch locationInView:self.view];
        for (Circle *circle in playerTwoCircles) {
            if ([circle.layer.presentationLayer hitTest:touchLocation]) {
                [touchedCirclesPlayerTwo addObject:circle];

                } 
            }
    }

    if (touchedCirclesPlayerOne.count) {

        NSLog(@"test");

        for (Circle *circle in touchedCirclesPlayerOne) {

            [circle playerTap:nil];

        }

    } else if (touchedCirclesPlayerTwo.count) {

        NSLog(@"test2");

        for (Circle *circle in touchedCirclesPlayerTwo) {

            [circle SecondPlayerTap:nil];

        }


    } else {

        for (UITouch *touch in touches) {
            CGPoint touchLocation = [touch locationInView:self.view];

            if (CGRectContainsPoint(CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2), touchLocation)) {

            NSLog(@"wrong player 1");
            [[NSNotificationCenter defaultCenter] postNotificationName:@"wrong player 1" object:self];

            }  else {

                NSLog(@"wrong player 2");
                [[NSNotificationCenter defaultCenter] postNotificationName:@"wrong player 2" object:self];
            }

        }


    }

}

}

here's a little schematic. this all works except when two players do the same thing. it fires the same action twice for each player.

schematic


Solution

  • That code looks familiar :-). However, what you have there does lots of extra work. Also, if there are two taps, it will alway do player 1's tap, and not player 2's tap.

    Do you really want to enforce one tap per player area? Note, that this code doe not prevent that. You can begin another touch after a previous touch, and that will not be in the set. However, you can see all current touches, regardless of their state by looking at the object. Query allTouches and it will give you all current touches on the screen.

    Consider the following changes...

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // Since this rect object is always needed, you probably want to
        // make it once, and keep it as private variables.  If your area is
        // not really divided completely in half, you will want two separate
        // rects, each defining the area for each player.
        CGRect player1Area = CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2);
    
        // For performance sake, we will loop through the touches a single time
        // and process each tap in that loop.  Use these flags to later determine
        // what to do if the player tapped some place other than on a circle.
        BOOL player1Tapped = NO;
        BOOL player2Tapped = NO;
        BOOL player1TappedCircle = NO;
        BOOL player2TappedCircle = NO;
    
        for (UITouch *touch in touches) {
            CGPoint touchLocation = [touch locationInView:self.view];
            if (CGRectContainsPoint(player1Area, touchLocation)) {
                // This touch is in player 1's area
                // Small additional code to restrict player to a single tap.
                player1Tapped = YES;
                for (Circle *circle in playerOneCircles) {
                    // I guess you are using a CAShapeLayer, and looking at the
                    // position during animation?
                    if ([circle.layer.presentationLayer hitTest:touchLocation]) {
                        player1TappedCircle = YES;
                        [circle playerTap:nil];
                    }
                }
            } else {
                // This touch is not for player 1, so must be for player 2...
                player2Tapped = YES;
                for (Circle *circle in playerTwoCircles) {
                    if ([circle.layer.presentationLayer hitTest:touchLocation]) {
                        player2TappedCircle = YES;
                        [circle secondPlayerTap:nil];
                    }
                }
            }
        }
    
        // All touches have now been handled.  We can do stuff based on whether any player
        // has tapped any area, or circles, or whatever.
        if (player1Tapped && !player1TappedCircle) {
            // Player 1 tapped, but did not tap on a circle.
        }
        if (player2Tapped && !player2TappedCircle) {
            // Player 2 tapped, but did not tap on a circle.
        }    
    }