Co-ordinate of touch location is zero - swift

I am struggling to get the coordinates based on touch.
For simplicity I have created a new Project using swift and a sprite kit game - the default spinning spaceship one.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed: "Spaceship")
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
**NSLog("position x %f and y %f", location.x, location.y)**
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1)
sprite.runAction(SKAction.repeatActionForever(action))
self.addChild(sprite)
}
}
All I added to the project was the NSLog line to output the coordinates of the touch. But all I get in the output is 0 for x and y.
Anyone have any idea?

Convert whatever the location object is returning for variables x and y to a Float if you want to print the coordinates using NSLog with the %f formatter.
NSLog("position x %f and y %f", Float(location.x), Float(location.y))
If you aren't dead set on using NSLog, you can also do what Okapi suggested in his comment and use println. Both mechanisms work.
println("x:\(location.x) y:\(location.y)")

Related

How can I change the touch offset of a sprite in SpriteKit?

When I touch the player mallet (Air Hockey), I want to make it so the mallet moves slightly above the touch. This way the mallet will be more visible in the game. I have found some solutions but am having a hard time implementing properly in my function.
Here is a sample of my touchesMoved() function:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
bottomTouchIsActive = true
var releventTouch:UITouch!
//convert set to known type
let touchSet = touches
//get array of touches so we can loop through them
let orderedTouches = Array(touchSet)
for touch in orderedTouches
{
//if we've not yet found a relevent touch
if releventTouch == nil
{
//look for a touch that is in the activeArea (Avoid touches by opponent)
if activeArea.contains(touch.location(in: parent!))
{
isUserInteractionEnabled = true
releventTouch = touch
}
else
{
releventTouch = nil
}
}
}
if (releventTouch != nil)
{
//get touch position and relocate player
let location = releventTouch!.location(in: parent!)
position = location
//find old location and use pythagoras to determine length between both points
let oldLocation = releventTouch!.previousLocation(in: parent!)
let xOffset = location.x - oldLocation.x
let yOffset = location.y - oldLocation.y
let vectorLength = sqrt(xOffset * xOffset + yOffset * yOffset)
//get eleapsed and use to calculate speed6A
if lastTouchTimeStamp != nil
{
let seconds = releventTouch.timestamp - lastTouchTimeStamp!
let velocity = 0.01 * Double(vectorLength) / seconds
//to calculate the vector, the velcity needs to be converted to a CGFloat
let velocityCGFloat = CGFloat(velocity)
//calculate the impulse
let directionVector = CGVector(dx: velocityCGFloat * xOffset / vectorLength, dy: velocityCGFloat * yOffset / vectorLength)
//pass the vector to the scene (so it can apply an impulse to the puck)
delegate?.bottomForce(directionVector, fromBottomPlayer: self)
delegate?.bottomTouchIsActive(bottomTouchIsActive, fromBottomPlayer: self)
}
//update latest touch time for next calculation
lastTouchTimeStamp = releventTouch.timestamp
}
}
Is the position var, which you take from the touch location, used to set the position of the mallet? If it is, then if you want the mallet above the touch, why not do something like position.y += 50 immediately after position = location to move it up by 50 points?
Alternatively, you might find it more logical to set the mallet's anchorPoint property (https://developer.apple.com/documentation/spritekit/skspritenode/1519877-anchorpoint and https://developer.apple.com/documentation/spritekit/skspritenode/using_the_anchor_point_to_move_a_sprite) to be somewhere other than the default poisition (the centre of the sprite) e.g. the point that corresponds to the part of the handle of the mallet where one would normally hold it.

An efficient way to find all SKNodes within a certain distance from a given point?

