Spritekit - calculate distance the finger has travelled between two events to increment your a NSUInteger - ios

I use the following code to draw a line from point Touch point A to Touch point B.
The code also allows me to draw multiple lines simultaneously.
My first attempt was to create a NSUInteger and increase it by 1 per pixel moved in touchesMoved. However the increment is not consistent. It appears that moving your finger fast will increase the number slowly whereas moving your finger slowly will increase the number fast.
I think I need to calculate the distance moved between two events and then increment the number. I don't know how to do this, can anyone help?
//Create a line at first point of touch
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKLabelNode *touchedNode = (SKLabelNode *)[self nodeAtPoint:positionInScene];
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// Create a mutable path
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:location];
// Create a shape node using the path
lineNode = [SKShapeNode shapeNodeWithPath:path.CGPath];
lineNode.strokeColor = [SKColor blackColor];
lineNode.glowWidth = 3.0;
[_gameLineNode addChild:lineNode];
// Use touch pointer as the dictionary key. Since the dictionary key must conform to
// NSCopying, box the touch pointer in an NSValue
NSValue *key = [NSValue valueWithPointer:(void *)touch];
[lines setObject:lineNode forKey:key];
}
}
// Draw the line to last touch point dynamically
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// Retrieve the shape node that corresponds to touch
NSValue *key = [NSValue valueWithPointer:(void *)touch];
lineNode = [lines objectForKey:key];
if (lineNode != NULL) {
// Create and initial a mutable path with the lineNode's path
UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:lineNode.path];
// Add a line to the current touch point
[path addLineToPoint:location];
// Update lineNode
lineNode.path = path.CGPath;
lineNode.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:path.CGPath];
lineNode.physicsBody.categoryBitMask = lineNodeCategory;
lineNode.physicsBody.contactTestBitMask = bubble1Category | bubble2Category| bubble3Category | bubble4Category | bubble5Category | bubble6Category;
lineNode.physicsBody.collisionBitMask = ballCategory | ballHalf1Category | ballHalf2Category;
lineNode.physicsBody.dynamic = YES;
lineNode.name = lineNodeCategoryName;
//ATTEMPT 1 - NSUINTEGER that increases per pixal moved
testNumber ++;
testLabel.text = [NSString stringWithFormat:#"%lu",(unsigned long)testNumber];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
inkLockOrUnlock = [[NSUserDefaults standardUserDefaults] boolForKey:#"inkLockOrUnlock"];
if (inkLockOrUnlock == NO) {
SKAction *block0 = [SKAction runBlock:^{
for (UITouch *touch in touches) {
NSValue *key = [NSValue valueWithPointer:(void *)touch];
[lines removeObjectForKey:key];
}
[_gameLineNode removeAllChildren];
}];
SKAction *inkAnimation = [SKAction sequence:#[block0,]];
[self runAction:[SKAction repeatAction:inkAnimation count:1]];
} else {}
}

If I understand you, I think you might be misunderstanding a lot here. Every time touchMoved gets called, it gives you an xy coord for current position of that touch. That function can only be called max once per frame.
It sounds like you are thinking the function is called for every pixel the touch gets moved, which is not the case.
So store the initial point from touchBegan, then use math to determine distance between initial point and the current point inside touchMoved.
But also keep in mind that points don't necessarily equal pixels.

Related

Spritekit - Objective C - Add SKShapenodes to an array and then remove nodes one by one via button

Essentially I draw multiple lines on the screen by using a SKShapenode and adding it to a dictionary.
I want to now store the SKShapenodes in a array and remove the last item in the array via a button. Any idea anyone?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKLabelNode *touchedNode = (SKLabelNode *)[self nodeAtPoint:positionInScene];
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// Create a mutable path
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:location];
// Create a shape node using the path
lineNode = [SKShapeNode shapeNodeWithPath:path.CGPath];
lineNode.strokeColor = [SKColor blackColor];
lineNode.glowWidth = 3.0;
[_gameLineNode addChild:lineNode];
// Use touch pointer as the dictionary key. Since the dictionary key must conform to
// NSCopying, box the touch pointer in an NSValue
NSValue *key = [NSValue valueWithPointer:(void *)touch];
[lines setObject:lineNode forKey:key];
_lineNodeArray = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
[_lineNodeArray addObject:lineNode];
}
}
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// Retrieve the shape node that corresponds to touch
NSValue *key = [NSValue valueWithPointer:(void *)touch];
lineNode = [lines objectForKey:key];
if (lineNode != NULL) {
// Create and initial a mutable path with the lineNode's path
UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:lineNode.path];
// Add a line to the current touch point
[path addLineToPoint:location];
// Update lineNode
lineNode.path = path.CGPath;
lineNode.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:path.CGPath];
lineNode.physicsBody.categoryBitMask = lineNodeCategory;
lineNode.physicsBody.contactTestBitMask = bubble1Category | bubble2Category| bubble3Category | bubble4Category | bubble5Category | bubble6Category;
lineNode.physicsBody.collisionBitMask = ballCategory | ballHalf1Category | ballHalf2Category;
lineNode.physicsBody.dynamic = YES;
lineNode.name = lineNodeCategoryName;
CGPoint _currentPoint = [[touches anyObject] locationInNode:self];
CGPoint _previousPoint = [[touches anyObject] previousLocationInNode:self];
_delta = CGPointMake(_currentPoint.x - _previousPoint.x, _currentPoint.y - _previousPoint.y);
}
}
}
Delete action - This will remove the line from the array but how do i remove the last line from the game view?
-(void)deleteLine{
_lineNodeArray = [NSMutableArray array];
[_lineNodeArray lastObject ];
[_lineNodeArray removeLastObject];
}

