Swift SpriteKit 3D Touch and touches moved - swift

I have recently send out a version of my game to TestFlight for internal testing.
My friend noticed that on his iPhone 6S Plus the controls to shoot do not work correctly.
The controls are as follow
1) left half of screen = jump
2) right half of screen = shoot
3) Slide and hold on right half of screen = start slow motion
Now in my touches began method I have a slight delay for the shooting, so if touches moved is called it cancels the shooting and starts slow mo.
This seems to be causing the problem on 3D Touch enabled phones.
Digging further into this I came across this post
iOS 9 Spritekit Double Tap Not Working on iPhone 6S
where it basically states that on 3D Touch enabled devices the touches moved method gets called automatically.
On 3D Touch capable devices, touch pressure changes cause
touchesMoved:withEvent: to be called for all apps running on iOS
9.0. When running iOS 9.1, the method is called only for apps
linked with the iOS 9.0 (or later) SDK.
Apps should be prepared to receive touch move events with no
change in the x/y coordinates. "
This seems to make sense of why my friend has problems, if touches moved is always called it will cancel the shooting and start slow mo, even though he is not sliding on the screen.
So my questions is
1) Can you disable 3D Touch in SpriteKit games?
2) Is this a bug?
3) any other way to use touches moved correctly on 3D Touch devices?
4) alternatively can I use gesture recognisers to simulate a slide and hold feature that I am currently doing in touches moved.
Thanks for any help

After trying various other things that didnt work, including UISwipeGestureRecognizer subclasses, I finally figured it out.
The answer was actually staring me right in the face in that same stackOverflow post I linked in my question
iOS 9 Spritekit Double Tap Not Working on iPhone 6S
The solution is to create a property
var startingTouchLocation: CGPoint?
than in touches Began you set that property
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
/// Set starting touch. this is for 3d touch enabled devices, see touchesMoved method below
startingTouchLocation = location
....
}
}
and than add a check in touchesMoved to return from the method until the touch has actually moved.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
/// WAY 1 - Check if touch moved because on 3d touch devices this method gets called on a force touch.
guard location != startingTouchLocation else { return }
// Your code
...
/// WAY 2 - Add a min distance for the finger to travel in any direction before calling touches moved code
let minValue: CGFloat = 30
guard let startingLocation = startingLocation else { return }
guard currentLocation.y < startingLocation.y - minValue ||
currentLocation.y > startingLocation.y + minValue ||
currentLocation.x < startingLocation.x - minValue ||
currentLocation.x > startingLocation.x + minValue else { return false }
// Your code
....
}
}

Related

Spritekit move camera only if not dragging from sprite

In a game I'm building a user is able to move the camera around by dragging around the screen similar to a scrollview. This is all handled in the main scene file by the touchesBegan and touchesMoved method. However I also want the ability for a user to drag from an SKSpriteNode (not actually moving it) to another SKSpriteNode - I then perform an action based on the 2 nodes. I can do both of these fine separately. But when a user is dragging from one node to another, I don't want the camera to pan.
Again I've managed to resolve this using a boolean flag that's set on touchesBegan if they have touched a node. However the screen then doesn't pan if a user starts touching on one of the sprites but finishes off of one e.g they actually quickly performed a pan that happened to start on a node. Does anyone have any good solutions for this?
One thought I had was to store all the touch events in touchesMoved, and then loop through them in touchesEnded performing the same movement logic, provided they didn't start and end on a sprite. e.g
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
movingTouches.append(touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if startedOnSprite && !endedOnSprite || !(startedOnSprite && endedOnSprite) {
let location = movingTouches.last().location(in: self)
let previousLocation = movingTouches.first().location(in: self)
let deltaY = location.y - previousLocation.y
let deltax = location.x - previousLocation.x
gameCamera?.position.y -= deltaY
view.frame.size.height
gameCamera?.position.y = (gameCamera?.position.y)! > viewHeight/2 ? viewHeight/2 : (gameCamera?.position.y)!
gameCamera?.position.y = (gameCamera?.position.y)! < -(viewHeight/2) ? -(viewHeight/2) : (gameCamera?.position.y)!
gameCamera?.position.x -= deltax
gameCamera?.position.x = (gameCamera?.position.x)! < -(viewWidth/2) ? -(viewWidth/2) : (gameCamera?.position.x)!
gameCamera?.position.x = (gameCamera?.position.x)! > (viewWidth/2) ? (viewWidth/2) : (gameCamera?.position.x)!
}
movingTouches = [UITouch]()
endedOnSprite = false
startedOnSprite = false
}
To complicate matters I also want the Sprites to have a tap event on them as well as dragging. I'm struggling to find a good way to do all this in SpriteKit
Unfortunately the above isn't very smooth at all, and in think it appears to not even scroll the distance I would expect either (the exact same code does scroll correctly if I'm not bothered about whether or not it starts on a Sprite)
So to be clear, I want scrolling behaviour, provided a user isn't actually using a finger to 'draw' from one sprite to another
Gesture recognisors on your scene would do what you need, keeping taps and swipes separated.