If I want to know which node or nodes cover certain point of the scene I can do either scene.nodeAtPoint(point) or scene.nodesAtPoint(point). Is there an efficient way to do something similar with a circle? That is to be able to find all nodes that have their positions within certain distance from a given point.
I need this to be able to handle user taps better. The scene is rather scarce (not much user-active sprites on it), so there is no need to demand the user to be very precise with tapping. I want to use some sort a tolerance around the coordinates of the tap and detect the node that is just close enough to tap location.
The best idea that I have so far is to do nodeAtPoint() several times: at the location of the tap, and then at various offsets from it (e.g. up, up-right, rigth, ..., up-left).
Distance
First of all we need an extension to find the distance between 2 CGPoint(s)
extension CGPoint {
func distance(point: CGPoint) -> CGFloat {
return CGFloat(hypotf(Float(point.x - self.x), Float(point.y - self.y)))
}
}
The closest child
Now we can add an extension to SKScene to find the child closest to a given point and within a maxDistance.
extension SKScene {
func closestChild(point: CGPoint, maxDistance: CGFloat) -> SKNode? {
return self
.children
.filter { $0.position.distance(point) <= maxDistance }
.minElement { $0.0.position.distance(point) < $0.1.position.distance(point) }
}
}
Time
The computational complexity of the extension above is O(n) where n is the number of direct children of the scene.
To determine if one or more nodes in the scene are within a fixed distance of a touch event, 1) compute the distance between the touch and each node and 2) compare if that distance is less than or equal to a constant. Here's an example of how to do that:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let location = touch.locationInNode(self)
let nodes = nodesNearPoint(self, point:location, maxDistance: 30)
if nodes.count > 0 {
print("node is near touch point")
}
}
}
// Returns an array of all SKNode objects in the subtree that near the specified point.
// If no nodes are near the point, an empty array is returned.
func nodesNearPoint(container:SKNode, point:CGPoint, maxDistance:CGFloat) -> [SKNode] {
var array = [SKNode]()
for node in container.children {
// Only test sprite nodes (optional)
if node is SKSpriteNode {
let dx = point.x - node.position.x
let dy = point.y - node.position.y
let distance = sqrt(dx*dx + dy*dy)
if (distance <= maxDistance) {
array.append(node)
}
}
}
return array
}
Thanks to all the answerers, this is what I came up with in the end:
extension SKNode {
private func squareDistanceTo(point: CGPoint) -> CGFloat {
let dx = point.x - position.x
let dy = point.y - position.y
return (dx * dx + dy * dy)
}
func nearestNode(point: CGPoint) -> SKNode {
return children.minElement() {
$0.squareDistanceTo(point) < $1.squareDistanceTo(point)
} ?? self
}
func nodesInVicinity(point: CGPoint, tolerance: CGFloat) -> [SKNode] {
let squareTolerance = tolerance * tolerance
return children.filter() {
$0.squareDistanceTo(point) <= squareTolerance
}
}
}
Above extends SKNode with functionality:
nearestNode to find a child node nearest to the specific point, or give back the parent if there's no child nodes.
nodesInVicinity to get an array of nodes that have their positions within certain distance from a given point.
After some test I came to the conclusion that even nodeAtPoint has O(n) complexity, hence no hope for quick wins.
One way I've handled this before is to create a circle (using a png of a circle), setting the size to the radius I'm interested in and then seeing what nodes fall in that radius.
This is the basic idea assuming otherNodes is an array of the nodes I'm interested in checking:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let radius = 500
let circle = SKSpriteNode(texture: SKTexture(imageNamed: "circle.png"), color: UIColor.clearColor(), size: CGSize(width: radius, height: radius))
circle.position = touches.first!.locationInNode(self)
for node in otherNodes {
if circle.containsPoint(node.position) {
//The center of this node falls in the radius
}
}
}
EDIT:
After looking at an old solution where I used this, I noticed that containsPoint is actually looking to see if the point is within the node's bounding box, so having a png of a circle is actually irrelevant and consequently, matches might fall outside of the radius of the circle, but still within its bounding box.

Why do I need to adjust after convert point?

