Looks like I just had to move my counter into the CCTouchEnded method rather than the swipeLeftComplete/swipeRightComplete methods. :)
Update Incoming to my Gesture Detection Class
(42 posts) (22 voices)-
Posted 1 year ago #
-
I needed some Gesture code for a game I'm currently working on, it needed to emulate 4 directions with swipe controls to move the player around a maze like arena.
Firstly thanks to Metric for providing the initial code as it seemed nice and simple and perfect for what I needed, and secondly to the others who helped people struggling to get it implemented.
It was very easy to bolt in as a Delegate, after testing though I discovered it had a few bugs, and needed a bit of work to get it running how I wanted. The swipes didn't seem to be registering correctly and the code seemed to kill the array of points incorrectly.
Please note, my comments are not meant to be critical, I just gratefully used this code, and made it work how I though it should...I'll first explain what I wanted it to do and then provide the modified working code below.
I wanted the user to be able to swipe a direction say left, then the player onscreen moved in that direction. If the user continued to swipe left then I wanted the gesture controls to continue to register swipes. So a long swipe movement across the screen may register multiple *left* swipes for instance. I like it this way because it emulates the way a digital joystick would work, polling the joystick would result in a constant stream of "I want to move left" messages from the hardware.
Also I wanted the user to, for example, swipe right, pause then swipe up without removing the finger off the screen, this would register a swipe right, followed by a swipe up being registered.In the code as well as changing it slightly I removed some loops that used to work out leftMost, rightMost points touched etc... This wasn't needed as it could be calculated when the points are added to the points array.
I only addressed swipes left/right/up & down, not circle, square or XGesture, I will adress these in the future, when I need them or perhaps if requested, but only when I get my Dev License payed for so I can actually work on the device.
There are comments at the top of the .m file that I used to mark my progress in the last couple of days whilst fixing it, and for people on here to see what I did and how it works...
In fact if you do try this on the device, please let me know if it works firstly, which I imagine/hope it will, and secondly if the values needed tweaking much?
The only 2 files I have modified are Gestures.m & Gestures.h, I will paste the code below for you to use if you wish.
// // Gestures.h // // Containment for all Gesture Motions // // Created by Aaron Klick on 10/15/09. // Copyright 2009 Vantage Technic. All rights reserved. // #import <UIKit/UIKit.h> #import "CGPointUtils.h" #import "CircleGesture.h" #import "XGesture.h" #import "SquareGesture.h" //Protocol for Gesture Delegate @protocol GestureComplete <NSObject> @optional -(void) circleComplete:(CGCircle) circle; -(void) xComplete:(CGX) x; -(void) squareComplete:(CGSquare) square; -(void) swipeLeftComplete; -(void) swipeRightComplete; -(void) swipeUpComplete; -(void) swipeDownComplete; @end @interface Gestures : NSObject { CircleGesture *_circle; XGesture *_x; SquareGesture *_square; NSMutableArray *_allPoints; CGPoint _firstTouch; CGPoint _lastTouch; CGPoint _leftMostPoint; CGPoint _rightMostPoint; CGPoint _topMostPoint; CGPoint _bottomMostPoint; NSTimeInterval _firstTouchTime; NSTimeInterval _lastTouchTime; id <GestureComplete> _delegate; int _swipeTolerance; int _swipeLengthTolerance; double _swipeTimeOutTolerance; int _pointMaxLimit; BOOL _useSwipes; BOOL _useSquare; BOOL _useCircle; BOOL _useX; } + (Gestures *) sharedGestures; - (id) init; @property (readonly) CircleGesture *circle; @property (readonly) NSMutableArray *points; @property (readonly) XGesture *x; @property (readonly) SquareGesture *square; @property (assign) id <GestureComplete> delegate; @property (readwrite) int pointMaxLimit; @property (readwrite) BOOL useSwipes; @property (readwrite) BOOL useSquare; @property (readwrite) BOOL useCircle; @property (readwrite) BOOL useX; @property (readwrite) int swipeTolerance; @property (readwrite) int swipeLengthTolerance; @property (readwrite) double swipeTimeOutTolerance; @property (readonly) CGPoint firstTouch; @property (readonly) CGPoint lastTouch; @property (readonly) CGPoint leftMostPoint; @property (readonly) CGPoint rightMostPoint; @property (readonly) CGPoint topMostPoint; @property (readonly) CGPoint bottomMostPoint; @property (readonly) NSTimeInterval firstTouchTime; @property (readonly) NSTimeInterval lastTouchTime; -(BOOL) isSwipeLeft; -(BOOL) isSwipeRight; -(BOOL) isSwipeUp; -(BOOL) isSwipeDown; -(void) addPoint:(CGPoint) point; -(void) configure:(BOOL) checkCircle checkSquare:(BOOL) checkSquare checkX:(BOOL) checkX checkSwipes:(BOOL) checkSwipes resetLimit:(int) number; /* No longer required see comments at top of .m file -(CGPoint) getLeft; -(CGPoint) getRight; -(CGPoint) getDown; -(CGPoint) getUp; */ -(CGPoint) accelerationSpeed; -(void) reset; -(CGPoint) calculateVelocity:(CGPoint) point1 point2:(CGPoint) point2 duration:(CGFloat) duration; @endI'll do this as 2 post, as I'm not sure if the above will appear correctly, so all well, next post will contain the .m file....
Frank.
Posted 11 months ago # -
Good, it appears to have appeared properly, the wonders of modern technology...
As promised here's the .m file, and the extra bits needed...
// // Gestures.m // // Created by Aaron Klick on 10/15/09. // Copyright 2009 Vantage Technic. All rights reserved. // // Corrected formatting inconsistances and made to work better & faster by Frank Serpico on 8/6/11 // // Currently swipe left/right/up/down, bugged, these issues need resolving. // Have not yet addressed the other types of swipe as I did not need them. // // 1. Holding a contiuous swipe, kills all points if held down longer than "pointResetLimit", this is wrong and should simply stop writing to the points array not kill it! // 1. *Fixed*, changed code, a continuous swipe will not kill the array, it'll just stop filling it until player lifts finger from screen or a successful swipe is executed. // // 2. A swipe is not always registered, in fact, it barely ever seems to register a swipe at all, but it does at least get the type of swipe correct. // 2. *Fixed*, along with code for fixing 1. above, registers properly now. // // 3. The Maths for detection the swipe types seems solid, just the actual detection of a single swipe needs fixing. // 3. *Fixed*, detection fixed and working. // // 4. Functions for detecting leftMost, rightMost point (ie getLeft, getRight etc...), may possibly be added to code for adding point, saves going through a loop. // 4. Code in "addPoint", should continuously register, the leftMost, rightMost, downMost and upMost points, these 4 points then used for detecting swipes, no looping needed. // 4. *Fixed*, loops no longer needed, these 4 points are worked out when the point is saved in the array of points, so much quiker code. // // 5. Swipes need to be rocognised even if finger is not released from screen, add more options regarding this. // 5. *Fixed*, see 1, a continous swipe would result in 1 swipe being registered, a pause then another swipe without lifting is address in 6. next. // // 6. If the player swipes then pauses "swipeTimeOutTolerance" is used to calculate the allowed pause before resetting the swipe array and detecting a new swipe. // 6 *Fixed*, if the player pauses for the time set in "swipeTimeOutTolerance" then the array is reset and the swipe is restarted, var is a DOUBLE in seconds. // // Will remove or adjust above comments when fixed in next few days, then this will be a solid gesture recogniser, that's quick and easy to implement. // // Left in lots of CCLOGs in "addPoint" & "isSwipeLeft" so anyone that uses this can understand better what's heppening when, if you remove the comment slashes. // // // Most importantly, as yet this has not been tested on a actual device because I can't do that yet, so tweaking may need to be done to initial values. #import "Gestures.h" #import "cocos2d.h" // Inlcuded so I can use CCLOG instead of NSLog @implementation Gestures @synthesize circle = _circle, points = _allPoints, firstTouch = _firstTouch, lastTouch = _lastTouch, pointMaxLimit = _pointMaxLimit; @synthesize firstTouchTime = _firstTouchTime, lastTouchTime = _lastTouchTime, x = _x, square = _square, delegate = _delegate, swipeTolerance = _swipeTolerance; @synthesize swipeLengthTolerance = _swipeLengthTolerance, swipeTimeOutTolerance = _swipeTimeOutTolerance; @synthesize useX = _useX, useCircle = _useCircle, useSquare = _useSquare, useSwipes = _useSwipes; @synthesize leftMostPoint = _leftMostPoint, rightMostPoint = _rightMostPoint, topMostPoint = _topMostPoint, bottomMostPoint = _bottomMostPoint; static Gestures *sharedGestures; -(void) dealloc { [_circle release]; [_x release]; [_allPoints release]; [_square release]; [super dealloc]; } //Init + (Gestures *) sharedGestures { @synchronized(self) { if(sharedGestures == nil) { sharedGestures = [[Gestures alloc] init]; } } return sharedGestures; } +(id) allocWithZone:(NSZone *)zone { @synchronized(self) { if(sharedGestures == nil) { sharedGestures = [super allocWithZone:zone]; return sharedGestures; } } return nil; } -(id)copyWithZone:(NSZone *)zone { return self; } -(id)retain { return self; } -(unsigned)retainCount { return UINT_MAX; } -(void)release { //do nothing } -(id)autorelease { return self; } -(id) init { self = [super init]; if(self != nil) { CCLOG(@"Gestures: Initializing Singleton"); _circle = [[CircleGesture alloc] init]; _x = [[XGesture alloc] init]; _square = [[SquareGesture alloc] init]; _allPoints = [[NSMutableArray alloc] initWithCapacity:10]; _firstTouch = CGPointZero; _firstTouchTime = [NSDate timeIntervalSinceReferenceDate]; _lastTouch = CGPointZero; _lastTouchTime = [NSDate timeIntervalSinceReferenceDate]; _swipeTolerance = 40; // Amount of off Axis movement allowed during swipe _swipeLengthTolerance = 20; // Minimum length of swipe allowed _pointMaxLimit = 10; // Amount of recorded Points in array for a swipe _swipeTimeOutTolerance = 1.0000;// Amount of time in seconds finger allowed at rest before new swipe is registered _useCircle = YES; _useSquare = YES; _useX = YES; _useSwipes = YES; } return self; } // Adds a point to the _allPoints array // // If "pointResetLimit" is reached, then no more points should be added... // ...also the array should not be destroyed at this point, but it should trigger the swipe to be executed. // // The only time the array should be destroyed is; // 1. When the player removes his finger from the screen. // 2. After the swipe has been executed successfully & player has rested finger for stated amount of time. // 3. When there is a pause longer than "swipeTimeOutTolerance" // // Also "pointResetLimit" should be renamed to "pointMaxLimit", as this better describes the new codes action // - (void) addPoint:(CGPoint) point { BOOL isComplete = NO; int pointCount = [_allPoints count]; // CCLOG(@"Gestures: Adding point"); // If player has taken too long or paused whilst touching the screen, the swipe is cancelled. if (_lastTouchTime - _firstTouchTime > _swipeTimeOutTolerance) { pointCount = 0; // CCLOG(@"Gestures: Timed out, reseting... *****************"); [self reset]; } if(pointCount == 0) { _firstTouch = point; _firstTouchTime = [NSDate timeIntervalSinceReferenceDate]; _leftMostPoint = point; _rightMostPoint = point; _topMostPoint = point; _bottomMostPoint = point; // CCLOG(@"Gestures: First Touch Set"); } _lastTouchTime = [NSDate timeIntervalSinceReferenceDate]; // CCLOG(@"Gestures: firsTouchTime:%f lastTouchTime:%f difference:%f", _firstTouchTime, _lastTouchTime, _lastTouchTime - _firstTouchTime); // Only add a new point if the "pointResetLimit" is not yet reached if (pointCount < _pointMaxLimit) { [_allPoints addObject:NSStringFromCGPoint(point)]; _lastTouch = point; // Now work out if this point is the uppermost, leftmost, rightmost or bottom most point if (point.x < _leftMostPoint.x) _leftMostPoint = point; if (point.x > _rightMostPoint.x) _rightMostPoint = point; if (point.y > _topMostPoint.y) _topMostPoint = point; if (point.y < _bottomMostPoint.y) _bottomMostPoint = point; // CCLOG(@"Gestures: New point added at position:%d", pointCount); } if (pointCount >= _pointMaxLimit) { // CCLOG(@"Gestures: Testing gesture...:%d", pointCount); if(_useX) { if([_x isX:_firstTouch last:_lastTouch]) { if([_delegate respondsToSelector:@selector(xComplete:)]) { isComplete = YES; [_delegate xComplete:[_x getX]]; } } } if(_useSquare) { if([_square isCompleteSquare:_allPoints first:_firstTouch last:_lastTouch firstTime:_firstTouchTime lastTime:_lastTouchTime]) { if([_delegate respondsToSelector:@selector(squareComplete:)]) { isComplete = YES; [_delegate squareComplete:[_square getSquare]]; } } } if(_useCircle) { if([_circle isCompleteCircle:_allPoints first:_firstTouch last:_lastTouch firstTime:_firstTouchTime lastTime:_lastTouchTime]) { if([_delegate respondsToSelector:@selector(circleComplete:)]) { isComplete = YES; CCLOG(@"Gestures: Calling Circle Complete Delegate"); [_delegate circleComplete:[_circle getCircle]]; } } } if(_useSwipes) { if([self isSwipeLeft]) { if([_delegate respondsToSelector:@selector(swipeLeftComplete)]) { isComplete = YES; [_delegate swipeLeftComplete]; } } else if([self isSwipeRight]) { if([_delegate respondsToSelector:@selector(swipeRightComplete)]) { isComplete = YES; [_delegate swipeRightComplete]; } } else if([self isSwipeUp]) { if([_delegate respondsToSelector:@selector(swipeUpComplete)]) { isComplete = YES; [_delegate swipeUpComplete]; } } else if([self isSwipeDown]) { if([_delegate respondsToSelector:@selector(swipeDownComplete)]) { isComplete = YES; [_delegate swipeDownComplete]; } } } } if(isComplete) [self reset]; } - (void) reset { _firstTouch = CGPointZero; _firstTouchTime = [NSDate timeIntervalSinceReferenceDate]; _lastTouch = CGPointZero; _lastTouchTime = [NSDate timeIntervalSinceReferenceDate]; [_allPoints removeAllObjects]; } - (void) configure:(BOOL) checkCircle checkSquare:(BOOL) checkSquare checkX:(BOOL) checkX checkSwipes:(BOOL) checkSwipes resetLimit:(int) number; { _useX = checkX; _useSquare = checkSquare; _useCircle = checkCircle; _useSwipes = checkSwipes; _pointMaxLimit = number; } // Test to see if the user has swiped to the left. // // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance // // Updated comments on untouched code... // Tolerance is not used exclusively as described above. // It is used for how far the swipe moves or wanders away from the axis along which the swipe is detected as well as the length of the swipe // ...So for left swipe, if the swipe moves upwards or downwards (or off-axis) by the Tolerance amount, then the gesture fails // This is fine, but the description is wrong and should be noted // // After changes... // Now "swipeTolerance" is exclusively as the amount of off-axis movement allowed in a swipe gesture. // Added "swipeLengthTolerance" as the length of the swipe that the user must move their finger before the swipe is registered. - (BOOL) isSwipeLeft { // CCLOG(@"Gestures: Testing swipe left gesture..."); CGPoint left; // left = [self getLeft]; left = _leftMostPoint; if((left.x - _firstTouch.x) < 0) { // Check if the leftmost point in the array is further left then the first touched point int length = abs((left.x - _firstTouch.x)); int distanceY = abs((left.y - _firstTouch.y)); // CCLOG(@"Gestures: Swipe left length:%d, left.x:%f, firstTouch.x:%f", length, left.x, _firstTouch.x); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceY < _swipeTolerance) { CCLOG(@"Gestures: Swipe left gesture ***SUCCESS***"); return YES; } } // CCLOG(@"Gestures: Swipe left gesture ***FAIL***"); // [self reset]; return NO; } // Tests to see if the user has swiped to the right // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance - (BOOL) isSwipeRight { CGPoint right; // right = [self getRight]; right = _rightMostPoint; if((_firstTouch.x - right.x) < 0) { int length = abs((_firstTouch.x - right.x)); int distanceY = abs((_firstTouch.y - right.y)); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceY < _swipeTolerance) { CCLOG(@"Gestures: Swipe right gesture ***SUCCESS***"); return YES; } } // [self reset]; return NO; } // Tests to see if the user has swiped up // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance - (BOOL) isSwipeUp { CGPoint up; // up = [self getUp]; up = _topMostPoint; if((_firstTouch.y - up.y) < 0) { int length = abs((_firstTouch.y - up.y)); int distanceX = abs((_firstTouch.x - up.x)); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceX < _swipeTolerance) { CCLOG(@"Gestures: Swipe up gesture ***SUCCESS***"); return YES; } } // [self reset]; return NO; } // Tests to see if the user has swiped down // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance - (BOOL) isSwipeDown { CGPoint down; // down = [self getDown]; down = _bottomMostPoint; if((down.y - _firstTouch.y) < 0) { int length = abs((down.y - _firstTouch.y)); int distanceX = abs((down.x - _firstTouch.x)); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceX < _swipeTolerance) { CCLOG(@"Gestures: Swipe down gesture ***SUCCESS***"); return YES; } } // [self reset]; return NO; } // Calculates the velocity based on given duration and points // @return CGPoint -(CGPoint) calculateVelocity:(CGPoint) point1 point2:(CGPoint) point2 duration:(CGFloat) duration { CGFloat accelerationY; CGFloat accelerationX; float distanceX; float distanceY; if (duration <= 0) duration = 0.1; distanceY = point2.y - point1.y; distanceX = point2.x - point1.x; accelerationX = (distanceX / (duration * duration)); accelerationY = (distanceY / (duration * duration)); return CGPointMake(accelerationX, accelerationY); } // Calculates a simple acceleration speed based on distance and time // from the first touch point to the last touch point - (CGPoint) accelerationSpeed { CGFloat totalTime; float distanceX; float distanceY; CGFloat accelerationX; CGFloat accelerationY; totalTime = _lastTouchTime - _firstTouchTime; distanceX = _lastTouch.x - _firstTouch.x; distanceY = _lastTouch.y - _firstTouch.y; accelerationX = (distanceX / (totalTime*totalTime)); accelerationY = (distanceY / (totalTime*totalTime)); return CGPointMake(accelerationX, accelerationY); } // Below no longer needed, see comments at top /* // Gets the left most point of the touches - (CGPoint) getLeft { CGPoint leftMost; leftMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.x < leftMost.x) { leftMost = onePoint; } } return leftMost; } // Gets the right most point of the touches - (CGPoint) getRight { CGPoint rightMost; rightMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.x > rightMost.x) { rightMost = onePoint; } } return rightMost; } // Gets the down most point of the touches - (CGPoint) getDown { CGPoint downMost; downMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.y < downMost.y) { downMost = onePoint; } } return downMost; } // Gets the up most point of the touches - (CGPoint) getUp { CGPoint upMost; upMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.y > upMost.y) { upMost = onePoint; } } return upMost; } */ @endThis is how I initialise the Gesture controls...
// Initilase the gesture controls. touched = FALSE; [Gestures sharedGestures].delegate = self; [Gestures sharedGestures].swipeTolerance = 40; [Gestures sharedGestures].swipeLengthTolerance = 20; [Gestures sharedGestures].swipeTimeOutTolerance = 0.78000; [Gestures sharedGestures].pointMaxLimit = 8; [Gestures sharedGestures].useX = NO; [Gestures sharedGestures].useCircle = NO; [Gestures sharedGestures].useSquare = NO;And my touchedBegan and Moved Funcs...
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { [[Gestures sharedGestures] reset]; return YES; } -(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint point = [touch locationInView: [touch view]]; CGPoint converted = [[CCDirector sharedDirector] convertToGL:point]; // CCLOG(@"Touched point x:%f, y:%f", point.x, point.y); [[Gestures sharedGestures] addPoint:converted]; }Hope this helps, the initial code I must thank Metric for once again, as it saved me starting from scratch...
Any questions I will answer as best I can, plus I may have made the odd mistake with this post here and there...Frank...
Posted 11 months ago # -
Hi
I managed to get the above code to call and show up in the log when the sprite is moved.
However, could someone advise me how to use the gestures? How would i call a 'swipe up' gesture to do something (like animation or action)?
I have tried to place the action code in gestures with no luck. Would I call the gesture in my main scene - if so how?Thanks in advance
David
Posted 10 months ago # -
Hi David,
Sorry for late reply been working hard on my game this week.
Yes indeed you would put the callback function in your main scene where you initialise the Gesture Controls
This call [_delegate swipeLeftComplete]; from the addPoint: function in Gestures.m...
if([self isSwipeLeft]) { if([_delegate respondsToSelector:@selector(swipeLeftComplete)]) { isComplete = YES; [_delegate swipeLeftComplete]; }It will look for "swipeLeftComplete" function where you implemented and initialised the Gestures, in your case mainScene.m I presume...
In you mainScene.m you should have this code which although I forgot to add is described earlier on the previous page in this post.
// Swipe gesture complete Functions -(void) swipeUpComplete { CCLOG(@"**** Swipe UP ****"); [player1 setDirection:kEntityMovement_MovingUp]; } -(void) swipeDownComplete { CCLOG(@"**** Swipe DOWN ****"); [player1 setDirection:kEntityMovement_MovingDown]; } -(void) swipeLeftComplete { CCLOG(@"**** Swipe LEFT ****"); [player1 setDirection:kEntityMovement_MovingLeft]; } -(void) swipeRightComplete { CCLOG(@"**** Swipe RIGHT ****"); [player1 setDirection:kEntityMovement_MovingRight]; }You can see my code is calling a movement function for my player, which you should remove before putting in your code, my player takes this input, treats it like a digital 4-way joypad and acts on it accordingly.
So basically the callback to your main function is where you act upon the inputs from the swipe code, and set any flags you may need, up/down/left/right movement as I do, or a direction vector with velocity, or whatever your prefer or need.
Happy to help.
Z.
Posted 10 months ago # -
OK,
I've been using this on device for a short while now, and to be honest whilst it works perfectly well within the confines of how it was written for the emulator, it's a bit poor at detecting proper swipes of different types from different people.
My idea of having the code give you multiple swipes when you don't lift your finger was rubbish quite frankly so I've changed it. I only realised this when I finally got it going on the actual device, seeing people swipe and fail, then me telling them to swipe slower was not how it should be done.
So, I fixed it....
I have only addressed the left/right/up/down swipes, not circle, X or square as I haven't needed them, but will do this when needed, or if anyone asks me nicely enough and I've got time.** Backup first before making any changes ***
Here is the new code for Gestures.m, if you read the comments at the top of it you will see what I did to fix and change it...// // Gestures.m // // Created by Aaron Klick on 10/15/09. // Copyright 2009 Vantage Technic. All rights reserved. // // Corrected formatting inconsistances and made to work better & faster by Frank Serpico on 8/6/11 // And made to work for people with Saussage fingers on 23/9/11 // // Currently swipe left/right/up/down, bugged, these issues need resolving. // Have not yet addressed the other types of swipe as I did not need them. // // 1. Holding a contiuous swipe, kills all points if held down longer than "pointResetLimit", this is wrong and should simply stop writing to the points array not kill it! // 1. *Fixed*, changed code, a continuous swipe will not kill the array, it'll just stop filling it until player lifts finger from screen or a successful swipe is executed. // // 2. A swipe is not always registered, in fact, it barely ever seems to register a swipe at all, but it does at least get the type of swipe correct. // 2. *Fixed*, along with code for fixing 1. above, registers properly now. // // 3. The Maths for detection the swipe types seems solid, just the actual detection of a single swipe needs fixing. // 3. *Fixed*, detection fixed and working. // // 4. Functions for detecting leftMost, rightMost point (ie getLeft, getRight etc...), may possibly be added to code for adding point, saves going through a loop. // 4. Code in "addPoint", should continuously register, the leftMost, rightMost, downMost and upMost points, these 4 points then used for detecting swipes, no looping needed. // 4. *Fixed*, loops no longer needed, these 4 points are worked out when the point is saved in the array of points, so much quiker code. // // 5. Swipes need to be rocognised even if finger is not released from screen, add more options regarding this. // 5. *Fixed*, see 1, a continuous swipe would result in 1 swipe being registered, a pause then another swipe without lifting is address in 6. next. // // 6. If the player swipes then pauses "swipeTimeOutTolerance" is used to calculate the allowed pause before resetting the swipe array and detecting a new swipe. // 6 *Fixed*, if the player pauses for the time set in "swipeTimeOutTolerance" then the array is reset and the swipe is restarted, var is a DOUBLE in seconds. // // Will remove or adjust above comments when fixed in next few days, then this will be a solid gesture recogniser, that's quick and easy to implement. // // Left in lots of CCLOGs in "addPoint" & "isSwipeLeft" so anyone that uses this can understand better what's happening when, if you remove the comment slashes. // // // Most importantly, as yet this has not been tested on a actual device because I can't do that yet, so tweaking may need to be done to initial values. // // *************** New update on 23/9/11 ************** // OK, After testing on device the swipe of a normal/average/me player is very quick and most often doesn't get picked up. // A slow steady swipe still works perfectly but doesn't suit the many different ways different people will swipe, so..... // Point 5. above I now declare is wrong, a swipe should be registered when finger is lifted, no matter how long the points array is. // This will allow for different types of swipes from different people, and not rely on a restricted minimum or maximum length of swipe. // So, a continuous finger swipe in 1 direction should not register multiple swipes, just one, after the Player has finished swiping. // // Now fixed... // A continuous swipe will only be registered when finger is lifted, no matter how long the swipe is up to the increased "_pointMaxLimit" of 60 // Achieved by splitting the "addPoint" method into 2 parts, handling adding points and detecting in separate places; // 1st part adds the points to the array as the swipe is being executed, called from same place as before. // 2nd part called from your main function where touches are detected, specifically from "ccTouchEnded" checks if the swipe is valid. // The "_pointMaxLimit" has also been increased to 60 to allow for large swipes, increase further if needed on iPad for example. // Also make sure you set _pointMaxLimit = 60; in your own setup code, otherwise it will most probably be set too low!!! // Anyone who tries this new version out, please let me know if your happy with it, it should be fine once the new fix is done to Point 5 above. // My experience on the changes is it works great, picks up quick short swipes, long slow ones, much better on device than it used to be. // Perhaps off-axis tolerance may need to be adjusted slightly higher for people who swipe at more of an angle, you know, people with big oversized sausage thumbs for example ;) #import "Gestures.h" #import "cocos2d.h" // Included so I can use CCLOG instead of NSLog @implementation Gestures @synthesize circle = _circle, points = _allPoints, firstTouch = _firstTouch, lastTouch = _lastTouch, pointMaxLimit = _pointMaxLimit; @synthesize firstTouchTime = _firstTouchTime, lastTouchTime = _lastTouchTime, x = _x, square = _square, delegate = _delegate, swipeTolerance = _swipeTolerance; @synthesize swipeLengthTolerance = _swipeLengthTolerance, swipeTimeOutTolerance = _swipeTimeOutTolerance; @synthesize useX = _useX, useCircle = _useCircle, useSquare = _useSquare, useSwipes = _useSwipes; @synthesize leftMostPoint = _leftMostPoint, rightMostPoint = _rightMostPoint, topMostPoint = _topMostPoint, bottomMostPoint = _bottomMostPoint; static Gestures *sharedGestures; -(void) dealloc { [_circle release]; [_x release]; [_allPoints release]; [_square release]; [super dealloc]; } //Init +(Gestures*) sharedGestures { @synchronized(self) { if(sharedGestures == nil) { sharedGestures = [[Gestures alloc] init]; } } return sharedGestures; } +(id)allocWithZone:(NSZone *)zone { @synchronized(self) { if(sharedGestures == nil) { sharedGestures = [super allocWithZone:zone]; return sharedGestures; } } return nil; } -(id)copyWithZone:(NSZone *)zone { return self; } -(id)retain { return self; } -(unsigned)retainCount { return UINT_MAX; } -(void)release { //do nothing } -(id)autorelease{ return self; } -(id)init{ self = [super init]; if(self != nil) { CCLOG(@"Gestures: Initializing Singleton"); _circle = [[CircleGesture alloc] init]; _x = [[XGesture alloc] init]; _square = [[SquareGesture alloc] init]; _allPoints = [[NSMutableArray alloc] initWithCapacity:10]; _firstTouch = CGPointZero; _firstTouchTime = [NSDate timeIntervalSinceReferenceDate]; _lastTouch = CGPointZero; _lastTouchTime = [NSDate timeIntervalSinceReferenceDate]; _swipeTolerance = 40; // Amount of off Axis movement allowed during swipe _swipeLengthTolerance = 20; // Minimum length of swipe allowed _pointMaxLimit = 60; // Amount of recorded Points in array for a swipe _swipeTimeOutTolerance = 1.0000; // Amount of time in seconds finger allowed at rest before new swipe is registered _useCircle = YES; _useSquare = YES; _useX = YES; _useSwipes = YES; } return self; } // Adds a point to the _allPoints array // // If "pointResetLimit" is reached, then no more points should be added... // ...also the array should not be destroyed at this point, but it should trigger the swipe to be executed. // // The only time the array should be destroyed is; // 1. When the player removes his finger from the screen. // 2. After the swipe has been executed successfully & player has rested finger for stated amount of time. // 3. When there is a pause longer than "swipeTimeOutTolerance" // // Also "pointResetLimit" should be renamed to "pointMaxLimit", as this better describes the new codes action // - (void)addPoint:(CGPoint)point { // BOOL isComplete = NO; int pointCount = [_allPoints count]; CCLOG(@"Gestures: Adding point"); // If player has taken too long or paused whilst touching the screen, the swipe is cancelled. if (_lastTouchTime - _firstTouchTime > _swipeTimeOutTolerance) { pointCount = 0; // CCLOG(@"Gestures: Timed out, reseting... *****************"); [self reset]; } if(pointCount == 0) { _firstTouch = point; _firstTouchTime = [NSDate timeIntervalSinceReferenceDate]; _leftMostPoint = point; _rightMostPoint = point; _topMostPoint = point; _bottomMostPoint = point; // CCLOG(@"Gestures: First Touch Set"); } _lastTouchTime = [NSDate timeIntervalSinceReferenceDate]; // CCLOG(@"Gestures: firsTouchTime:%f lastTouchTime:%f difference:%f", _firstTouchTime, _lastTouchTime, _lastTouchTime - _firstTouchTime); // Only add a new point if the "pointResetLimit" is not yet reached if (pointCount < _pointMaxLimit) { [_allPoints addObject:NSStringFromCGPoint(point)]; _lastTouch = point; // Now work out if this point is the uppermost, leftmost, rightmost or bottom most point if (point.x < _leftMostPoint.x) _leftMostPoint = point; if (point.x > _rightMostPoint.x) _rightMostPoint = point; if (point.y > _topMostPoint.y) _topMostPoint = point; if (point.y < _bottomMostPoint.y) _bottomMostPoint = point; CCLOG(@"Gestures: New point added at position:%d", pointCount); } } // Called from "ccTouchEnded" in your Main function, works out whether the recorded points are a gesture. // Whether the Gesture is a success or failure, the points array will be reset as the Player has lifted his finger. -(void)decodeGesture { // if (pointCount >= _pointMaxLimit) { // CCLOG(@"Gestures: Testing gesture...:%d", pointCount); if(_useX) { if([_x isX:_firstTouch last:_lastTouch]) { if([_delegate respondsToSelector:@selector(xComplete:)]) { // isComplete = YES; [_delegate xComplete:[_x getX]]; [self reset]; return; } } } if(_useSquare) { if([_square isCompleteSquare:_allPoints first:_firstTouch last:_lastTouch firstTime:_firstTouchTime lastTime:_lastTouchTime]) { if([_delegate respondsToSelector:@selector(squareComplete:)]) { // isComplete = YES; [_delegate squareComplete:[_square getSquare]]; [self reset]; return; } } } if(_useCircle) { if([_circle isCompleteCircle:_allPoints first:_firstTouch last:_lastTouch firstTime:_firstTouchTime lastTime:_lastTouchTime]) { if([_delegate respondsToSelector:@selector(circleComplete:)]) { // isComplete = YES; CCLOG(@"Gestures: Calling Circle Complete Delegate"); [_delegate circleComplete:[_circle getCircle]]; [self reset]; return; } } } if(_useSwipes) { if([self isSwipeLeft]) { if([_delegate respondsToSelector:@selector(swipeLeftComplete)]) { // isComplete = YES; [_delegate swipeLeftComplete]; } } else if([self isSwipeRight]) { if([_delegate respondsToSelector:@selector(swipeRightComplete)]) { // isComplete = YES; [_delegate swipeRightComplete]; } } else if([self isSwipeUp]) { if([_delegate respondsToSelector:@selector(swipeUpComplete)]) { // isComplete = YES; [_delegate swipeUpComplete]; } } else if([self isSwipeDown]) { if([_delegate respondsToSelector:@selector(swipeDownComplete)]) { // isComplete = YES; [_delegate swipeDownComplete]; } } [self reset]; } // } // if(isComplete) [self reset]; } - (void)reset { _firstTouch = CGPointZero; _firstTouchTime = [NSDate timeIntervalSinceReferenceDate]; _lastTouch = CGPointZero; _lastTouchTime = [NSDate timeIntervalSinceReferenceDate]; [_allPoints removeAllObjects]; } - (void)configure:(BOOL) checkCircle checkSquare:(BOOL) checkSquare checkX:(BOOL) checkX checkSwipes:(BOOL) checkSwipes resetLimit:(int) number; { _useX = checkX; _useSquare = checkSquare; _useCircle = checkCircle; _useSwipes = checkSwipes; _pointMaxLimit = number; } // Test to see if the user has swiped to the left. // // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance // // Updated comments on untouched code... // Tolerance is not used exclusively as described above. // It is used for how far the swipe moves or wanders away from the axis along which the swipe is detected as well as the length of the swipe // ...So for left swipe, if the swipe moves upwards or downwards (or off-axis) by the Tolerance amount, then the gesture fails // This is fine, but the description is wrong and should be noted // // After changes... // Now "swipeTolerance" is exclusively as the amount of off-axis movement allowed in a swipe gesture. // Added "swipeLengthTolerance" as the minimum length of the swipe that the user must move their finger before the swipe is registered. - (BOOL) isSwipeLeft { // CCLOG(@"Gestures: Testing swipe left gesture..."); CGPoint left; // left = [self getLeft]; left = _leftMostPoint; if((left.x - _firstTouch.x) < 0) { // Check if the leftmost point in the array is further left then the first touched point int length = abs((left.x - _firstTouch.x)); int distanceY = abs((left.y - _firstTouch.y)); // CCLOG(@"Gestures: Swipe left length:%d, left.x:%f, firstTouch.x:%f", length, left.x, _firstTouch.x); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceY < _swipeTolerance) { CCLOG(@"Gestures: Swipe left gesture ***SUCCESS***"); return YES; } } // CCLOG(@"Gestures: Swipe left gesture ***FAIL***"); // [self reset]; return NO; } // Tests to see if the user has swiped to the right // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance - (BOOL) isSwipeRight { CGPoint right; // right = [self getRight]; right = _rightMostPoint; if((_firstTouch.x - right.x) < 0) { int length = abs((_firstTouch.x - right.x)); int distanceY = abs((_firstTouch.y - right.y)); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceY < _swipeTolerance) { CCLOG(@"Gestures: Swipe right gesture ***SUCCESS***"); return YES; } } // [self reset]; return NO; } // Tests to see if the user has swiped up // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance - (BOOL) isSwipeUp { CGPoint up; // up = [self getUp]; up = _topMostPoint; if((_firstTouch.y - up.y) < 0) { int length = abs((_firstTouch.y - up.y)); int distanceX = abs((_firstTouch.x - up.x)); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceX < _swipeTolerance) { CCLOG(@"Gestures: Swipe up gesture ***SUCCESS***"); return YES; } } // [self reset]; return NO; } // Tests to see if the user has swiped down // Tolerance is the distance the user has to travel to // signal a complete swipe. // @param tolerance - (BOOL) isSwipeDown { CGPoint down; // down = [self getDown]; down = _bottomMostPoint; if((down.y - _firstTouch.y) < 0) { int length = abs((down.y - _firstTouch.y)); int distanceX = abs((down.x - _firstTouch.x)); // Test if distance of swipe is less than the minimum amount allowed AND how far off axis the swipe is if(length >= _swipeLengthTolerance && distanceX < _swipeTolerance) { CCLOG(@"Gestures: Swipe down gesture ***SUCCESS***"); return YES; } } // [self reset]; return NO; } // Calculates the velocity based on given duration and points // @return CGPoint -(CGPoint) calculateVelocity:(CGPoint) point1 point2:(CGPoint) point2 duration:(CGFloat) duration { CGFloat accelerationY; CGFloat accelerationX; float distanceX; float distanceY; if (duration <= 0) duration = 0.1; distanceY = point2.y - point1.y; distanceX = point2.x - point1.x; accelerationX = (distanceX / (duration * duration)); accelerationY = (distanceY / (duration * duration)); return CGPointMake(accelerationX, accelerationY); } // Calculates a simple acceleration speed based on distance and time // from the first touch point to the last touch point - (CGPoint) accelerationSpeed { CGFloat totalTime; float distanceX; float distanceY; CGFloat accelerationX; CGFloat accelerationY; totalTime = _lastTouchTime - _firstTouchTime; distanceX = _lastTouch.x - _firstTouch.x; distanceY = _lastTouch.y - _firstTouch.y; accelerationX = (distanceX / (totalTime*totalTime)); accelerationY = (distanceY / (totalTime*totalTime)); return CGPointMake(accelerationX, accelerationY); } // Below no longer needed, see comments at top /* // Gets the left most point of the touches - (CGPoint) getLeft { CGPoint leftMost; leftMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.x < leftMost.x) { leftMost = onePoint; } } return leftMost; } // Gets the right most point of the touches - (CGPoint) getRight { CGPoint rightMost; rightMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.x > rightMost.x) { rightMost = onePoint; } } return rightMost; } // Gets the down most point of the touches - (CGPoint) getDown { CGPoint downMost; downMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.y < downMost.y) { downMost = onePoint; } } return downMost; } // Gets the up most point of the touches - (CGPoint) getUp { CGPoint upMost; upMost = _firstTouch; for (NSString *onePointString in _allPoints){ CGPoint onePoint = CGPointFromString(onePointString); if (onePoint.y > upMost.y) { upMost = onePoint; } } return upMost; } */ @endSave the file above over the old one and add these lines to your main code or mainScene.m as I talk about above....
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint touchLocation = [touch locationInView: [touch view]]; touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation]; // End of touched, see if this results in a swipe movement [[Gestures sharedGestures] decodeGesture];...and at the top where you initialise the Gesture stuff change this line to.
[Gestures sharedGestures].pointMaxLimit = 60; // Amount of points recorded during swipingI think I left in some CCLOGs, so remove those once sorted and happy...
I think I've put everything in here that's needed, so enjoy, hope it helps, and let me know of any issues you might have with it.
Z.
Posted 8 months ago # -
"I have only addressed the left/right/up/down swipes, not circle, X or square as I haven't needed them, but will do this when needed, or if anyone asks me nicely enough and I've got time."
I've tried a number of examples found in books/online, but haven't been able to find a reliable way to detect a checkmark gesture. If you have time, I would certainly appreciate your suggestions/code for that gesture.
Posted 8 months ago # -
Hi,
I haven't got time to actually right the code for you but I could maybe help you out with a few suggestions.
If you read this post from the beginning you will see what I mean by I haven't addressed those aspects of the original code, you may have done so already, cool.
So I only updated and got the swipes in a specific direction to work. If you examine the code you'll see a swipe called X, this is very similar to what you need to achieve, a little tweaking to the X code called XGesture.h/,m, or copy it and make a new Tick or Checkmark code chunk.Setting [Gestures sharedGestures].useX = YES; in the initialisation would turn on the code to detect an "X" gesture, you will need to get this working first, then make your modification to this which is similar to recognise a Checkmark, should be relatively easy once you understand the "X" code.
I think just setting it to YES may actually work I haven't tried it, but because I had to make changes to the swipes I would guess it may not be perfect, but a little tweaking may get going just right. I have allowed for the other Gestures to work in my additions, so it might only need slight adjustments to get it working fully, and then change the code so it detects a Tick/Checkmark instead of and X which is very similar.
Z.
Posted 8 months ago # -
Correction to above, the "x" will not work, it will try to figure out if it happened and the code runs, but as I changed the code to reset the points array in "decodeGesture" once the finger is lifted it will not detect "X".
But as you need to detect a Checkmark, a change to this "x" code still stands as said earlier...Posted 8 months ago # -
Can someone please help with circle detection? I want to detect the number of times a circle is made even before the touch is released. What I want is to be able to detect multiple gestures in one instance of the user touch down. For example, I want to detect two circle's and one swipe left/right. I have tried playing with code but somehow it just does not work out, either no circle is detected or even before the circle is complete I register up to 11 circles and sometimes even after a circle is complete i register multiple circles even when Im actually not making one on the screen. Any help is appreciated.
Posted 7 months ago # -
Hi,
has anyone got a small example application that shows how to use this?
Sure a lot of people would appreciate a working example.
Thank you in advance.
Shane
Posted 6 months ago #
Reply
You must log in to post.