SpriteKit SKAction by touching a Node - objective-c

I want to run a SKAction if my SKSpriteNode Sorcerer is touched.
With my current code, whenever I touch, my SpriteNode 'stone' will move to the Sorcerer.
I could call the location of the touch with specific x and y coord., but since the 'Sorcerer' is moving, I can't do that.
How can I fix this?
- (void)Sorcerer {
Sorcerer = [SKSpriteNode spriteNodeWithImageNamed:#"Sorcerer.png"];
Sorcerer.size = CGSizeMake(85, 85);
Sorcerer.zPosition = 2;
Sorcerer.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(50, 50)];
Sorcerer.physicsBody.dynamic = NO;
Sorcerer.physicsBody.allowsRotation = NO;
Sorcerer.physicsBody.usesPreciseCollisionDetection = YES;
Sorcerer.physicsBody.restitution = 0;
Sorcerer.position = CGPointMake(self.frame.size.width * 1.25, self.frame.size.height / 2.2);
SKAction * actionMove = [SKAction moveToX:self.frame.size.width / 2 duration:10];
[Sorcerer runAction:[SKAction repeatActionForever:[SKAction sequence:#[actionMove]]]];
[self addChild:Sorcerer];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:Sorcerer];
Stone = [SKSpriteNode spriteNodeWithImageNamed:#"Stone.png"];
Stone.position = Human.position;
Stone.zPosition = 1;
Stone.scale = 0.6;
SKAction *action = [SKAction moveTo:Sorcerer.position duration:0.5];
SKAction *remove = [SKAction removeFromParent];
[Stone runAction:[SKAction sequence:#[action,remove]]];
[self addChild:Stone];
}

There are many situations when SKNode needs to inform its scene about some events or wants to force particular behavior only scene can provide.
Of course you can implement a touchesEnded: method in the scene and iterate through node but what if you have couple of thousands of them on the screen? The cleanest possible way of doing this is either by using delegation or notifications.
Delegation
We will create a protocol and set a scene as its delegate. Sourcerer will call delegate's method when it's touched. Let's define the protocol:
#class Sorcerer;
#protocol SorcererInteractionDelegate <NSObject>
- (void)sorcererTapped:(Sorcerer)sorcerer;
#end
Add a delegate property to Sorcerer class:
#interface Sorcerer : SKSpriteNode
#property (nonatomic, weak) id <SorcererInteractionDelegate> delegate;
#end
Finally, implement the touchesEnded: method.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if ([_delegate respondsToSelector:#selector(sorcererTapped:)])
{
[_delegate performSelector:#selector(sorcererTapped:)
withObject:self];
}
}
Remember to set userInteractionEnabled to YES in your init method of Sourcerer class.
When creating a sourcerer in your scene, set the delegate:
Sourcerer *sourcerer = ...
sourcerer.delegate = self;
and implement the sorcererTapped: method:
- (void)sorcererTapped:(Sorcerer)sorcerer;
{
// Do stuff with sorcerer.
}
Notifications
Delegation is cool but what if you need to inform several objects at once about the change. Then, you should use notifications.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UserDidTapSourcererNotification" object:self];
}
Your scene needs to register as an observer of #"UserDidTapSourcererNotification" notification and handle it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUserDidTapSourcererNotification:)
name:#"UserDidTapSourcererNotification";
object:nil];
- (void)handleUserDidTapSourcererNotification:(NSNotification *)notification
{
Sorcerer *sorcerer = (Sorcerer *)[notification object];
// Do stuff with sorcerer.
}
Hope it helps.

//give stone or Sorcere a name
through this name you can easily access that node
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// decide if the node is of interest or can be ignored, something like this:
if ([node.name isEqualToString:#“stone”]) {
//apply action on stone
}
}

Related

UIView detecting touches in only one half of the screen

I am currently working on a 3D OpenGLES-based iPhone Game project and I want to have a view that detect touches so I can then rotate my camera according to the touch gestures.
So here I created a TouchPad class that derives from UIView to record the touch gestures.
Once done I have figured out that only half of the screen is effective. The whole left side.
Whenever i touch the right part of the screen it does not call the -[touchesBegan] method
Here is my class definition
#interface TouchPad : UIView
{
CGPoint fingerMove, originalPosition;
}
#property (nonatomic, assign) CGPoint fingerMove;
#end
And here is my class implementation
#import "TouchPad.h"
#implementation TouchPad
#synthesize fingerMove;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
fingerMove = CGPointMake(0, 0);
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
std::cout << "called";
UITouch *touch = [touches anyObject];
originalPosition = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self];
fingerMove = CGPointMake(currentPosition.x - originalPosition.x,
currentPosition.y - originalPosition.y);
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
{
UITouch *touch = [touches anyObject];
originalPosition = [touch locationInView:self];
fingerMove = CGPointMake(0, 0);
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
fingerMove = CGPointMake(0, 0);
}
#end
And here is how i use it inside my ViewController
- (void)prepareGame
{
game = [[MainGame alloc] init];
[self.view addSubview:game.IrrView];
[game.IrrView release];
rotationPad = [[TouchPad alloc] initWithFrame:CGRectMake(0.0, 0.0,
[[UIScreen mainScreen] bounds].size.width,
[[UIScreen mainScreen] bounds].size.height)];
[self.view addSubview:rotationPad];
[rotationPad release];
}
- (void)runGame
{
[game updateMapPattern];
[game updateCamera :rotationPad.fingerMove];
[game draw];
}
I really want to know how to make the touchPad work in the whole screen and not only the left part of the screen. Maybe there is a conflict with my game view

Creating a draggable image view

I have been searching the net for a long time and I have not found a concrete way of making an image view draggable. Here is what I have so far:
tempViewController.h
#import <UIKit/UIKit.h>
#import "MyRect.h"
#class UIView;
#interface tempViewController : UIViewController
#property (nonatomic, strong) MyRect *rect1;
#end
tempViewController.m
#import "tempViewController.h"
#interface tempViewController ()
#end
#implementation tempViewController
#synthesize rect1 = _rect1;
- (void)viewDidLoad
{
[super viewDidLoad];
_rect1 = [[MyRect alloc]initWithFrame:CGRectMake(150.0, 100.0, 80, 80)];
[_rect1 setImage:[UIImage imageNamed:#"cloud1.png"]];
[_rect1 setUserInteractionEnabled:YES];
[self.view addSubview:_rect1];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == _rect1)
{
CGPoint pt = [[touches anyObject] locationInView:_rect1];
NSLog(#"%#",NSStringFromCGPoint(pt));
_rect1.center = pt;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == _rect1)
{
CGPoint pt = [[touches anyObject] locationInView:_rect1];
NSLog(#"%#",NSStringFromCGPoint(pt));
_rect1.center = pt;
}
}
#end
MyRect right now is an empty UIImageView Class.
Dragging the image from a point such as [532,589] on micron moves it to a totally different part of the screen such as [144, 139]
Just attach a UIPanGestureRecognizer to your view. In the recognizer's action, update your view's center based on the “translation” (offset) of the recognizer, then reset the recognizer's translation to zero. Here's an example:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *draggableView = [[UIView alloc] initWithFrame:CGRectMake(150, 100, 80, 80)];
draggableView.userInteractionEnabled = YES;
draggableView.backgroundColor = [UIColor redColor];
[self.view addSubview:draggableView];
UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(panWasRecognized:)];
[draggableView addGestureRecognizer:panner];
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
UIView *draggedView = panner.view;
CGPoint offset = [panner translationInView:draggedView.superview];
CGPoint center = draggedView.center;
draggedView.center = CGPointMake(center.x + offset.x, center.y + offset.y);
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:draggedView.superview];
}
Don't use -touchesBegan:withEvent: etc.
What you want to use for such "high-level" things are UIGestureRecognizers.
You would add a pan gesture recognizer to your view set a delegate (possibly the view itself), and in the callback move the view by the distance the recognizer moved.
You either remember the initial position and move by the -translationInView each time, or simply move by the translation and then use -setTranslation:inView: to reset the recognizers translation to zero, so on the next call of the delegate method, you will again get the movement since the last call.

Detect Swipe faster than UISwipeGestureRecognizer?

i was wondering how to detect a "swipe" faster than this? I'd like to call a method as soons as the user moves his finger to the left. Let's call it a "small" swipe gesture.
This would be the normal/long swipe…
UISwipeGestureRecognizer *recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeFrom:)];
recognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[scrollView addGestureRecognizer:recognizer];
[recognizer release];
[scrollView delaysContentTouches];
Now I build this:
#import "UICustomScrollView.h"
#implementation UICustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// do stuff
}
return self;
}
// Listen for "fast" swipe
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self];
CGPoint prevLocation = [touch previousLocationInView:self];
if (location.y - prevLocation.y > 0) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"fastSwipe" object:self];
}
[super touchesMoved:touches withEvent:event];
}
#end

