Detecting touching a SKSpriteNode within a touchesBegan event?

Detecting touching a SKSpriteNode within a touchesBegan event?

My experience to date has focused on using GamepadControllers with Apps, not a touch-activated iOS App.

Here are some short code snippets:

Note: the error I am trying to correct is noted in the very first snippet = touchesBegan within the comment <== shows "horse"

Yes, there is a "horse", but it is no where near the "creditsInfo" SKSpriteNode within my .sksfile.

Please note that this "creditsInfo" SKSpriteNode is programmatically generated by my addCreditsButton(..) and will be placed very near the top-left of my GameScene.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    if let ourScene = GameScene(fileNamed: "GameScene") {
        
        if let touch:UITouch = touches.first {
            let location = touch.location(in: view)
            let node:SKNode = ourScene.atPoint(location)
            
            print("node.name = \(node.name!)")   // <== shows "horse"
            if (node.name == "creditsInfo") {
                showCredits()
            }
        }
        
    }   // if let ourScene
    
}   // touchesBegan

The above touchesBegan function is an extension GameViewController which according to the docs is okay, namely, touchesBegan is a UIView method besides being a UIViewController method.

Within my primary showScene() function, I have:

    if let ourScene = GameScene(fileNamed: "GameScene") {

#if os(iOS)
    addCreditsButton(toScene: ourScene)
#endif

     }

with:

func addCreditsButton(toScene: SKScene) {
            
    if thisSceneName == "GameScene" {
                    
        itsCreditsNode.name = "creditsInfo"

        itsCreditsNode.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        
        itsCreditsNode.size = CGSize(width: 2*creditsCircleRadius,
                                     height: 2*creditsCircleRadius)
        itsCreditsNode.zPosition = 3
        creditsCirclePosY = roomHeight/2 - creditsCircleRadius - creditsCircleOffsetY
        creditsCirclePosX = -roomWidth/2 + creditsCircleRadius + creditsCircleOffsetX
        itsCreditsNode.position = CGPoint(x: creditsCirclePosX,
                                          y: creditsCirclePosY)
        
        toScene.addChild(itsCreditsNode)
                    
    }   // if thisSceneName

}   // addCreditsButton

To finish, I repeat what I stated at the very top:

The error I am trying to correct is noted in the very first snippet = touchesBegan within the comment <== shows "horse"

Accepted Reply

HERE is the THE answer ...

Make touchesBegan an extension of GameScene, not GameViewController with:

extension GameScene {
        
    // "if we set isUserInteractionEnabled = false, the Scene will receive touch events"
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        //  not needed because addCreditsButton() isn't called
        //  unless we're staring at "GameScene"
    //  if let ourScene = GameScene(fileNamed: "GameScene") {
            
            if let touch:UITouch = touches.first {
                let location = touch.location(in: self)
                let ourNode:SKNode = atPoint(location)   // not ourScene.atPoint(location)
                
            //  print("location = \(location)")
                print("ourNode.name = \(ourNode.name!)")
                if (ourNode.name == "creditsInfo") {
                    showCredits()
                }
            }   // if let touch:UITouch
            
    //  }   // if let ourScene
        
    }   // touchesBegan

Replies

I don't see anything obviously wrong in your code. The only possible problem I see is a coordinate system problem where the values of the touch location may not be what you expect because the view and the scene have different coordinates.

Set a breakpoint at the following line of code:

let node:SKNode = ourScene.atPoint(location)

Check if location is where you expect it to be.

  • An hour ago I just inserted a print() statement. When I clicked in the Simulator the SKSpriteNode I got a position near the top-left corner of the UIDevice. I re-clicked everywhere and the position changed as it should have.

  • I think my problem comes from my .sks file which has many figures, including the horse. Within the .sks file I set the GameScene’s size to something very large. Within my swift code I set the GameViewController’s .scaleMode = .resizeFill so the .sks file fills the UIDevice screen.

    SO, somehow my touchesBegan code is singling out the .sks figures.

  • Let me check my .zPosition numbers and I will provide feedback

Add a Comment

I did check my .zPosition numbers and they were okay. I even set its .zPosition to a crazy 20 - no change. I also checked that node.isHidden = false and it is. So, it appears your statement that location is the culprit. As a matter of interest, physically this "info" SKSpriteNode is physically very close to the top-left corner .. so I clicked very close to this location and got, for example (1.0, 30.0). No matter how precise I tried to get (0.0, 0.0) I could never get that close vertically. So, that truly mystifies me. One last thing: .anchorPoint = (0.5, 0.5) AHAH = it's a circle with radius = 40.0, a left-edge offset from left scene border = 50.0 and a top-edge offset from top scene border = 15.0 That number = 30.0 mentioned above still bugs me greatly.

ALMOST THERE (1) change "let" to "var" + (2) add location.x = -roomWidth/2 + location.x & location.y = roomHeight/2 - location.y which together changes the view coordinates of the original location to match the node.location. I know I am on the right track because besides the "horse", I have several buildings and 2 people added directly to the .sks file. An appropriate print("\(node.name)") prints the names of the buildings + people. BUT, this does not work for my itsCreditsNode since this node is programmatically added, versus added to the .sks file. ??

Strictly speaking this is not the answer, but I present here what the last comment above talks about - but in a much more readable format. Hopefully this will generate some much needed help:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    if let ourScene = GameScene(fileNamed: "GameScene") {
        
        if let touch:UITouch = touches.first {
            var position = CGPoint()
            let location = touch.location(in: ourScene)
            position.x = -roomWidth/2 + location.x
            position.y = roomHeight/2 - location.y
            let node:SKNode = ourScene.atPoint(position)              
           
            if (node.name == "creditsInfo") {
                showCredits()
            }
        }            

    }   // if let ourScene
    
}   // touchesBegan

Please note that with the above touchesBegan, the node.name is not "creditsInfo", but "room" which is the SKSpriteNode immediately below my itsCreditsNode. "room" has zPosition = 0 versus 5 for itsCreditsNode. So, my itsCreditsNode is not seen.

Finally, note that my addCrreditsButton(...) presented at the top of this Post will not change

  • Someone else provided this very illuminating comment which you may also find very insightful: "If we set isUserInteractionEnabled = true, the Node, not the Scene, will receive touch events. Therefore, the Scene's touchesBegan method is not called. Instead, the Node's touchesBegan is called. If we set isUserInteractionEnabled = false (default case), the Node will not receive any touch events. So where do the touch events go? = To the parent of the Node = the Scene!

Add a Comment

HERE is the THE answer ...

Make touchesBegan an extension of GameScene, not GameViewController with:

extension GameScene {
        
    // "if we set isUserInteractionEnabled = false, the Scene will receive touch events"
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        //  not needed because addCreditsButton() isn't called
        //  unless we're staring at "GameScene"
    //  if let ourScene = GameScene(fileNamed: "GameScene") {
            
            if let touch:UITouch = touches.first {
                let location = touch.location(in: self)
                let ourNode:SKNode = atPoint(location)   // not ourScene.atPoint(location)
                
            //  print("location = \(location)")
                print("ourNode.name = \(ourNode.name!)")
                if (ourNode.name == "creditsInfo") {
                    showCredits()
                }
            }   // if let touch:UITouch
            
    //  }   // if let ourScene
        
    }   // touchesBegan