'How to properly encapsulate touch event handling in a SKSpriteNode and pass data back to a SKScene

I'm writing a game in SpriteKit that has a joystick to control the player. Previously, I had kept most of the joystick logic in the main GameScene's touchesBegan, touchesMoved, and touchesEnded methods.

Since I have multiple SKScenes, I wanted to abstract this logic out into a SKSpriteNode subclass JoyStick that contains its own touches event handlers. This seems like a good solution as it automatically handles whether or not the touch is "in bounds" of the joystick and allows me to remove logic from the scene itself.

However, I can't find any good resources outlining how to properly pass information back and forth between a SKScene and a SKSpriteNode instance that has implemented touch event handlers. I could pass the JoyStick an instance of the object that I want to modify (like the player sprite) but I'm curious if there is a proper way to pass data back and forth without coupling the joystick to a specific "instance-to-modify".

Additionally, are there any other downsides to implementing touch event handlers in my SKSpriteNode instances, rather than handling everything in the scene's handlers?



Solution 1:[1]

I like to handle as much of the code in the object class as possible. So I would handle any of the object touch code inside of it's class file and send the touch back to the scene to be handle separately by the scene if needed, and use delegation to send the information regarding the touch to the scene.

There are no downsides to removing this code, and several upsides.

cleaner code faster loading time in xcode if the scenes have less line (my own findings) you don't have to pinpoint the node that the touch is landing on because it is encapsulated in a sublass

In subclass (cat)

protocol CatDelegate: class{
    func catTouched(cat: Cat)
}

class Cat: SKSpriteNode {

    weak var delegate: CatDelegate!

    var isEnabled = true
    var sendTouchesToScene = true
    var color: SKColor = .white

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

         guard isEnabled else { return }

         //send touch to scene if you need to handle further touches by the scene
         if sendTouchesToScene {
             super.touchesBegan(touches, with event)
         }

         //handle touches for cat
         delegate?.catTouched(cat: self)
    }
}

meanwhile in Game Scene...

class GameScene: SKScene {

    private var cat1: Cat!
    private var cat2: Cat!

    …

    func createCats() {
   
     cat1 = Cat()
        cat1.color = .magenta
        cat1.delegate = self
        addChild(cat1)
    
        cat2 = Cat()
        cat2.color = .green
        cat2.delegate = self
        addChild(cat2)
    }
}

extension GameScene: CatDelegate {

    func catTouched(cat: Cat) {
        print("cat of color \(cat.color)")
    }
}

Solution 2:[2]

I am sorry but previous answer is simply incomplete. Here is the same, with all the code needed to make it work.

import SwiftUI
import SpriteKit

class GameScene: SKScene {

private var cat1: Cat!
private var cat2: Cat!

func createCats() {
    cat1 = Cat(imageNamed: "img1")
    cat1.position = CGPoint(x: 64, y: 128)
    cat1.color = .magenta
    cat1.isUserInteractionEnabled = true
    cat1.delegate = self
    addChild(cat1)
    
    cat2 = Cat(imageNamed: "img2")
    cat2.position = CGPoint(x: 128, y: 128)
    cat2.color = .green
    cat2.isUserInteractionEnabled = true
    cat2.delegate = self
    addChild(cat2)
}

override func didMove(to view: SKView) {
    createCats()
}

}

extension GameScene: CatDelegate {

func catTouched(cat: Cat) {
    print("cat of color \(cat.color)")
}
}


protocol CatDelegate {
  func catTouched(cat: Cat)
}

class Cat: SKSpriteNode {
 var delegate: CatDelegate!

 var isEnabled = true
 var sendTouchesToScene = true
 var colour: SKColor = .blue

 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent!) {
    
    guard isEnabled else { return }
    
    //send touch to scene if you need to handle further touches by the scene
    if sendTouchesToScene {
        super.touchesBegan(touches, with: event)
    }
    
    //handle touches for cat
    delegate?.catTouched(cat: self)
}
}



struct ContentView: View {

var scene: SKScene {
    let scene = GameScene()
    scene.size = CGSize(width: 256, height: 256)
    scene.scaleMode = .fill
    scene.backgroundColor = .white
    return scene
}

var body: some View {
    SpriteView(scene: scene)
        .frame(width: 256, height: 256)
        .border(Color.red)
}
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 user3069232
Solution 2 user3069232