skip to Main Content

I need to make a sprite node to ignore touch, so that it fall through to other nodes behind it (lower z order).

In UIKit this can be done by overriding hitTest, but there doesn’t seem to be a way here in SpriteKit. Another approach in UIKit is to change the zPosition of the CALayer. This way we can setup the view such that its zOrder is at the top (so that it renders on top), but it’s touch behavior is still based on the relative order in superview’s subviews array. Basically, we can decouple the touch handling and rendering order. I dont think we can do that in SpriteKit either.

Note that setting isUserInteractionEnabled = false doesn’t work, since it still swallows the touch and prevent the nodes behind to receive touch event.

Anyone who used cocos2d before – this is basically the swallow touch flag.

My use case is that, I have a particle effect added to the screen during game play (it’s added to the top of screen). Currently the particle swallows touches on its region, and affect my game play. I want to just show the particle, but do not interfere with touches for other components on screen.

2

Answers


  1. override your touch functions at the SKScene level, then filter by SKNode class name. this will bypass any particles in front

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches {
            let location = t.location(in: self)
            let touchNodes = self.nodes(at: location) //all nodes at the point
                .compactMap{ $0 as? TouchMe } //filter by class. alternate: filter by sknode name 
            
            for node in touchNodes {
                node.touch() //pass the touch down 
            }
        }
    }
    

    and here is the corresponding SKNode

    class TouchMe: SKNode {
        func touch() {
            print("TouchMe.touch")
        }
    }
    
    Login or Signup to reply.
  2. I guess you are looking for this kind of behaviour.

    enter image description here

    Let’s see how to implement it!

    The isSwallowTouchEnabled

    Let’s add the property to SKNode.

    extension SKNode {
        var isSwallowTouchEnabled: Bool {
            get {
                userData?["isSwallowTouchEnabled"] as? Bool ?? false
            }
            set {
                if let userData {
                    userData["isSwallowTouchEnabled"] = newValue
                } else {
                    userData = ["isSwallowTouchEnabled": newValue]
                }
    
            }
        }
    }
    
    > As you can see the default value for this property is `false`.
    

    Enhancing SKScene

    We need to provide SKScene with a method capable of searching the SKNode affected by a touch and forward the touch event to it. During this search the method will skip the nodes where isSwallowTouchEnabled == true.

    extension SKScene {
        func handleTouchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            for touch in touches {
                let location = touch.location(in: self)
                nodes(at: location)
                    .filter { !$0.isSwallowTouchEnabled }
                    .max { $0.zPosition > $1.zPosition }?
                    .touchesBegan(touches, with: event)
            }
        }
    }
    

    The EmitterNodeWithSwallowTouch class

    Now we need to subclass SKEmitterNode in order be able to forward a touch event to the scene.

    class EmitterNodeWithSwallowTouch: SKEmitterNode {
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            guard !isSwallowTouchEnabled else {
                self.scene?.handleTouchesBegan(touches, with: event)
                return
            }
            print("touchesBegan on EmitterNodeWithSwallowTouch")
        }
        
    }
    

    The Square class

    Finally, I have created this class to test the functionality.

    We will put the Square node behind the particle effect in the next paragraph.

    class Square: SKShapeNode {
        init(fillColor: SKColor, edge: CGFloat) {
            super.init()
            self.path = .init(rect: .init(origin: .zero, size: .init(width: edge, height: edge)), transform: nil)
            self.fillColor = fillColor
            
        }
        
        @available(*, unavailable)
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            print("touchesBegan on Square")
        }
    }
    

    That’s it! Let’s test it

    class GameScene: SKScene {
        override func didMove(to view: SKView) {
            let rain = EmitterNodeWithSwallowTouch(fileNamed: "Rain.sks")! // This force unwrap is just for simplicity, never do it.
            rain.zPosition = 1
            rain.isUserInteractionEnabled = true
            rain.isSwallowTouchEnabled = true
            rain.position.y = frame.maxY
            addChild(rain)
            
            let blueSquare = Square(fillColor: .blue, edge: 300)
            blueSquare.isUserInteractionEnabled = true
            blueSquare.zPosition = 0
            blueSquare.position.x = -150
            blueSquare.position.y = -150
            addChild(blueSquare)
        }
    }
    

    Hope it helps.

    P.S. Cocos2D was a fun game engine.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search