TouchesEnded is called the other button is tapped and cancels other actions - ios

When the player is holding down a button to move around and then presses the shoot button, TouchesEnded is called which then cancels the player's movement. Both actions work separately but not when they're both called at the same time.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touch = touches.first {
let location = touch.location(in: self)
let objects = nodes(at: location)
for node in objects {
if node.name == "leftBtn" {
player.run(player.leftMovement, withKey: "leftMovement")
} else if node.name == "rightBtn" {
player.run(player.rightMovement, withKey: "rightMovement")
} else if node.name == "upBtn" {
let jump = SKAction.applyImpulse(CGVector(dx: 0, dy: 1000), duration: 0.2)
player.run(jump, withKey: "jump")
} else if node.name == "downBtn" {
let downMovement = SKAction.applyImpulse(CGVector(dx: 0, dy: -500), duration: 0.2)
player.run(downMovement, withKey: "downMovement")
} else if node.name == "shootBtn" {
player.shoot()
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
player.removeAction(forKey: "leftMovement")
player.removeAction(forKey: "rightMovement")
player.removeAction(forKey: "jump")
player.removeAction(forKey: "downMovement")
}
I expect both actions to work independently from another, but unfortunately,​ that is not the case.

This is probably because when you are touching the shoot button, touchesEnded is also being called which will cancel all your movements.
Similar to how you are checking which nodes were touched in your touchesBegan method, you will need to check if the shoot button was pressed in touchesEnded:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let objects = nodes(at: location)
for node in objects {
if ["leftBtn", "rightBtn", "upBtn", "downBtn"].contains(node.name) {
player.removeAction(forKey: "leftMovement")
player.removeAction(forKey: "rightMovement")
player.removeAction(forKey: "jump")
player.removeAction(forKey: "downMovement")
}
}
}

Related

Touching moving Sprites in SpriteKit

I'm creating a SpriteKit game that involves touching falling targets. Currently, the targets are too difficult to catch on first touch (touchesBegan:), and only seem to be touchable by positioning your finger ahead of time (touchesMoved:). Is there a technique for dampening touches or widening the touch location to make the first touch more effective? My code looks something like this right now:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
print(positionInScene)
guard let touchedNode = self.nodes(at: positionInScene).first as? SKSpriteNode else { return }
if let dot = touchedNode.name {
if dot == "dot" {
removeTarget(touchedNode)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
print(positionInScene)
guard let touchedNode = self.nodes(at: positionInScene).first as? SKSpriteNode else { return }
if let dot = touchedNode.name {
if dot == "dot" {
removeTarget(touchedNode)
}
}
}
Are your entities too small? With the given info, I recreated a small scene in a playground, and all is working as expected. If I have a presented child node, defined as
let circle = SKShapeNode(circleOfRadius: 20)
And I have defined both the physicsWorld of the SKScene, and physicsBody of the SKNode, with the given touchesBegan, there is no problem in detecting a collision.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let location = touches.first?.location(in: self)
if self.atPoint(location) === circle {
print("TOUCH")
}
}
}

Touch event not making spritenode reset its size when moved off location of node

I have a sprite node in game that when touched makes an action of the node being pressed and when the touches began event is called and then goes back to the normal size when the touches ended event is called. my problem is when i press down on the node and then move my finger outside of the node it doesn't go back to its original size after I take my finger off the screen.
I tried using multiple things in the touches moved section of the code to try and get it to go back to its original size after i've moved my finger outside of the node while holding the touch down but it didn't work. My code is below
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
let node = self.atPoint(touch.location(in: self))
let pushdown = SKAction.scale(to: 0.8, duration: 0.1)
if node == mainMenu.settingsButton {
node.run(pushdown)
} else if node == mainMenu.viewMapButton {
node.run(pushdown)
}else if node == mainMenu.shopButton {
node.run(pushdown)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
var location: CGPoint = touch.location(in: self)
let node: SKNode = atPoint(location)
let pushUp = SKAction.scale(to: 1.0, duration: 0.2)
if node != mainMenu.settingsButton {
//node.run(pushUp)
} else if touch.phase == .moved || touch.phase == .cancelled {
node.run(pushUp)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
let node = self.atPoint(touch.location(in: self))
let pushUp = SKAction.scale(to: 1.0, duration: 0.2)
if node == mainMenu.settingsButton {
node.run(pushUp)
//Run Sound Here
let scene = SettingsMenu(fileNamed:"SettingsMenu")
scene?.scaleMode = .aspectFill
//scene?.backgroundColor = UIColor.lightGray
let transition = SKTransition.crossFade(withDuration: 0.5)
self.scene?.view?.presentScene(scene!, transition: transition)
} else if node == mainMenu.viewMapButton {
node.run(pushUp)
}
}
How can i get it to go back to the original size after i've moved my finger outside of the nodes location while holding the touch down?
In touchesBegan, touchesMoved, touchesEnded you are referring always the current node at point. let node = self.atPoint(touch.location(in: self))
Why not to keep a reference to the initial node which was detected in touchesBegan to scale it back if the current node atPoint is not equal to the initial one. I assume this would solve your issue.
EDIT:
To capture the node on first touch ...
private var currentTouchedNode : SKNode?
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
let touch: UITouch = touches.first!
currentTouchedNode = self.atPoint(touch.location(in: self))
let pushdown = SKAction.scale(to: 0.8, duration: 0.1)
if currentTouchedNode == mainMenu.settingsButton {
currentTouchedNode.run(pushdown)
} else if currentTouchedNode == mainMenu.viewMapButton {
currentTouchedNode.run(pushdown)
}else if currentTouchedNode == mainMenu.shopButton {
currentTouchedNode.run(pushdown)
}
}
In touchesMoved, touchesEnded you would compare on if the current node is equal currentTouchedNode and resize it if needed

