How to detect precisely when a SKShapeNode is touched? - ios

I'm working with Swift and SpriteKit.
I have the following situation :
Here, each of the "triangles" is a SKShapenode.
My problem is that I would like to detect when someone touches the screen which triangle is being touched.
I assume that the hitbox of all these triangles are rectangles so my function returns me all the hitboxes touched while I only want to know which one is actually touched.
Is there any way to have a hitbox that perfectly match the shape instead of a rectangle ?
Here's my current code :
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first
let touchPosition = touch!.locationInNode(self)
let touchedNodes = self.nodesAtPoint(touchPosition)
print(touchedNodes) //this should return only one "triangle" named node
for touchedNode in touchedNodes
{
if let name = touchedNode.name
{
if name == "triangle"
{
let triangle = touchedNode as! SKShapeNode
// stuff here
}
}
}
}

You could try to use CGPathContainsPoint with a SKShapeNode instead of nodesAtPoint, which is more appropriate:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first
let touchPosition = touch!.locationInNode(self)
self.enumerateChildNodesWithName("triangle") { node, _ in
// do something with node
if node is SKShapeNode {
if let p = (node as! SKShapeNode).path {
if CGPathContainsPoint(p, nil, touchPosition, false) {
print("you have touched triangle: \(node.name)")
let triangle = node as! SKShapeNode
// stuff here
}
}
}
}
}

This would be the easiest way of doing it.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
for touch in touches {
let location = touch.locationInNode(self)
if theSpriteNode.containsPoint(location) {
//Do Whatever
}
}
}

The way I do this with Swift 4:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchPosition = touch.location(in: self)
let touchedNodes = nodes(at: touchPosition)
for node in touchedNodes {
if let mynode = node as? SKShapeNode, node.name == "triangle" {
//stuff here
mynode.fillColor = .orange //...
}
}
}

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")
}
}
}

Get Property of SKSpriteNode in Touches

I am trying to read a property off of a SKSpriteNode in the touchesBegan method but the property does not exist. Where as it does on the created object elsewhere.
let enemy = enemy(imageName: "enemy.png",force: "12")
addChild(enemy)
enemy.name = "enemy"
print (enemy.force) // 12
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
let touchedNode = self.atPoint(touchLocation) as! SKSpriteNode
if(touchedNode.name == "enemy"){
print(enemy.force) //Force property does not exist
}
}
Knowing that SKSpriteNode don't have a force property, you should use your class name that inherits SKSpriteNode properties (used to make enemy..)
An example could be this:
class Enemy : SKSpriteNode {
var force: Int = 0
...
}
Then in your game scene do:
...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self)
let touchedNode = self.atPoint(touchLocation)
if(touchedNode.name == "enemy" && touchNode is Enemy){
// Yes, I'm absolutely sure this is an enemy node..
let enemy = touchedNode as! Enemy
print(enemy.force)
}
}

Detect touch on SKNode (swift)

I have created a container node to put in all my SKSpriteNodes that need to be moved all in one touch, I can detect touches on them normally in iOS 8 but in iOS 7 I can only detect touches on my main node and when I touch an SKSpriteNode that it's in the container node nothing happens.. How can I fix this?
let lvlsNode = SKNode()
override func didMoveToView(view: SKView) {
self.addChild(lvlsNode)
axe = SKSpriteNode(imageNamed:"axe")
axe.anchorPoint = CGPointMake(1, 0)
axe.size = CGSizeMake(axe.size.width/1.4, axe.size.height/1.4)
axe.position = CGPointMake(0+screenWidth/7, shield.position.y-shield.size.width*1.4)
axe.zPosition = 12
axe.name = "axe"
lvlsNode.addChild(axe)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node.name == "axe" {
// do something.... this work in iOS8 but not in iOS 7.1
}
Yournode.name = "nodeX"
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first as UITouch!
if atPoint((touch?.location(in: self))!).name == Yournode.name {
//Your code
}
}

Dragging of SKShapeNode Not Smooth

So I am super new to swift and iOS development but not totally new to programming, and was just going through some tutorials, but basically my code looks like this:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let circle = SKShapeNode(circleOfRadius: 30.0)
circle.position = CGPointMake(100, 200)
addChild(circle)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
touchedNode.position = location;
}
}
}
When I build this it recognizes and moves the circle when I drag it, but only for about 30 pixels and then I have to touch it again to move it. What am I doing wrong here?
I might be wrong but I believe your finger is leaving the node limits and enters the background. I would set it up as such:
// First make the shape available throughout the code and make sure you have a place to save the touch event for later use.
var circle: SKShapeNode!
var circleTouch: UITouch?
// Create the Shape
override func didMoveToView(view: SKView) {
circle = SKShapeNode(circleOfRadius: 30.0)
circle.position = CGPointMake(100, 200)
addChild(circle)
}
// When a finger is placed on the screen, check if it's in the circle, if it is, keep that touch even in memory so we can reference it later because other fingers could touch other things in the scene.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if nodeAtPoint(touch.locationInNode(self)) == circle {
circleTouch = touch as? UITouch
}
}
}
// Check if the touch event that is moving is the one that anchored the circle to our finger, if yes, move the circle to the position of the finger.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
let location = touch.locationInNode(self)
circle.position = location
}
}
}
}
// Clean up the touch event when the finger anchoring the circle is raised from the screen.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
circleTouch = nil
}
}
}
}
You can also use if let statements in there but I checked for nil instead for clarity.
Tokuriku, thanks so much, you we're not quite right but it got me to the eventual answer, here it is
import SpriteKit
class GameScene: SKScene {
var circleTouch: UITouch?
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let circle = SKShapeNode(circleOfRadius: 40.0)
circle.fillColor = UIColor.blackColor()
circle.position = CGPoint(x: size.width * 0.5, y: size.height * 0.2)
circle.name = "userCircle"
addChild(circle)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if nodeAtPoint(touch.locationInNode(self)).name == "userCircle" {
circleTouch = touch as? UITouch
}
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
touchedNode.position = location
}
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
circleTouch = nil
}
}
}
}

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