Avoid UIView touch delay

I am using a subclass of UIView in my app. When the user touches on view I need to get the coordinates of the point; for this I am using:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
currentPoint=[[touches anyObject]locationInView:self];
}
It has some delay. Can any one give me the solution to get the points immediately? Thank you all.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
self.frame=frame;
rect=[[NSMutableArray alloc] initWithCapacity:1];
currentPointsList=[[NSMutableArray alloc]initWithCapacity:1];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
firstTouchedPoint = [touch locationInView:self];
NSLog(#"the firstTouched point is %");
for (int i=0; i<[rect count]; i++) {
if(CGRectContainsPoint([[rect objectAtIndex:i]CGRectValue], firstTouchedPoint)){
CGRect firstLineRect = CGRectMake(firstTouchedPoint.x,
[[rect objectAtIndex:i]CGRectValue].origin.y,
[[rect objectAtIndex:i]CGRectValue].size.width-firstTouchedPoint.x+[[rect objectAtIndex:i]CGRectValue].origin.x,
[[rect objectAtIndex:i]CGRectValue].size.height);
UIImageView *firstLine=[[UIImageView alloc]initWithFrame:firstLineRect];
firstLine.backgroundColor=[[UIColor blueColor]colorWithAlphaComponent:0.3f];
[self addSubview:firstLine];
break;
}
}
If you are using a UIScrollView there is a delay touches property (delaysContentTouches). You may want to make sure that is NO.

objective-c touch-events

I've got a set of Images and would like to know which I have touched. How could I implement that...?
To be more precise:
A "Home-Class" will instantiate a couple of Image-Classes:
Image *myImageView = [[Image alloc] initWithImage:myImage];
The image-class looks something like this:
- (id) initWithImage: (UIImage *) anImage
{
if ((self = [super initWithImage:anImage]))
{
self.userInteractionEnabled = YES;
}
return self;
}
later on, I use these touches-event-methods also in the Image-class:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{}
My problem at the moment: the touchesBegan/Ended methods will be fired no matter where I touched the screen, but I would like to find out which of the Images has been touched.....
Whenever you get the touch, you check if that touch happen in between your image area. Here is the example code, lets suppose you have UIImage object called img.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if (location.x >= img.x && location.x <= img.x && location.y >= img.y && location.y <= img.y) {
// your code here...
}
}
Inside your *.h (interface) file:
#interface MyViewController : UIViewController{
IBOutlet UIImageView *imageViewOne;
IBOutlet UIImageView *imageViewTwo;
UIImageView * alphaImage;
}
-(BOOL)isTouch:(UITouch *)touch WithinBoundsOf:(UIImageView *)imageView;
Place UIImageView components on your *.xib and bind them with 'imageViewOne' and 'imageViewTwo' using the "File's owner".
Go to the *.m (implementation) file and:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([self isTouch:touch WithinBoundsOf:imageViewOne])
{
NSLog(#"Fires first action...");
}
else if([self isTouch:touch WithinBoundsOf:imageViewTwo]){
NSLog(#"Fires second action...");
}
}
//(Optional 01) This is used to reset the transparency of the touched UIImageView
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[alphaImage setAlpha:1.0];
}
-(BOOL)isTouch:(UITouch *)touch WithinBoundsOf:(UIImageView *)imageView{
CGRect _frameRectangle=[imageView frame];
CGFloat _imageTop=_frameRectangle.origin.y;
CGFloat _imageLeft=_frameRectangle.origin.x;
CGFloat _imageRight=_frameRectangle.size.width+_imageLeft;
CGFloat _imageBottom=_frameRectangle.size.height+_imageTop;
CGPoint _touchPoint = [touch locationInView:self.view];
/*NSLog(#"image top %f",_imageTop);
NSLog(#"image bottom %f",_imageBottom);
NSLog(#"image left %f",_imageLeft);
NSLog(#"image right %f",_imageRight);
NSLog(#"touch happens at %f-%f",_touchPoint.x,_touchPoint.y);*/
if(_touchPoint.x>=_imageLeft &&
_touchPoint.x<=_imageRight &&
_touchPoint.y>=_imageTop &&
_touchPoint.y<=_imageBottom){
[imageView setAlpha:0.5];//optional 01 -adds a transparency changing effect
alphaImage=imageView;//optional 01 -marks the UIImageView which deals with the transparency changing effect for the moment.
return YES;
}else{
return NO;
}
}
That's how I handled that. I got the idea having read the post of "itsaboutcode".