squarehippo10 squarehippo10 - 4 months ago 13
Swift Question

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

Answer

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.

Comments