Spritekit - Using gestures to draw multiple lines with multiple fingers simultaneously

Using the below code, I can draw a line using the pan gesture using 1 finger. However what I am attempting to do is for 1 finger to draw 1 line and another finger to draw the second simultaneously. I've read somewhere about using a dictionary to store the touches but don't know how to write it in code. Can any one help?
-(void)didMoveToView:(SKView *)view {
UIPanGestureRecognizer *gestureRecognizerPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:gestureRecognizerPan];
}
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, touchLocation.x, touchLocation.y);
lineNode = [[SKShapeNode alloc] init];
lineNode.path = pathToDraw;
lineNode.name = lineNodeCategoryName;
[_gameLineNode addChild:lineNode];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
CGPathAddLineToPoint(pathToDraw, NULL, touchLocation.x, touchLocation.y);
lineNode.path = pathToDraw;
lineNode.name = lineNodeCategoryName;
lineNode.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:pathToDraw];
lineNode.physicsBody.dynamic = YES;
lineNode.strokeColor = [SKColor blackColor];
lineNode.glowWidth = 3.0;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPathRelease(pathToDraw);
}
}
Drawing multiple lines simultaneously is fairly straightforward. The key is to track each touch event independently. One way to do that is to maintain a dictionary that uses the touch event as the key and the shape node (used to draw the line) as the value.
#implementation GameScene {
NSMutableDictionary *lines;
}
-(void)didMoveToView:(SKView *)view {
self.scaleMode = SKSceneScaleModeResizeFill;
// Create a mutable dictionary
lines = [NSMutableDictionary dictionaryWithCapacity:10];
}
// Add each line node to the dictionary with the touch event as the key
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// Create a mutable path
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:location];
// Create a shape node using the path
SKShapeNode *lineNode = [SKShapeNode shapeNodeWithPath:path.CGPath];
lineNode.strokeColor = [SKColor blackColor];
[self addChild:lineNode];
// Use touch pointer as the dictionary key. Since the dictionary key must conform to
// NSCopying, box the touch pointer in an NSValue
NSValue *key = [NSValue valueWithPointer:(void *)touch];
[lines setObject:lineNode forKey:key];
}
}
// Update the lines as needed
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// Retrieve the shape node that corresponds to touch
NSValue *key = [NSValue valueWithPointer:(void *)touch];
SKShapeNode *lineNode = [lines objectForKey:key];
if (lineNode != NULL) {
// Create and initialize a mutable path with the lineNode's current path
UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:lineNode.path];
// Add a line to the current touch point
[path addLineToPoint:location];
// Update lineNode
lineNode.path = path.CGPath;
}
}
}
// Remove the line nodes from the dictionary when the touch ends
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSValue *key = [NSValue valueWithPointer:(void *)touch];
[lines removeObjectForKey:key];
}
}
#end

NSUInteger increment is not consistent when moving with Touches Moved with different hand speeds

I've created a NSUInteger that increments per pixel moved in Touches Moved method. However the increment is not consistent. It appears that moving your finger fast will increase the number slowly whereas moving your finger slowly will increase the number fast.
Essentially I'm creating a ink bottle effect, so that drawing on the screen will decrease the ink in line with finger movement, but with different speeds of drawing, this wont be always the same.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKLabelNode *touchedNode = (SKLabelNode *)[self nodeAtPoint:positionInScene];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [[SKShapeNode alloc] init];
lineNode.path = pathToDraw;
[_gameNode addChild:lineNode];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
lineNode.name = lineNodeCategoryName;
lineNode.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:pathToDraw];
lineNode.physicsBody.categoryBitMask = lineNodeCategory;
lineNode.physicsBody.contactTestBitMask = bubble1Category|bubble2Category|bubble3Category|bubble4Category|bubble5Category;
lineNode.physicsBody.collisionBitMask = ballCategory;
lineNode.physicsBody.dynamic = YES;
lineNode.strokeColor = [SKColor blackColor];
lineNode.glowWidth = 3.0;
testNumber ++;
testLabel.text = [NSString stringWithFormat:#"%lu",(unsigned long)testNumber];
}
touchesMoved:withEvent: is called when a move event is detected. You won't be able to rely on the number of times this method is called to detect how far the user's finger moved.
You should rather try to rely on the distance the finger has travelled between two events to increment your test number.

Have particle emitter trail follow finger path in spriteKit