touchesMoved not functioning properly

I am trying to implement an overriden touchesMoved func to enable SKSpriteNodes to be moved around by the user. However, I have to move the node very slowly for it to follow my touches when I drag. In addition, there are three SKSpriteNodes in the background (which you will see I explicitly set to .userInteractionEnabled = false) and these nodes will occasionally respond to the touches. Any help would be greatly appreciated. Let me know if there are any other parts of the code you need.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
var positionInScene = CGPoint(x: 375.0, y: 400.0) //sets a default position
titleLabel.userInteractionEnabled = false
drawingBoard.userInteractionEnabled = false
sideBar.userInteractionEnabled = false
for touch in touches {
positionInScene = touch.locationInNode(self)
if self.nodeAtPoint(positionInScene) is SKSpriteNode {
if (self.nodeAtPoint(positionInScene)).name == movableNodeName { //movableNodeName is the name assigned to all SKSpriteNodes that should be draggable
//I know this might be a strange way of doing it
(self.nodeAtPoint(touch.previousLocationInNode(self))).position = positionInScene
}
}
}
}
I've managed to fix this issue by just setting the coordinates of the node that shouldn't move back to what they were originally whenever it is moved. This seems to work as I haven't been able to replicate the bug again in testing. If anyone could come up with a better solution though, I would love to hear it.
Your problem is you are using touch.locationInNode(self), where self is your SKSpriteNode. This means that your touch move code will only respond to what is going on inside of your SKSpriteNode. What you need to do, is use either the parent of the sprite, or the scene, based on how you want to apply this logic.

How to make touch.locationInNode() recognize the difference between a node and its child?

