Objective-C SpriteKit Create dotted line to certain points - objective-c

I have a certain point at the bottom of the screen . . . when I touch the screen somewhere, I'd like a dotted line to appear between the point, and the point my finger is at. The length and rotation of the line will change based on where my finger is, or moves to.
I'm assuming I'd make the dotted line with a repetition of a small line image, but I guess that's why I need your help!

Note that all this can be organized better, and I personally don't like SKShapeNode in any shape :) or form, but this is the one way to do it:
#import "GameScene.h"
#implementation GameScene{
SKShapeNode *line;
}
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
line = [SKShapeNode node];
[self addChild:line];
[line setStrokeColor:[UIColor redColor]];
}
-(void)drawLine:(CGPoint)endingPoint{
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
CGPathAddLineToPoint(pathToDraw, NULL, endingPoint.x,endingPoint.y);
CGFloat pattern[2];
pattern[0] = 20.0;
pattern[1] = 20.0;
CGPathRef dashed =
CGPathCreateCopyByDashingPath(pathToDraw,NULL,0,pattern,2);
line.path = dashed;
CGPathRelease(dashed);
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
[self drawLine:location];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
[self drawLine:location];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
line.path = nil;
}
The result is:
Also I don't know how much performant this is, but you can test it, tweak it and improve it. Or even use SKSpriteNode like you said. Happy coding!
EDIT:
I just noticed that you said dotted (not dashed) :)
You have to change pattern to something like:
pattern[0] = 3.0;
pattern[1] = 3.0;

Related

Drag and drop. Nudging other images while moving