I have created a particle emitter in Xcode that has quite a lengthy trail. When i move it inside of the particle generator it leaves a trail following my mouse path.
some background info on my goal:
In my spriteKit game the user drags their finger around the screen to shoot moving objects. I am attempting to create a "Bullet Time" effect where the objects slow down and highlights when the current finger location touches them. When the finger stops moving or they run out of ammo the touchesEnded method is fired shooting all of the highlighted objects. Currently I have the path that they travel showing up as a line drawn to the screen using SKShapeNode & CGPath, but I would like the trail to be highlighted with the emitter trail instead.
In the touches began method I create a circle SpriteNode that moves around wherever the finger moves to (the physics collision is attached to the circle not the path). I've attached the particle trail emitter to this circle and it moves with the circle when I move it around the screen, but does not leave a trail.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
startPoint = touchLocation;
CGPathMoveToPoint(pathToDraw, NULL, touchLocation.x, touchLocation.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.lineWidth = 2;
lineNode.zPosition = 110;
lineNode.strokeColor = [SKColor whiteColor];
[self addChild:lineNode];
circle = [SKSpriteNode spriteNodeWithTexture:[animationsAtlas textureNamed:#"anim_countdown_9"]];
circle.name = #"circle";
circle.scale = 0.3;
circle.alpha = 0.5;
circle.zPosition = 110;
circle.position = touchLocation;
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
circle.physicsBody.categoryBitMask = weaponCategory;
circle.physicsBody.dynamic = NO;
circle.physicsBody.contactTestBitMask = billyCategory;
circle.physicsBody.collisionBitMask = 0;
[self addChild:circle];
pathEmitter = [SKEmitterNode skt_emitterNamed:#"FollowPath"];
//pathEmitter.position = circle.position;
//pathEmitter.targetNode = self;
//pathEmitter.scale = 0.2;
//pathEmitter.zPosition = 60;
[circle addChild:pathEmitter];
}
In touchesMoved method I move the circle accordingly to the new position
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInNode:self];
circle.position = currentPoint;
SKAction *wtf = [SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:0.1];
//pathEmitter.position = currentPoint;
//[pathEmitter runAction:wtf];
//pathEmitter.particleAction = wtf;
pathEmitter.particleAction = [SKAction moveTo:currentPoint duration:1.0];
CGPathAddLineToPoint(pathToDraw, NULL, currentPoint.x, currentPoint.y);
lineNode.path = pathToDraw;
I've tried setting pathEmitter.targetNode = self; like this post Making a particle follow a path in spriteKit suggests but then the emitter doesn't appear at all.
and if i set the particleAction to followPath it does not leave a trail either.
In my code you can see I've commented out some lines, basically I've tried every combination of targetNode & particleAction I can think of.
Any suggestions on how I can get the emitter to leave a trail on my finger path?
thanks
actually pathEmitter.targetNode = self; is the one that will let you have a particle to leave a trail as your object moves, I'm not seeing any reason why it's not working for you 'cause I've been using this method for like a long time ago now, check your position specially for method touchesMoved
I think this code is all what you need;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
circle = [SKSpriteNode spriteNodeWithTexture:[animationsAtlas textureNamed:#"anim_countdown_9"]];
circle.name = #"circle";
circle.scale = 0.3;
circle.alpha = 0.5;
circle.zPosition = 110;
circle.position = touchLocation;
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
circle.physicsBody.categoryBitMask = weaponCategory;
circle.physicsBody.dynamic = NO;
circle.physicsBody.contactTestBitMask = billyCategory;
circle.physicsBody.collisionBitMask = 0;
[self addChild:circle];
pathEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile: [[NSBundle mainBundle] pathForResource:#"FollowPath"ofType:#"sks"]];
pathEmitter.position = CGPointMake(0, -60);
[circle addChild:pathEmitter];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInNode:self];
circle.position = currentPoint;
}
- (void)update:(CFTimeInterval)delta
{
if (!pathEmitter.targetNode) {
pathEmitter.targetNode = self;
}
}

UIBezierPath Draw line while touchesMoved with two fingers

How can I draw a line in touchesMoved using UIBezierPath? When I am trying to draw a line using two touches/fingers, it is being drawn but it draws multiple lines. I tried removing lastObjectIndex and also tried removeAllObjects but still the same result. I would just like to draw a single line from finger 1 to finger 2 and when the user releases his/her finger then the line will be drawn on a UIImageView. Here is my code:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([drawMode isEqualToString:#"stroke"]) {
UITouch *touch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[touch locationInView:self]];
}
else if ([drawMode isEqualToString:#"line"]) {
NSArray *allTouches = [touches allObjects];
if ([touches count] == 2) {
UITouch *touch1 = [allTouches objectAtIndex:0];
UITouch *touch2 = [allTouches objectAtIndex:1];
CGPoint touchLoc1 = [touch1 locationInView:self];
CGPoint touchLoc2 = [touch2 locationInView:self];
[myPath moveToPoint:touchLoc1];
[myPath addLineToPoint:touchLoc2];
[shapeArray removeAllObjects];
[shapeArray addObject:myPath];
}
}
[self setNeedsDisplay];
}

Resources