UITapGesture and TouchesBegan are producing different locations in view - swift

I'm using SKTileMaps and a CameraNode as well as GameplayKit and SpriteKit
The code I want to work is incorrectly finding a location in the "view"
func handleTapFrom(_ sender: UITapGestureRecognizer)
{
let location = sender.location(in: self.view)
if (map.contains(location))
{
let tRow = map.tileRowIndex(fromPosition: location)
let tColumn = map.tileColumnIndex(fromPosition: location)
let movePosition = map.centerOfTile(atColumn: tColumn, row: tRow)
let moveAction = SKAction.move(to: movePosition, duration:1.0)
cam.run(moveAction)
}
}
and the code that is working correctly is finding locations in self (rather than self.view) and this is correct
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let firstTouch = touches.first?.location(in: self)
if (map.contains(firstTouch!))
{
let tRow = map.tileRowIndex(fromPosition: firstTouch!)
let tColumn = map.tileColumnIndex(fromPosition: firstTouch!)
let movePosition = map.centerOfTile(atColumn: tColumn, row: tRow)
let moveAction = SKAction.move(to: movePosition, duration:1.0)
cam.run(moveAction)
}
}
The columns and rows are correct based on the location, the problem I am having is that the locations (xfloat, yfloat) are different between the two functions even when the touch is at the same location
These two bits of code are all in the class GameScene, but I cannot figure out why they have different locations? And how to fix the tap gesture so that the locations it finds are the same as those found in touchesBegan.
Sorry if this is confusing or unintelligible. I welcome an opportunity to clarify.

In your handleTapFrom function the location should come from convertPoint.
func handleTapFrom(_ sender: UITapGestureRecognizer) {
if sender.state != .ended {
return
}
let location = sender.location(in: sender.view!)
let targetLocation = self.convertPoint(fromView: location)
if map.contains(targetLocation) {
// Your code here
}
}

Related

How can I access the indices of a hit test?

I am trying to add a different object to a scene with a hit test, and I am currently only able to add one object. I want to access the second, third and fourth indices in my hit test results in order to to add these objects with a touch.
I have tried accessing through .indices but it is not compatible with UITouch.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchLocation = touch.location(in: sceneView)
let results = sceneView.hitTest(touchLocation, types: .featurePoint)
if let hitResult = results.first {
let cubeScene = SCNScene(named: "art.scnassets/cube.scn")!
if let cubeNode = cubeScene.rootNode.childNode(withName: "cube", recursively: true) {
sceneView.scene.rootNode.addChildNode(cubeNode)
}
}
}
}
How should I go about this? I have thought about creating one SCNScene, pass it as an array, and then would load all the models from the scene.
As Samual suggested... example
var hitTestOptions = [SCNHitTestOption: Any]()
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
let location: CGPoint = recognizer.location(in: gameScene)
let hitResults: [SCNHitTestResult] = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
gameControl.selectPanel(vPanel: vHit.node.name!)
return
}
}
}

Changing Scenes In Spritekit, Detecting Touches on Buttons

I am attempting to change from a home scene to a game scene in Spritekit. When I run it in the simulator, there is no transition to the GameScene. I also added a print command to see if the touches on the play button were even being registered, and they were not. Here's the code. Thanks!
import SpriteKit
class HomeScene: SKScene {
var playButton: SKSpriteNode?
var gameScene: SKScene!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "startButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == playButton {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
print("touch")
}
}
}
}
The touches parameter inside of your touchesBegan(_: with:) method is of type Set<UITouch>, i.e. a set of touches. As stated in the Apple Documentation for Sets, a set is used when the order of the items in the collection are not a concern. Therefore, you cannot assume that the first item in touches is the first touch in the set. Knowing this, I recommend refactoring your code inside of HomeScene.swift to the following:
import SpriteKit
class HomeScene: SKScene {
private var playButton: SKSpriteNode?
override func sceneDidLoad() {
self.playButton = self.childNode(withName: "//starButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}
func touchDown(atPoint pos: CGPoint) {
let nodes = self.nodes(at: pos)
if let button = playButton {
if nodes.contains(button) {
let gameScene = SKScene(fileNamed: "GameScene")
self.view?.presentScene(gameScene)
}
}
}
The touchDown(atPoint:) method does all the heavy lifting and touchesBegan(_: with:) is freed up to listen for touch events. Another thing to note when calling childNode(withName:):
Adding / before the name of the file starts searching for nodes beginning at the root of your file hierarchy.
Adding // before the name of the file starts searching for nodes beginning at the root of your file hierarchy, then recursively down the hierarchy.
Adding * before the name of the file acts as a character wildcard and allows you to search for file names that are wholly or partially unknown.
I think you should use the node's name instead of the node itself for comparison in the touchesBegan... try something like this:
import SpriteKit
class HomeScene: SKScene {
var playButton: SKSpriteNode?
var gameScene: SKScene!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "startButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "startButton" {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
print("touch")
}
}
}
}
Hope it helps! :)