I've implemented drag and drop for multiple images which works fine but I'm facing one issue. When I'm dragging one image, other images will be nudged along when my finger moves over them whilst Im still dragging the original image. I'd love to be able to have only one image moveable at once.
Heres some of my code.
-(void)touchesBegan: (NSSet *) touches withEvent:(UIEvent *)event{
UITouch* touch = [touches anyObject];
for (UIImageView *noseImage in noseArray) {
if ([touch.view isEqual:noseArray]) {
firstTouchPoint = [touch locationInView:[self view]];
xd = firstTouchPoint.x - [[touch view]center].x;
yd = firstTouchPoint.y - [[touch view]center].y;
}
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint oldPoint = [touch previousLocationInView:touch.view];
CGPoint newPoint = [touch locationInView:touch.view];
CGPoint diff = CGPointMake(newPoint.x - oldPoint.x, newPoint.y - oldPoint.y);
for (UIImageView *noseImageView in noseArray) {
if (CGRectContainsPoint(noseImageView.frame, newPoint)) {
CGPoint cntr = [noseImageView center];
[noseImageView setCenter:CGPointMake(cntr.x + diff.x, cntr.y + diff.y)];
}
}
}
Typically you would determine which image is being dragged in the touchesBegan: and remember that. Then in the touchesMoved:, move the remembered image the given amount.
But a Gesture Recogniser works a lot easier than these low level methods, so I suggest you use that instead.
You wrote:
for (UIImageView *noseImage in noseArray) {
if ([touch.view isEqual:noseArray]) {
Are you sure the second line shouldn't be the following?
if ([touch.view isEqual: noseImage]) {

Xcode 5.0.2 - Attempted to add a SKNode which already has a parent: <SKEmitterNode>

I am attempting to add a particle effect in the touchesBegan method, so when the user touches the drawn sprite(SKSpriteNode), it draws the particle effect. However, I receive an error Attempted to add a SKNode which already has a parent: SKEmitterNode. To add some context... The game is of the bejeweled/candy crush style where blocks(deleteNode) are deleted based upon neighboring colors. In the touch event I iterate recursively, checking neighboring blocks and add them to an array to later be removed. Prior to each block(deleteNode) being removed I would like the particle event to occur. They both inherit from SKNode (right?), so I don't understand the conflict...
#interface
{
NSString *blockParticlePath;
SKEmitterNode *blockParticle;
}
in the initialization method...
blockParticlePath = [[NSBundle mainBundle] pathForResource:#"blockParticle" ofType:#"sks";
blockParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:blockParticlePath];
in touchesBegan...
blockParticle.position = deleteNode.position;
blockParticle.particleColor = deleteNode.color;
[self addChild:blockParticle];
To make sure I wasn't crazy, I checked other forums and have seen this same logic for adding particle effects to a scene. Thanks in advance. If you need any more info let me know.
#whfissler, you explanations helped a lot to pinpoint this solution.
This error occurred only when I had many SKSpriteNodes active (ballon game). On each ballon click it pops and a SKEmitterNode (explosion) is added. It seems that when the particles created by the explosion touch each other I recieve the same error like you. I changed my code from:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
SKEmitterNode *explosion = [SKEmitterNode orb_emitterNamed:#"ballonEksplosion"];
CGPoint positionInScene = [touch locationInNode:self];
explosion.position = positionInScene;
SKSpriteNode *touchedSprite;
for ( int i = 0; i < [[self nodesAtPoint:positionInScene] count]; i++)
{
touchedSprite = (SKSpriteNode *)[[self nodesAtPoint:positionInScene] objectAtIndex:i];
if ([touchedSprite.name isEqualToString:#"BALLON"])
{
[(MBDBallon *)touchedSprite popAndRemoveWithSoundEffect];
[self addChild:explosion];
}
}
}
to:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKSpriteNode *touchedSprite;
for ( int i = 0; i < [[self nodesAtPoint:positionInScene] count]; i++)
{
touchedSprite = (SKSpriteNode *)[[self nodesAtPoint:positionInScene] objectAtIndex:i];
if ([touchedSprite.name isEqualToString:#"BALLON"])
{
SKEmitterNode *explosion = [SKEmitterNode orb_emitterNamed:#"ballonEksplosion"];
explosion.position = positionInScene;
[(MBDBallon *)touchedSprite popAndRemoveWithSoundEffect];
[self addChild:explosion];
}
}
}
and it worked. To me it seems that my explosion SKEmitterNode had been kept somehow to long on the SKScene and therefore adding another SKEmitterNode for the currentPosition lead to problems with the:
self nodesAtPoint:positionInScene
nodesAtPoint stack.
I donĀ“t understand it fully, maybe this helps you in further understanding.

draw a line in sprite kit in touchesmoved

I would like to draw a line in sprite kit along the points collected in touchesmoved.
Whats the most efficient way of doing this? I've tried a few times and my line is either wrong on the y axis, or takes up a lot of processing power that the fps goes down to 10 a second.
Any ideas?
You could define a CGpath and modify it by adding lines or arcs in your touch moved function. After that, you can create a SKShapeNode from your path and configure it as you prefer.
If you want to draw the line while the finger is moving on the screen you can create the shape node when the touch begins with an empty path and then modify it.
Edit: I wrote some code, it works for me, draws a simple red line.
In your MyScene.m:
#interface MyScene()
{
CGMutablePathRef pathToDraw;
SKShapeNode *lineNode;
}
#end
#implementation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.strokeColor = [SKColor redColor];
[self 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;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// delete the following line if you want the line to remain on screen.
[lineNode removeFromParent];
CGPathRelease(pathToDraw);
}
#end

How to move a UIImageView with Touch

I'm trying to create moving functionality to my imageView (maskPreview in the code below), so that users can move a picture, which is contained in maskPreview, around the screen. Here's my code for touch begin and touch moved:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1) {
UITouch *touch= [touches anyObject];
originalOrigin = [touch locationInView:maskPreview];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1) {
UITouch *touch = [touches anyObject];
CGPoint lastTouch = [touch previousLocationInView:self.view];
CGFloat movedDistanceX = originalOrigin.x-lastTouch.x;
CGFloat movedDistanceY = originalOrigin.y-lastTouch.y;
[maskPreview setFrame:CGRectMake(maskPreview.frame.origin.x+movedDistanceX, maskPreview.frame.origin.y + movedDistanceY, maskPreview.frame.size.width, maskPreview.frame.size.height)];
}
}
but I'm getting some weird responses from the app. I haven't put restrictions on how far the imageview can move, i.e. to prevent it from going out of the screen, but even if it's a small move, my imageview goes wild and disappears.
Thanks alot in advance for all the help
Implementing touchesBegan and so on is way overkill in this modern world. You're just confusing the heck out of yourself, and your code will quickly become impossible to understand or maintain. Use a UIPanGestureRecognizer; that's what it's for. Making a view draggable with a UIPanGestureRecognizer is trivial. Here's the action handler for a UIPanGestureRecognizer that makes the view draggable:
- (void) dragging: (UIPanGestureRecognizer*) p {
UIView* vv = p.view;
if (p.state == UIGestureRecognizerStateBegan ||
p.state == UIGestureRecognizerStateChanged) {
CGPoint delta = [p translationInView: vv.superview];
CGPoint c = vv.center;
c.x += delta.x; c.y += delta.y;
vv.center = c;
[p setTranslation: CGPointZero inView: vv.superview];
}
}
There are two problems with your code. First, this line is wrong:
CGPoint lastTouch = [touch previousLocationInView:self.view];
It should be this:
CGPoint lastTouch = [touch previousLocationInView:maskPreview];
Really, you shouldn't even be using previousLocationInView:. You should just be using locationInView: like this:
CGPoint lastTouch = [touch locationInView:maskPreview];
Second, you are getting the signs of movedDistanceX and movedDistanceY wrong. Change them to this:
CGFloat movedDistanceX = lastTouch.x - originalOrigin.x;
CGFloat movedDistanceY = lastTouch.y - originalOrigin.y;
Also, the documentation for touchesBegan:withEvent: says this:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empy) implementations.
So make sure you're also overriding touchesEnded:withEvent: and touchesCancelled:withEvent:.
Anyway, you can do this quite a bit more simply. One way is to make touchesBegan:withEvent: empty and do all the work in touchesMoved:withEvent: by using both previousLocationInView: and locationInView:, and updating maskPreview.center instead of maskPreview.frame. You won't even need the originalOrigin instance variable:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1) {
UITouch *touch = [touches anyObject];
CGPoint p0 = [touch previousLocationInView:maskPreview];
CGPoint p1 = [touch locationInView:maskPreview];
CGPoint center = maskPreview.center;
center.x += p1.x - p0.x;
center.y += p1.y - p0.y;
maskPreview.center = center;
}
}
Another way to do this is by using a UIPanGestureRecognizer. I leave that as an exercise for the reader.

Bug when moving a UIView horizontally

I've created a puzzle engine for iPad (iOS SDK 4.2)
I have a PuzzleViewController that controls a UIView (rootView) that holds a smaller UIView (puzzleView) and twelve puzzle pieces (PuzzlePieceView class extends UIImageView).
The ideia is very simple. The puzzlePieces are around the puzzleView and are picked and dragged to the puzzleView. When the pieces are picked they double they're size and are centered to place where the finger is touching.
When they're dropped in the right place they stay put (they're removed from rootView and added as subviews to the puzzleView) if they're drop in the wrong place they return to the original position and size.
Currently I'm overriding touches Began/Moved/Ended/Cancelled in the PuzzlePieceView class so that each PuzzlePiece controls its own movements.
here's what they do
#pragma mark UIResponder overriding
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesBegan");
if (!isDeployed) {
if (self.initialSize.width == 0 || self.initialSize.height ==0) { //if there is still no initial size
self.initialSize = self.frame.size;
}
NSLog(#"self.initialLocation.x %f %f", self.initialLocation.x, self.initialLocation.y);
if (self.initialLocation.x == 0. || self.initialLocation.y == 0.) { //if there is still no initial location
self.initialLocation = self.center; //record the initial location
}
UITouch *touch = [[event allTouches] anyObject];
self.center = [touch locationInView:self.superview]; //set the center of the view as the point where the finger touched it
[self.superview bringSubviewToFront:self]; //the piece brings itself as frontMostView so that it is always visible and never behind any other piece while moving
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesMoved");
if (self.frame.size.width == self.initialSize.width) {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.image.size.width, self.image.size.height);
}
if (!isDeployed) {
UITouch *touch = [[event allTouches] anyObject];
self.center = [touch locationInView:self.superview]; //set the center of the view as the point where the finger moved to
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesEnded");
if (!isDeployed) {
UITouch *touch = [[event allTouches] anyObject];
NSLog(#"point %# x%f y%f", self.puzzleView, [touch locationInView:self.puzzleView].x, [touch locationInView:self.puzzleView].y);
if (CGRectContainsPoint(self.dropZone,[touch locationInView:self.puzzleView])) {
[self removeFromSuperview];
[self.puzzleViewController addPiece:self];
self.center = self.finalCenter;
self.isDeployed = YES;
}
else {
[self restoreToInitialSize];
[self restoreToInitialPosition];
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesCancelled");
if (!isDeployed) {
[self restoreToInitialSize];
[self restoreToInitialPosition];
}
}
#pragma mark -
Seemed pretty easy and it was.
I've got only one bug left.
When I try to make an horizontal move on any puzzlePiece the touch gets cancelled.
This only happens when the move is started in a fast way. If i touch the piece and wait for a moment (about a second) the pieces move perfectly.
The pieces also move perfectly in the vertical direction.
I've tried not changing the puzzlePiece size when the view is first touched bu the bug remains...
I found the solution to this.
One of my team-mates registered a UISwipeGestureRecognizer in the RootView instead of the specific subview were the swipeGesture should be recognized.