swiftsprite-kittouchesbegan

SpriteKit tracking multiple touches


I have several buttons that move a main character. (Left, right, jump, etc.) But, when I touch more than one button at a time, the previous one is ended. My question is, how do I keep both touches alive for the duration of their touches? As an example, this would allow the character to move forward and jump at the same time. I have set multipleTouchEnabled to true. I've read that using a dictionary to track touches would help, but I can't seem to wrap my head around the implementation.

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        for touch in touches {
            let location = touch.locationInView(nil)

            if location.x < self.size.width / 2 && location.y > self.size.height / 2 {
                movingLeft = true
            }
            if location.x > self.size.width / 2 && location.y > self.size.height / 2 {
                movingRight = true
            }
            if location.x < self.size.width / 2 && location.y < self.size.height / 2 {
                jump()
            }
            if location.x > self.size.width / 2 && location.y < self.size.height / 2 {
                jump()
            }
        }
    }

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        movingLeft = false
        movingRight = false
    }

func jump() {
    mainCharacter.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
    mainCharacter.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 400))
}

Solution

  • The logic

    You should create a dictionary of active touches.

    private var activeTouches = [UITouch:String]()
    

    Every time a touch begins you save it into the dictionary and assign to it a label.

    activeTouches[touch] = "left"
    

    So when the touch does end you can search for it into your dictionary and find the related label. Now you know which button has been released by the user.

    let button = activeTouches[touch]
    if button == "left" { ... }
    

    And don't forget to remove it from the dictionary.

    activeTouches[touch] = nil
    

    The implementation

    class GameScene: SKScene {
    
        private var activeTouches = [UITouch:String]()
    
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            for touch in touches {
                let button = findButtonName(from:touch)
                activeTouches[touch] = button
                tapBegin(on: button)
            }
        }
    
        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
            for touch in touches {
                guard let button = activeTouches[touch] else { fatalError("Touch just ended but not found into activeTouches")}
                activeTouches[touch] = nil
                tapEnd(on: button)
            }
        }
    
        private func tapBegin(on button: String) {
            print("Begin press \(button)")
            // your custom logic goes here
        }
    
        private func tapEnd(on button:String) {
            print("End press \(button)")
            // your custom logic goes here
        }
    
    
        private func findButtonName(from touch: UITouch) -> String {
            // replace this with your custom logic to detect a button location
            let location = touch.locationInView(self.view)
            if location.x > self.view?.frame.midX {
                return "right"
            } else {
                return "left"
            }
        }
    }
    

    In the code above you should put your own code into

    1. tapBegin: this method receive the label of a button and start some action.

      E.g. start running.

    2. tapEnd: this method receive the label of a button and stop some action.

      E.g. stop running.

    3. findButtonName: this method receives a UITouch and returns the label of the button pressed by the user.

    Test

    I tested the previous code on my iPhone. I performed the following actions.

    1. started pressing the right of the screen
    2. started pressing the left of the screen
    3. removed finger from the right of the screen
    4. removed finger from the left of the screen

    As you can see in the following log the code is capable of recognizing different touches

    Begin press right
    Begin press left
    End press right
    End press left
    

    Conclusion

    I hope I made myself clear. Let me know if something is not.