Touching moving Sprites in SpriteKit - ios

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

Related

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

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

How to find touched Node within a UIRotationRecognizer (SpriteKit + Swift 3.0)

I have three nodes lineBlock of the same class MovableBlock on the screen. I want to rotate the lineBlock node someone is touching on the screen.
I've solved this for moving the correct lineBlock node in touchedMoved:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
touch = touches.first!
positionInScene = self.touch?.location(in: self)
let previousPosition = self.touch?.previousLocation(in: self)
let translation = CGVector(dx: (positionInScene?.x)! - (previousPosition?.x)!, dy: (positionInScene?.y)! - (previousPosition?.y)!)
if touchedNode?.name?.contains("LineBlock") == true {
(touchedNode as! MovableBlock).selected = true
(touchedNode as! MovableBlock).parent!.parent!.run(SKAction.move(by: translation, duration: 0.0))
}
}
But I have not been able to do the same within my UIRotationRecognizer function. In my rotate function, it simply rotates the first node, regardless of which lineBlock (of class MovableBlock) I am touching:
func rotate(_ sender: UIRotationGestureRecognizer){
if lineBlock.selected == true {
lineBlock.run(SKAction.rotate(byAngle: (-(self.rotationRecognizer?.rotation)!*2), duration: 0.0))
rotationRecognizer?.rotation = 0
}
}
For reference, here's how I define touchedNode (in touchBegan):
touches: Set<UITouch>, with event: UIEvent?) {
touch = touches.first!
positionInScene = self.touch?.location(in: self)
touchedNode = self.atPoint(positionInScene!)
UIGestureRecognizers have a location(in:UIView) method. You can use self.view?.convert(sender.location(in: self.view), to: self) to get the UIRotationGestureRecognizer's location and use logic similar to that in touchesBegan.
convert(_:to:) will make sure that the point is in your scene's coordinate space.

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

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