How can I access the indices of a hit test? - swift

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

Related

Can MotionDetection in Swift perform 2 different actions?

I am detecting when a user shakes his phone. Is it possible to perform 2 independent actions? I mean first do this1, then do this2.
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
// first this needs to be done
let scene = makeScene()
animationView.frame.size = scene.size
animationView.presentScene(scene)
// then this needs to be done
let scene2 = makeScene2()
animationView.frame.size = scene2.size
animationView.presentScene(scene2)
}
}
When I do that only the second one is executed
Instead of dealing with hardcoded delays, try wrapping your animation in a CATransaction and run your 2nd step in its completion handler:
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
CATransaction.begin()
let scene = makeScene()
animationView.frame.size = scene.size
animationView.presentScene(scene)
CATransaction.setCompletionBlock {
let scene2 = self.makeScene2()
self.animationView.frame.size = scene2.size
self.animationView.presentScene(scene2)
}
CATransaction.commit()
}
}
Solved the problem by adding a delay between the actions.
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
let scene = makeScene()
animationView.frame.size = scene.size
animationView.presentScene(scene)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
let scene2 = self.makeScene2()
self.animationView.frame.size = scene2.size
self.animationView.presentScene(scene2)
})
}
}
}

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! :)

UITapGesture and TouchesBegan are producing different locations in view

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

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

Sprite Kit Button touch location error | Swift

I'm trying to make a button that's an SKLabelNode. When it gets pressed, it's supposed to change scenes, but there is something wrong with the line where location is declared.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let location = touches.locationInNode(self)
let touchedNode = self.nodeAtPoint(location)
if touchedNode.name == "startGameButton" {
let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1.0)
let scene = GameScene(size: self.scene.size)
scene.scaleMode = SKSceneScaleMode.AspectFill
self.scene.view.presentScene(scene, transition: transition)
}
}
The error is here on the line.
let location = touches.locationInNode(self)
It reads
'Set< NSObject>' does not have a member named 'locationInNode'
I'm not sure how to fix it. I've looked at a lot of working button templates, but mine always has an error.
The problem is exactly what the error states - Set<NSObject> doesn't have a method named locationInNode. What you need to do is retrieve an object out of the Set; check it's a UITouch object; if it is, you can use it to get a touch location. Try:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let location = (touches.first as? UITouch)?.locationInNode(self) {
// ...
}
}
Or
if let touch = touches.first as? UITouch {
let location = touch.locationInNode(self)
// ...
}
To fix it, this is the default fix, it just enumerates through all the touches.
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
node = self.nodeAtPoint(location)
//do something
}