I am creating a game in swift that involves making words out of letters. The letters, which are individual SKSpriteNodes, sit on a "shelf" which is a SKSpriteNode. To remove the letters from the "shelf", I am trying to introduce swiping up. Unfortunately I am having issues with the swipe being picked up by the letters. The shelf seems to be absorbing it, or sometimes even the SKScene. I have disabled the user interaction on the shelf node.
This is my setup:
swipeRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("move:"))
swipeRecognizer.direction = UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeRecognizer)
I add the swipeRecognizer to the view, then in the move method, I have the following:
func move(swipe:UISwipeGestureRecognizer){
if(swipe.state == UIGestureRecognizerState.Ended && swipe.numberOfTouches() == 1){
var touchLocation = swipe.locationInView(swipe.view)
touchLocation = self.convertPointFromView(touchLocation)
var sprite = self.nodeAtPoint(touchLocation)
if sprite is Letter{
let letter = sprite as Letter
if(gameManager.letterOnShelf(letter)){
gameManager.letterFlicked(letter)
}
}
}
}
Only sometimes to it recognize the sprite as a letter, 80% of the time the sprite is the shelf and I can't figure out what the correct way to work it is.....
Any help is greatly appreciated.
Cheers all!!
I would recommend:
Using a UILongPressGestureRecognizer with minimumPressDuration set to 0.
When the gesture is at the UIGestureRecognizerStateBegan state, see if the sprite your touch is over is a Letter and save a reference to that tile.
In the UIGestureRecognizerStateEnded state, you should be able to see what location your gesture ended (or perhaps the trajectory, etc) and then move the tile you stored a reference to from earlier.
Related
Currently working on a game with scene kit in swift. I've got a joystick added to a SKScene, which is then attached to my scene kit game scene. Typical HUD overlay for controls.
let hudScene = SKScene(size: view.frame.size)
scnView.overlaySKScene = hudScene
I have also added a joystick to hudScene and want to add some buttons. Now, all of this works fine until I add a gesture recognizer to my main game scene. Once I do, all tap interaction with the hudScene is completely ignored. Even if I tap on the joystick and log what node was tapped at that location, it gives me the node in my game scene and completely ignores the nodes in hudScene.
Can anybody provide any insight into why this is happening?
For reference, the touch events for the joystick are implemented from this library, and my gesture recognizer is implimented like this:
let touchDownRec = UILongPressGestureRecognizer(target: self, action: #selector(didTouchDown(_:)) )
touchDownRec.minimumPressDuration = 0
touchDownRec.numberOfTouchesRequired = 1
scnView.addGestureRecognizer(touchDownRec)
I had the same problem... you have to replace the touchesBegan with a TapGesture, or replace the UILongPressGestureRecognizer with touchesEnded, but is more precise the first option.
On iPhone's with 3D touch enabled, there is a feature where long pressing the left hand side of the screen with enough force opens lets you change which app is active. Because of this, when a non-moving touch happens on the left hand side of the screen, the touch event is delayed for a second or two until the iPhone verifies that the user is not trying to switch tasks and is interacting with the app.
This is a major problem when developing a game with SpriteKit, as these touches are delayed by a second every time a user taps/holds their finger on the left edge of the screen. I was able to solve this problem by registering a UILongPressGestureRecognizer in the main Scene of the game, thus disabling TouchesBegan and implementing a custom touches function (used as a selector by the gesture recognizer):
-(void)handleLongPressGesture:(UITapGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:self.view];
if (gesture.state == UIGestureRecognizerStateBegan)
{
//
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
//
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
//
}
else if (gesture.state == UIGestureRecognizerStateCancelled)
{
//
}
}
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPressGesture:)];
longPressGestureRecognizer.delaysTouchesBegan = false;
longPressGestureRecognizer.minimumPressDuration = 0;
[view addGestureRecognizer:longPressGestureRecognizer];
// continue
}
The problem with this is that I would have to implement a gesture recognizer for every touch (including simultaneous ones) that I expect the user to enter. This interferes with any touchesBegan methods as subclasses of SKSpriteNode, SKScene, etc. and kills a lot of functionality.
Is there any way to disable this delay? When registering the gestureRecognizer, I was able to set delaysTouchesBegan property to false. Can I do the same somehow for my SKScene?
To see this issue in action, you can run the default SpriteKit project, and tap (hold for a second or two) near the left hand side of the screen. You will see that there is a delay between when you touch the screen and when the SKShapeNodes are rendered (as opposed to touching anywhere else on the screen).
* Edit 1 *
For those trying to find a way to get around this for now, you can keep the gesture recognizer but set its cancelsTouchesInView to false. Use the gesture recognizer to do everything you need to do until TouchesBegan kicks in (touchesBegan will receive the same touch event about a second after the gesture recognizer recognizes the touch). Once touchesBegan kicks in, you can disable everything happening in the gesture recognizer. This seems like a sloppy fix to me, but it works for now.
Still trying to find a more-or-less formal solution.
I have experienced this as an user and it is really annoying. The only thing that worked for me was to disable the 3D touch. Otherwise the left side of the touchscreen is almost useless.
The player drags a sprite in my game but when accidentally touch the screen with a second finger it screws the movement obviously.
I used the following solutions for disable the second touch, but unfortunately it doesn't work:
//--------------
-(void)touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event {
if (touches.count == 1 && draggedNode == nil) {
CGPoint pos = [[touches anyObject] locationInNode:self];
SKNode * touchedNode = [self nodeAtPoint:pos];
if([touchedNode.name isEqual: #"shooterBall"]){
draggedNode = touchedNode;
}
draggedNodeOffset = CGPointMake(draggedNode.position.x - pos.x, draggedNode.position.y - pos.y);
}
}
//--------------
-(void)touchesMoved:(NSSet*) touches withEvent:(UIEvent*) event {
if (touches.count <= 1) {
CGPoint pos = [[touches anyObject] locationInNode:self];
draggedNode.position = CGPointMake(pos.x + draggedNodeOffset.x, pos.y+draggedNodeOffset.y);
}
}
//--------------
-(void)touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event {
draggedNode = nil;
}
//--------------
Do you have any solution for this?
Thanks your help in advance!
You want to implement UIPanGestureRecognizer in your scene. It will allow you to track the location of the user's touch and at the same time control other "stray" touches: UIPanGestureRecognizer Documentation
After you initialize it, you need to implement a method to handle the user's pans. You will have to set flags inside of this method to control when the swipe started/ended. I think this answer on StackOverflow gave a really good explanation of using it (with Swift). BTW, when you initialize it, you should set the gesture recognizer's property maximumNumberOfTouches to 1 (that will cause it to ignore other touches while the user is panning).
The trickier part will be to translate the same code you wrote before to gesture recognizer. The difference is that your handler will be called only once for each "swipe" or "pan", while the touches method you are using now is called each time there is a "touch". There are a few ways to proceed at this point, and you could try whatever you like, but I think that this would be the easiest way to go once you have your gesture recognizer set up (spoiler):
make sure the gesture recognizer is an instance variable so you can access it from all methods.
go to the update: method and make an if statement that checks if gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged
use the same algorithm that you had before in this if statement. To check the location that the touch is at use the method: locationInView:. Use self.view as the parameter.
Hope this helped! good luck.
I am having the hardest time figuring out gesture recognizers and such on iOS. Unfortunately a lot of the documentation by apple appears to be in Objective-C and/or it doesn't give you examples that show what values can go in. Could you show me some examples of how to the the following things.
Get the current position of each touch event on the screen. If the finger isn't down then return false.
Make a direction recognizer other then the 4 main directions. Currently my code looks like this
var leftSwipe = UISwipeGestureRecognizer(target: self, action: Selector("HandleSwipes:"))
leftSwipe.direction = .Left
view.addGestureRecognizer(leftSwipe)
However what if I wanted to detect diagonal movement? Their isn't a .Diagonal. So the detection of a gesture works in the direction (0.5, 0.5).
Going back to the code I put before I have a function that I check the direction in
func HandleSwipes(sender: UISwipeGestureRecognizer) {
if (sender.direction == .Left) {
Label.text = "Left"
}
}
What if I want this swipe to only work for the 2nd finger down? Also how would I get the ending, and starting position of that gesture? (preferably inside of that function)
How can I find out how long it has taken the finger to do the gesture (getting from point A to point B).
I am not that familiar with swift, but in objective-c, this is how you will obtain the current position of a touch event.
CGPoint _originalCenter;
-(void)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
// if the gesture has just started, record the current centre location
_originalCenter = self.center;
}
Hope this helps answer the first part of your question.
I need to animate view translation from A to B on the screen.
However, I want that translation to occur when a user swipes his thumb on the screen.
I also want translation to depend on the thumb swipe in a way that they follow one another.
I assume I will need some sort of listener, which will follow my thumb motion on the screen and I would somehow tell my view to move on the screen left or right, depending on the direction of the swipe.
How can I achieve that?
I would use the UIPanGestureRecognizer, because it has more control, for example if you move slow, it can still pickup where your movement is, then maybe you can position your translation accordingly.
You can do something like:
var panRecongniser = UIPanGestureRecognizer(target: self, action: Selector("didPanRecongnised:"))
in your viewDidLoad, and then:
func didPanRecongnised(recongniser: UIPanGestureRecognizer){
if(recongniser.state == UIGestureRecognizerState.Changed || recongniser.state == UIGestureRecognizerState.Began){
self.didPanMove(recongniser)
}else if(recongniser.state == UIGestureRecognizerState.Ended || recongniser.state == UIGestureRecognizerState.Cancelled){
self.didPanEnd(recongniser)
}
}
and in your didPanMove:
func didPanMove(recongniser: UIPanGestureRecognizer){
if((self.view.superview) != nil){
if(recongniser.state == UIGestureRecognizerState.Began){
// save the original position
self._origPoint = self.view.frame.origin
}
// this is the translation of the last segment of the "move"
let trans = recongniser.translationInView(self.view.superview!)
// this is the velocity of the last segment of the "move"
let velocity = recongniser.velocityInView(self.view.superview!)
}
}
Use the translation values to workout which direction the user is swiping. Don't forget to add the "trans" value as the accumulated translation each time "didPanMove" is called.
You can now use those value to do a number of things. Like having the appearing view to follow the progress of your finger. Or when the velocity reaches certain speed, having the view "snap" to the required position, like with a swipe. And maybe if the user don't swipe fast enough or far enough, having the view follow the finger a bit, then "snap" back to the hiding position when the gesture ends.
To do the animation, if you're doing a custom sliding out view, you can probably use UIView.animateWithDuration to animate your view sliding out.
I hope it helps you...
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(tappedRightButton:)];
[swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:swipeLeft];
UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(tappedLeftButton:)];
[swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:swipeRight];