Button is still working even when removeFromParent() in Swift SpriteKit is called

In the code below if I press the removeButton the button would disappear but it's still working. Maybe I should make the if statement false but I don't know how.
class GameScene : SKScene {
var button : SKSpriteNode!
var removeButton : SKSpriteNode!
var image : SKSpriteNode!
override func didMove(to view: SKView) {
createButton()
createRemoveButton()
}
func createButton() {
button = SKSpriteNode(imageNamed: "button")
button.position = CGPoint(x: -300,y: 0)
self.addChild(button)
}
func createRemoveButton() {
removeButton = SKSpriteNode(imageNamed: "removeButton")
removeButton.position = CGPoint(x: 300,y: 0)
self.addChild(removeButton)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if button.contains(touchLocation) {
image = SKSpriteNode(imageNamed: "image")
image.position = CGPoint(x: 0,y: 300)
self.addChild(image)
}
if removeButton.contains(touchLocation) {
button.removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
}
}
You need to understand ARC (Automatic Reference Counting) and what it takes to retain an instance.
In your case you are retaining button, which means it does not delete from memory.
Now when you remove the button from the parent, the button's frame is still exactly the same, the only thing different is button.parent is now nil
When you call button.contains(touchLocation), this is going to pass because you are not concerned with whether or not button is on the scene, all you are doing is checking if the touchLocation is within the frame of the button.
The quickest fix is to check the parent:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if button.parent == self && button.contains(touchLocation) {
image = SKSpriteNode(imageNamed: "image")
image.position = CGPoint(x: 0,y: 300)
self.addChild(image)
}
if removeButton.contains(touchLocation) {
button.removeFromParent()
}
}
In reality though, you need to learn how to better manage your resources. I would recommend trying to find tutorials about how ARC works.
It is normal because you dont use the good button. You use button instead of removeButton.
You should do:
if removeButton.contains(touchLocation) {
removeButton.removeFromParent()
}
Now it should work correctly

how to get the delta of swipe/ draging touch

I want to calculate the delta between a swipe gesture or dragging gesture. what I want to do is to get this delta and then to use it as velocity (by adding time var). I really get confused about how should I do this - via touchesMoved or via UIPanGestureRecognizer. also, I don't really understand the difference between them. right now I set and get the first touch on the screen but I don't know how to get the last one so I can calculate the vector. can anyone help me with that?
right now I'm doing that via touchesBegan and touchesEnded, I'm not sure if its the right and the better way, here is my code so far:
class GameScene: SKScene {
var start: CGPoint?
var end: CGPoint?
override func didMove(to view: SKView) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.start = touch.location(in: self)
print("start point: ", start!)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.end = touch.location(in: self)
print("end point: ", end!)
let deltax:CGFloat = ((self.start?.x)! - (self.end?.x)!)
let deltay:CGFloat = ((self.start?.y)! - (self.end?.y)!)
print(UInt(deltax))
print(UInt(deltay))
}
You can detect swipe gestures using the built-in touch handlers of SpriteKit or you can implement a UISwipeGestureRecognizer. The following is an example of how to detect swipe gestures using SpriteKit's touch handlers:
First, define the variables and constants...
Define the starting point and time of the initial touch.
var touchStart: CGPoint?
var startTime : TimeInterval?
Define constants that specify the characteristics of the swipe gesture. By changing these constants, you can detect the difference between a swipe, drag, or flick.
let minSpeed:CGFloat = 1000
let maxSpeed:CGFloat = 5000
let minDistance:CGFloat = 25
let minDuration:TimeInterval = 0.1
In touchesBegan, store the starting point and time of the initial touch
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touchStart = touches.first?.location(in: self)
startTime = touches.first?.timestamp
}
In touchesEnded, calculate the gesture's distance, duration, and speed. Compare these value against the constants to determine if the gesture was a swipe.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchStart = self.touchStart else {
return
}
guard let startTime = self.startTime else {
return
}
guard let location = touches.first?.location(in: self) else {
return
}
guard let time = touches.first?.timestamp else {
return
}
var dx = location.x - touchStart.x
var dy = location.y - touchStart.y
// Distance of the gesture
let distance = sqrt(dx*dx+dy*dy)
if distance >= minDistance {
// Duration of the gesture
let deltaTime = time - startTime
if deltaTime > minDuration {
// Speed of the gesture
let speed = distance / CGFloat(deltaTime)
if speed >= minSpeed && speed <= maxSpeed {
// Normalize by distance to obtain unit vector
dx /= distance
dy /= distance
// Swipe detected
print("Swipe detected with speed = \(speed) and direction (\(dx), \(dy)")
}
}
}
// Reset variables
touchStart = nil
startTime = nil
}