I started out by declaring two SKSpriteNodes, handle and blade, and adding handle as a child of self, and blade as a child of handle
var handle = SKSpriteNode(imageNamed: "Handle.png")
var blade = SKSpriteNode(imageNamed: "Blade.png")
override func didMoveToView(view: SKView) {
handle.position = CGPointMake(self.size.width / 2, self.size.height / 14)
blade.position = CGPointMake(0, 124)
self.addChild(Handle)
Handle.addChild(Blade)
}
When I click on the handle, it prints to the console "Handle was clicked", however when I click on the Blade, it also prints "Handle was clicked". It is clearly recognizing that the blade is a child of handle, but how can I make it so when I click on blade, it prints "Blade was clicked"?
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if (Handle.containsPoint(location)){
NSLog("Handle was clicked")
}
else if (Blade.containsPoint(location)){
NSLog("Blade was clicked")
}
}
}
Determining whether the user touched the sword's handle or the blade is fairly straightforward with some caveats. The following assumes that 1. the sword image is facing to the right when zRotation = 0, 2. the anchorPoint of the sword is (0, 0.5), and 3. the sword (blade and handle) is a single sprite node. When you add a sprite to another sprite, the size of the parent's frame expands to include the child node. That's why your test of Handle.containsPoint is true no matter where you click on the sword.
The figure below shows a sword sprite with a dark gray handle (on the left) and lighter gray blade. The black rectangle surrounding the sword represents the sprite's frame and the circle represents the location of the user's touch. The length of the line labeled a is the distance from the touch point to the bottom of the sword. We can test this distance to see if the user touched the handle (if a <= handleLength) or the blade (if a > handleLength). When zRotation = 0, a = x so the test is x <= handleLength, where the bottom of the sword is x = 0.
In the below figure, the sword is rotated by 90 degree (i.e., zRotation = M_PI_2). Similarly, if a <= handleLength, the user touched the handle, else the user touched the blade. The only difference is a is now the y value instead of x due to the sword's rotation. In both cases, the frame's bounding box can be used, as is, to detect if the user touched the sword.
When the sprite is rotated by 45 degree, however, its frame automatically expands to enclose the sprite as shown by the black rectangle in the figure below. Consequently, when the user touches anywhere in the rectangle, the test if sprite.frame.contains(location) will be true. This may result in the user picking up the sword when the location of the touch is relatively far from the sword (i.e., when the distance b is large). If we want the maximum touch distance to be the same across all rotation angles, additional testing is required.
The good news is Sprite Kit provides a way to convert from one coordinate system to another. In this case, we need to convert from scene coordinates to the sword coordinates. This greatly simplifies the problem because it also rotates the point to the new coordinate system. After converting from scene to sword coordinates, the converted touch location's x and y values are the same as the distances a and b over all rotation angles! Now that we know a and b, we can determine how close the touch was to the sword and whether the user touched the handle or the blade.
From the above, we can implement the following code:
let location = touch.locationInNode(self)
// Check if the user touched inside of the sword's frame (see Figure 1-3)
if (sword.frame.contains(location)) {
// Convert the touch location from scene to sword coordinates
let point = sword.convertPoint(location, fromNode: self)
// Check if the user touched any part of the sword. Note that a = point.x and b = point.y
if (fabs(point.y) < sword.size.height/2 + touchTolerance) {
// Check if the user touched the handle
if (point.x <= handleLength) {
println("touched handle")
}
else {
println("touched blade")
}
}
}
This should work without changing too much of your existing code...
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let locationInScene = touch.locationInNode(self)
let locationInHandel = touch.locationInNode(Handle)
if (Blade.containsPoint(locationInHandel)){
NSLog("Blade was clicked")
}
else if (Handle.containsPoint(locationInScene)){
NSLog("Handle was clicked")
}
}
}
Note you are checking for blade first then you check for handle. Also note you have to convert the touchpoint to give you a point from within handle.
With that being said this will work on a small scale, but you may want to look at creating a subclass for SKSpriteNode called Handle or Sword (this is why you don't use first caps for variable names they are normally only use first caps for classes), set it to userInteractionEnabled and then override touchesBegan in that subclass and see if it is touching the blade and if not you know it touched the handle.
Hopefully that helped and made sense.

How to know 2 touch event changed to 1 touch in Sprite Kit

I am trying to identify in my sprite kit game when a multi touch of 2 touches changes to 1 touch, Like 2 thumbs to 1 thumb. So there are no new touch events so I attempted to find the change in the touchesMoved function. I have tried doing things like this...
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if event.allTouches()?.count > 1 && self.shieldActivated {
println("two touches moved")
} else {
println("1 touch moved")
}
}
I am able to see when there are two touches and one of the touches leaves the screen but it also picks up when one of the touches just moves so it doesn't work. self.shieldActivated is just a Boolean that tells that the touchesBegan event with 2 fingers did occur. I couldn't find anything else that seemed to do the trick. Does anyone have any ideas? thanks!

EXEC_BAD_ACCESS when adding sprite to view

I'm playing around with Sprite Kit in Swift on 10.9 using XB6. I have some code in my Scene that adds a sprite at the location of a mouse click. It loads the sprite thus:
let location = theEvent.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.position = location
sprite.setScale(0.5)
self.addChild(sprite)
This code runs fine for a while; I click and a sprite appears where I expect it to. But if I keep clicking, eventually that second line will result in a:
EXEC_BAD_ACCESS (code=EXC_I386_GPFLT)
(I wish they let you copy the error...). Sometimes it takes 5 clicks, other times 20, there's no apparent pattern to it. Googling the error, its obviously something that's happening deep in the bowels of SK or Swift.
Is anyone else seeing this?
Is this in the touchesBegan function? If so, did you use a loop?
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches{
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.position = location
sprite.setScale(0.5)
self.addChild(sprite)
}
}
OK this turned out to be an error in the beta builds. Replacing my Xcode with the 6.0 release and then copying the 10.10 SDK from 6.1B fixes this problem.