Swift scene kit: move SCNNode to tap position? - ios

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)?

Related

Sprite Node position not updating with touch?

Essentially, what I want is for when I touch a node, I want to be able to move it across the screen. The problem is that whenever I move my finger too fast, the node just stops following it.
The spriteNodes in particular that I'm trying to do this with have physics bodies and animating textures so I tried to do the same code with a completely plain spriteNode and I've encountered the same problem.
The code that I have here is pretty simple so I'm not sure if this is a problem with what I've written or if it's just a lag problem that I can't fix. It's also basically the same all throughout touchesBegan, touchesMoved and touchesEnded
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "activeRedBomb"{
node.position = pos
}
if node.name == "activeBlackBomb"{
node.position = pos
}
if node.name == "test"{
node.position.x = pos.x
node.position.y = pos.y
}
}
What's happening is that if you move your finger too fast, then at some point, the touch location will no longer be on the sprite, so you code to move the node won't fire.
What you need to do is set a flag in touchesBegan() to indicate that this sprite is touched, move the sprite to the location of the touch in touchesMoved() if the flag is set and then reset the flag in touchesEnded().
Here's roughly what you need to add for this:
import SpriteKit
class GameScene: SKScene {
var bombIsTouched = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if activeRedBomb.contains(touch.location(in: self)) {
bombIsTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
activeRedBomb.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
bombIsTouched = false
}
}

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

How to check if a child node has been touched Swift 3

I have sprites moving across the screen, and if they are clicked then they disappear (i.e deleted).
I have overridden the touchesBegan func as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let location = touch.location(in: self)
for child in self.children {
if child.position == location {
child.removeFromParent()
}
}
}
This doesn't seem to have any effect, can someone tell me where I am going wrong?
In which class did you implement this method?
If it was in SKNode itself, you simply do:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.removeFromParent()
}
However, if this method is in SKScene, this way that was implemented would probably not work. Because child.position returns a point (x, y) where the touch was made. And you're trying to compare the touch point and position of the SKNode (center point), it's unlikely to work.
Instead of using this way, try using .nodeAtPoint, a method of SKScene.
For this you will need to put a value in the 'name' property of your SKNode:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name
{
if name == "your-node-name"
{
touchedNode.removeFromParent()
}
}
}
Font: How do I detect if an SKSpriteNode has been touched

How Can I Disable Swiping Ball In A Certain Area of the Screen? (Swift ) (SpriteKit)

Ok so currently in my game I have a ball that can be thrown. However, I want to make so that the user can only throw the ball past a certain point. I have been trying this for a while but I can't figure it out. How can I do this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first as! UITouch?
let location = touch?.location(in: self)
if ball.frame.contains(location!) {
touchPoint = location!
touching = true
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first as! UITouch?
let location = touch?.location(in: self)
touchPoint = location!
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func update(_ currentTime: TimeInterval) {
if touching {
let dt:CGFloat = 2.8/60.0
let distance = CGVector(dx: touchPoint.x-ball.position.x, dy: touchPoint.y-ball.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
ball.physicsBody!.velocity=velocity
}
}
Here, I have an image of my game so far. Currently, the ball can be dragged, thrown, or swiped anywhere on the screen. However, I want to make so the ball can only be touched, thrown, or dragged below the middle of the screen. But I want the ball to continue being thrown if the user's finger accidently goes about the limit.
you will have to adjust the coordinates for your bottom area based on actual size and game scene anchorPoint. for example "100" in the sample assumes that your scene has an anchorPoint of (0,0) and the size of the bottom scroll area is 100.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let location = touch.location(in: self)
guard location < 100 else { return }
//if ball.frame.contains(location!) {
touchPoint = location!
touching = true
//}
}
}

How to detect precisely when a SKShapeNode is touched?

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 //...
}
}
}

Resources