My scene is setup scene > world: sknode > camera: sknode > ship: SKSpriteNode
I want the ship to point to the location of the touch:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touchLocation = touch.locationInNode(self)
let location = cameraNode.convertPoint(touchLocation, toNode: world)
moveCameraNode(CGPointsToVector(location, b: cameraNode.position))
let shipTouchPosition = scene?.convertPoint(touchLocation, fromNode: world)
let shipPosition = scene?.convertPoint(ship.position, fromNode: world)
let angle = atan2(shipTouchPosition!.y - shipPosition!.y, shipTouchPosition!.x - shipPosition!.x)
let magicNumber = CGFloat(M_PI_2)
ship.runAction(SKAction.rotateToAngle(angle - magicNumber, duration: 0.1, shortestUnitArc: true))
}
override func didFinishUpdate() {
centerOnNode(cameraNode)
}
func centerOnNode(node: SKNode) {
let cameraPositionInScene = node.scene?.convertPoint(node.position, fromNode: node.parent!)
node.parent!.position = CGPointsSubtract(node.parent!.position, b: cameraPositionInScene!)
}
So, it now works ok but I'm not sure why I need the magicNumber.
EDIT: Thanks to #mundi for catching a stupid error on my part which was obscuring my real question of what I'm doing wrong with convertPoint that I have to adjust it afterwards.
Angles are calculated in radians. 360 degrees are 2 times the constant PI, or 2 * M_PI. In SpriteKit, angle calculations start from center right (equivalent to 3 o'clock), corresponding to 90 degrees or one half PI, M_PI_2.
80 read as radians evaluates to 1.46018 which is very close to M_PI_2 or 1.57079. This is why this number appears to "magically" fix this discrepancy, even though it is slightly off. The true "magic" number is CGFloat(M_PI_2).

Returning coordinates from touches in Swift

I am currently in the process of writing a PONG game in Swift. I want the player's paddle to be controlled by the player's finger, so the paddle would move where ever the player drags their finger on the screen. I want to be able to find out the coordinates of the player's finger touch so I can implement this. By searching around Google and stack overflow, I have found this function:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject()! as UITouch
let location = touch.locationInView(self.view)
}
Though I am not really sure what it does, how it does it and how to obtain the x and y values of the touch from it. I would really appreciate it if someone could tell my how to obtain the x/y coordinates of the users!! I want the end result to be something similar to this:
PlayerPaddle.center.x = XTouchCoordinate
THANKS A LOT!!
For dragging use UIPanGestureRecognizer as for your original question of obtaining the x and y coordinates you can use something like this:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
{
let touch = touches.first as! UITouch
let location = touch.locationInView(self.view)
var xcoord = location.x
println(location.y)
}
If you are using GoogleMap SDK
extension TargetViewController: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D)
{
print("Tapped at Latitude: " + String(coordinate.latitude) + ", Longitude: "
+ String(coordinate.longitude))
}
}

Swift nodeAtPoint difficulty

I am building a game in which the player drags a piece around the gameboard. I wish to know what are all of the nodes underneath that piece, and I am getting odd results. Here is the touchesMoves func:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
self.position = location
println("checker x: \(location.x)")
println("node at point: \(self.nodeAtPoint(location))")
println("nodes at point: \(self.nodesAtPoint(location))")
}
The sprite moves around the board just fine, but what is reported as the nodeAtPoint is always the sprite being moved around (which kind of makes sense but is not useful. Any oddly, the nodesAtPoint is always reported as an empty array! How is this possible? What should I be doing differently?
Update: This continues to be a struggle. I want to keep the touch methods in the node itself, and not the scene. The most recent version of the touchesMoved is the following:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
// Turn off the touched bool:
touched = false
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
let loc = touch.locationInView(scene.view)
let loc2 = touch.locationInNode(scene)
self.position = location
println("checker: \(loc2.x), \(loc2.y)")
println("node at point: \(self.nodeAtPoint(loc2).name)")
println("nodes at point: \(self.nodesAtPoint(loc2))")
}
The nodesAtPoint array continues to be empty with one really odd exception. When hovering near the center of the scene, I get this:
nodes at point: [ name:'(null)' accumulatedFrame:{{-30, -19.80000114440918}, {60, 39.600002288818359}}]
There is not shape node there that I am aware of! Why am I not detecting the nodes I pass over?
I discovered the answer to this. Essentially I was trying to detect nodesAtPoint on self, which in this case was the node being moved around the screen. Once I changed that to self.scene, the nodesAtPoint array populated as expected. So, to be clear:
println("nodes at point: \(self.nodesAtPoint(loc2))")
Needed to be changed to this:
println("nodes at point: \(self.scene.nodesAtPoint(loc2))")
If self is SKScene, try to change
let location = touch.locationInNode(self.parent)
to
let location = touch.locationInNode(self)
because SKScene's parent is nil