SpriteKit: How to identify the previous scene the user is coming from?

I have a GameOver scene with a button to a Settings scene. (Code shown below) I want to add a button on the MainMenu scene to do the same, send the user to the Settings scene but I want just one “Back” button on the Settings scene. I'm assuming this can be accomplished with an IF statement. If the user comes from A scene move to B else if the user comes from X scene move to Y scene.
How can I tell in SpriteKit which scene is the user coming from?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "BackButton" {
let sceneToMoveTo = SKScene(fileNamed: "GameOverScene")
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo!, transition: gameTransition)
}
}
}
In this case you can adopt various solutions to achieve what you wish.
I want to show you just 2 example, a rapid and simple way and a more custom way.
1: - You can use the userData instance property of your next scene to store the current scene, something like:
sceneToMoveTo.userData = NSMutableDictionary()
sceneToMoveTo.userData?.setObject("MenuScene", forKey: "previousScene" as NSCopying)
and when you are in the next scene you can ask to this NSMutableDictionary who is the previous scene as:
guard let previousValue = self.userData?.value(forKey: "previousScene") else { return }
switch previousValue as! String {
case "MenuScene":
//do whatever you want if the previous scene was MenuScene
default:
break
}
2: - You can build an enumeration that contain a list of the scenes where you want to move.
After that, each scene contain a simple var to store the previous scene: it's default is .None but you should fill it when you call the new scene. When you arrive to the SettingsScene and press your back button you can ask to this previousScene var who is the previous scene and decide, through a switch statement, where to go.
// GAMEOVER SCENE
import SpriteKit
enum Scenes : Int {
case MainMenu = 0
case Settings = 1
case GameOver = 2
case None = 3
}
class GameOver: SKScene {
var previousScene:Scenes = .None
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "settingsBtn" {
if let sceneToMoveTo = SKScene(fileNamed: "SettingsScene") {
let gameTransition = SKTransition.fade(withDuration: 0.5)
(sceneToMoveTo as! SettingsScene).previousScene = .GameOver
self.view?.presentScene(sceneToMoveTo, transition: gameTransition)
}
}
}
}
}
// SETTINGS SCENE
import SpriteKit
class SettingsScene: SKScene {
var previousScene:Scenes = .None
override func didMove(to view: SKView) {
print("Current scene loaded: \(type(of:self))")
print("Previous scene was: \(previousScene)")
let label = SKLabelNode.init(text: "BackButton")
addChild(label)
label.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
label.name = "backBtn"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "backBtn" {
switch previousScene {
case .MainMenu: // go back to menu
if let sceneToMoveTo = SKScene(fileNamed: "MenuScene") {
let gameTransition = SKTransition.fade(withDuration: 0.5)
(sceneToMoveTo as! MenuScene).previousScene = .Settings
self.view?.presentScene(sceneToMoveTo, transition: gameTransition)
}
break
case .GameOver: // re-launch a new game
print("The game was ended")
if let sceneToMoveTo = SKScene(fileNamed: "GameScene") {
let gameTransition = SKTransition.fade(withDuration: 0.5)
(sceneToMoveTo as! GameScene).previousScene = .Settings
self.view?.presentScene(sceneToMoveTo, transition: gameTransition)
}
default:
break
}
}
}
}
}
//MENU SCENE
import SpriteKit
class MenuScene: SKScene {
var previousScene:Scenes = .None
override func didMove(to view: SKView) {
print("Current scene loaded: \(type(of:self))")
print("Previous scene was: \(previousScene)")
}
}
userData is your friend here, it is a dictionary that can be found on any type of SKNode. All you need to do is store the current scene you are in into the userData, and make the transition. This will keep your current scene in memory. Since your current scene is in memory and is not a part of the new scenes node tree, no update method will be called on it. Essentially you have a frozen scene state. Then when you are ready to return to the scene, just pull the instance out of the user data, and present it. Viola, you have transitioned back to your scene the moment that you have left it previously.
Basically, if you want the scene to resume where it left off, do the following:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "SettingButton" {
let sceneToMoveTo = SKScene(fileNamed: "SettingScene")
sceneToMoveTo.userData = ["previousScene":self.scene]
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo!, transition: gameTransition)
}
}
}
Then, at any point you want to return to the previous scene, just add the following.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "BackButton" {
if let userData = self.userData, let previousScene = userData["previousScene"] as? SKScene
{
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(previousScene, transition: gameTransition)
}
}
}
}