Detecting UITouches while UIViewController is being shown as 3DTouch preview

Is it possible to detect touches and get the location of a touch from a UIViewController which is being currently used as previewingContext view controller for 3D Touch? (I want to change the image of within the preview controller when the touch moves from left to right)
I've tried both touchesBegan and touchesMoved none of them are fired.
class ThreeDTouchPreviewController: UIViewController {
func getLocationFromTouch(touches: Set<UITouch>) -> CGPoint?{
guard let touch = touches.first else { return nil }
return touch.location(in: self.view)
}
//Not fired
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let location = getLocationFromTouch(touches: touches)
print("LOCATION", location)
}
//Not fired
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let location = getLocationFromTouch(touches: touches)
print("LOCATION", location)
}
}
Even tried adding a UIPanGesture.
Attempting to replicate FaceBook's 3D Touch feature where a user can move finger from left to right to change the current image being displayed.
Video for context: https://streamable.com/ilnln
If you want to achive result same as in FaceBook's 3D Touch feature you need to create your own 3D Touch gesture class
import UIKit.UIGestureRecognizerSubclass
class ForceTouchGestureRecognizer: UIGestureRecognizer {
var forceValue: CGFloat = 0
var isForceTouch: Bool = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
handleForceWithTouches(touches: touches)
state = .began
self.isForceTouch = false
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
handleForceWithTouches(touches: touches)
if self.forceValue > 6.0 {
state = .changed
self.isForceTouch = true
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = .ended
handleForceWithTouches(touches: touches)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
state = .cancelled
handleForceWithTouches(touches: touches)
}
func handleForceWithTouches(touches: Set<UITouch>) {
if touches.count != 1 {
state = .failed
return
}
guard let touch = touches.first else {
state = .failed
return
}
forceValue = touch.force
}
}
and now you can add this gesture in your ViewController in viewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
let gesture = ForceTouchGestureRecognizer(target: self, action: #selector(imagePressed(sender:)))
self.view.addGestureRecognizer(gesture)
}
Now you can manage your controller UI in Storyboard. Add cover view above UICollectionView and UIImageView in a center and connect it with IBOutlets in code.
Now you can add handler methods for gesture
func imagePressed(sender: ForceTouchGestureRecognizer) {
let location = sender.location(in: self.view)
guard let indexPath = collectionView?.indexPathForItem(
at: location) else { return }
let image = self.images[indexPath.row]
switch sender.state {
case .changed:
if sender.isForceTouch {
self.coverView?.isHidden = false
self.selectedImageView?.isHidden = false
self.selectedImageView?.image = image
}
case .ended:
print("force: \(sender.forceValue)")
if sender.isForceTouch {
self.coverView?.isHidden = true
self.selectedImageView?.isHidden = true
self.selectedImageView?.image = nil
} else {
//TODO: handle selecting items of UICollectionView here,
//you can refer to this SO question for more info: https://stackoverflow.com/questions/42372609/collectionview-didnt-call-didselectitematindexpath-when-superview-has-gesture
print("Did select row at indexPath: \(indexPath)")
self.collectionView?.selectItem(at: indexPath, animated: true, scrollPosition: .centeredVertically)
}
default: break
}
}
From this point you need to customize your view to make it look in same way as Facebook do.
Also I created small example project on GitHub https://github.com/ChernyshenkoTaras/3DTouchExample to demonstrate it

touchesMoved called without moving a finger

I'm trying to implement user control node with one ThumbStick and two buttons. I've create separate SKSpriteNode with control elements on it and override touch events of parent node to handle user touch.
The problem is that when I start touch screen, touchesMoved get called many times even if I don't move my finger.
Here is my touch events code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
for touch in touches {
let touchPoint = touch.location(in: self)
touchStatusLabel.text = String(format: "TOUCH BEGAN %#", arguments:[NSStringFromCGPoint(touchPoint)])
if aButton.frame.contains(touchPoint) {
NSLog("A BUTTON PRESSED")
delegate?.controlInputNode(self, beganTouchButtonWithName: aButton.name!)
}
else if bButton.frame.contains(touchPoint) {
NSLog("B BUTTON PRESSED")
delegate?.controlInputNode(self, beganTouchButtonWithName: bButton.name!)
}
else if touchPoint.x < 0 && touchPoint.y < 0 {
NSLog("THUMBSTICK PRESSED")
leftThumbStickNode.touchesBegan([touch], with: event)
leftThumbStickNode.position = pointByCheckingControlOffset(touchPoint)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
for touch in touches {
let touchPoint = touch.location(in: self)
touchStatusLabel.text = String(format: "TOUCH MOVED %#", arguments:[NSStringFromCGPoint(touchPoint)])
if !aButton.frame.contains(touchPoint) && !bButton.frame.contains(touchPoint) {
if touchPoint.x < 0 && touchPoint.y < 0 {
leftThumbStickNode.touchesMoved([touch], with: event)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
for touch in touches {
let touchPoint = touch.location(in: self)
touchStatusLabel.text = String(format: "TOUCH ENDED %#", arguments:[NSStringFromCGPoint(touchPoint)])
let node = atPoint(touchPoint)
if node == aButton || node == bButton {
delegate?.controlInputNode(self, endTouchButtonWithName: node.name!)
}
else {
leftThumbStickNode.touchesEnded([touch], with: event)
}
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
NSLog("TOUCH CANCELED")
leftThumbStickNode.touchesCancelled(touches, with: event)
}
You can try comparing the translation and if nothing has changed then don't do anything, so your touchesMoved function will look like this;
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
for touch in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
let translation = CGPoint(x: location.x - previousLocation.x, y: location.y - previousLocation.y)
if translation == CGPoint.zero {
continue
}
touchStatusLabel.text = String(format: "TOUCH MOVED %#", arguments:[NSStringFromCGPoint(location)])
if !aButton.frame.contains(location) && !bButton.frame.contains(location) {
if location.x < 0 && location.y < 0 {
leftThumbStickNode.touchesMoved([touch], with: event)
}
}
}
}
It appears that this problem occurs only during debugging so I assume that it is some debugger issue

Swift scene kit: move SCNNode to tap position?

Ok, working with ARKit in Swift here and trying to get a grip on this -
for my game controls I want to be able to control the point (position in 3d space) that the SCNNode is moving towards when the users finger is down on the screen, meaning started by the touchesBegan func.
I want it to be like Apple's fox game with the joystick her but in AR: https://developer.apple.com/library/content/samplecode/Fox/Introduction/Intro.html
My main issue is it doesn't seem that my SCNAction's position to move is being updated correctly on touchesMoved, and moreover I need the SCNNode to move at same speed to the position regardless of how far it is away.
Here's what I have, it is working but not correct:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return }
checkIfNodeTapped(touches: touches, node: theDude.node)
theDude.moveToPos(pos: getARPos(hitFeature: hitFeature))
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return }
theDude.updateWalkTo(pos: getARPos(hitFeature: hitFeature))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// if virtualObjectManager.virtualObjects.isEmpty {
//
// return
// }
// virtualObjectManager.reactToTouchesEnded(touches, with: event)
//Remove move actions
theDude.stopMoving()
}
func updateWalkTo(pos: SCNVector3)
{
walkAction = SCNAction.move(to: pos, duration: 1)
}
func moveToPos(pos: SCNVector3)
{
walkAction = SCNAction.move(to: pos, duration: 1)
self.node.runAction(walkAction, forKey: "walk")
}
func stopMoving()
{
self.node.removeAction(forKey: "walk")
}
where the walkAction is just a defined SCNAction. How can I fix this so that the node runs towards wherever the user's finger is on the screen (converted to AR points)?

Resources