I'm trying to detect whether the user is touching either the left or right hand side of the screen in an SKScene.
I've put the following code together however it is only outputting "Left" regardless of where is touched.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if(location.x < self.frame.size.width/2){
print("Left")
}
else if(location.x > self.frame.size.width/2){
print("Right")
}
}
}
If you are using the default scene for a SpriteKit project, the anchor point is (0.5, 0.5) which places the origin of the scene to the center. If you want to test the touch position is left or right, you should change the condition checking to (this will work regardless of anchor point):
for touch in touches {
let location = touch.location(in: self)
if(location.x < frame.midX){
print("Left")
}
else if(location.x > frame.midX){
print("Right")
}
}
More details at: SKScene
Related
I'm writing a Pong-like game using SpriteKit and Swift 5 for learning purpose. And I'm trying to made the player's paddle (an SKNode rectangle) move across the screen in the x coordinate but couldn't.
I've tried this simple solution:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
playerPaddle.position.x = location.x
}
It works but seems like the paddle only moves on clicks (touches), but not drags.
Also I've tried using the UIPanGestureRecognizer, but I don't really understand how to call it, here is the code which I copied from the official Apple Developer's doc and changed a little to match my situation:
#IBAction func panPiece(_ gestureRecognizer : UIPanGestureRecognizer) {
guard gestureRecognizer.view != nil else {return}
let piece = gestureRecognizer.view!
let translation = gestureRecognizer.translation(in: piece.superview)
if gestureRecognizer.state == .began {
self.initialPos = piece.center
}
else if gestureRecognizer.state != .changed {
let newPos = CGPoint(x: initialPos.x + translation.x, y: initialPos.y)
piece.center = newPos
}
}
Your touchesMoved solution works for me, so I'd assume that something else is amiss. Regardless, I can offer an alternative:
I also built Pong to learn Swift and SpriteKit, and I implemented the paddle movement using SKActions. Any time a touch begins, cancel the paddle's current actions and begin moving to the new x location.
If you want to keep it simple to start, then you can have a constant move time, like 0.5 seconds. Otherwise, you can calculate the time to move by getting the absolute distance between the paddle and touch location and dividing by some constant. Changing the constant will change the speed at which the paddle moves.
Here's an example with the constant move time:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if playerPaddle.hasActions() { playerPaddle.removeAllActions() }
for touch in touches {
let newX = touch.location(in: self).x
let oldX = playerPaddle.position.x
playerPaddle.run(.moveBy(x: newX - oldX, y: 0, duration: 0.5))
}
}
I have a sprite node on the screen, that the player is able to move left and right. I know how to make it move left and right using touchesMoved, however, if the player touches a location the node snaps to that location.
For example, if the node was on the left side of the screen, and the player touched the right side of the screen, the node would immediately move to the touch location. I don't want this behavior. I only want to the node to move when the player is moving their finger left or right, not when they touch a location. Below is the code I currently use. There isn't any code that would affect the playerNode x position in the touchesBegan or touchesEnded function.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches
{
if game.state == .game
{
let position = touch.location(in: playableArea)
game.playerNode.position.x = position.x
}
}
}
How can I stop this behavior from happening, and only have the node move when the players finger is moving left or right?
I ended up resolving my issue. I knew that I had to essentially move the node relative to the touch location, but I wasn't sure how to do that. This is how I got it working.
First I created a variable that would be equal to the players touch location. This variable gets set in touchesBegan.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if game.state == .game
{
let position = touch.location(in: playableArea)
playerTouchPosition = position
}
}
I use this variable as the starting point of the touch. When the player moves their finger, it calculates how much the player moved their finger relative to the starting point. For example, if the player touched at the X value of 100, and moved their finger to the X value of 120, it would calculate that the player moved their finger by 20. After finding that value, I set the playerTouchPosition to the position of touchesMoved. This is to make the next move be relative to the previous touch. Then I move the players node by the amount the player moved their finger.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches
{
if game.state == .game
{
let position = touch.location(in: playableArea)
let newX : CGFloat!
if position.x > playerTouchPosition.x
{
newX = position.x - playerTouchPosition.x
}else
{
newX = -(playerTouchPosition.x - position.x)
}
playerTouchPosition = position
game.playerNode.position.x = game.playerNode.position.x + newX
}
}
}
This stopped the players node from immediately moving to where ever the touch locations X value was.
I've been trying to figure out this bug in my code that whenever my player node is to the most left/right side of my screen it tends to behave weirdly i.e. it'd go off to the right/left even though when i've touched left/right
Here's my code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let action = SKAction.moveTo(x:location.x, duration: 0.45)
if location.x > (player?.position.x)! {
player?.run(action)
moveLeft = false
} else {
player?.run(action)
moveLeft = true
}
}
canMove = true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
canMove = false
}
Here's the moveleft in Player.swift
// Move function
func move(left: Bool) {
if left {
position.x -= 10
// Set boundary to minX so it wont go below
if position.x < minX {
position.x = minX
}
} else {
position.x += 10
// Set boundary to maxX so player cannot move over the right boundary
if position.x > maxX {
position.x = maxX
}
}
}
1) Your actions are stacking on top of each other, so every time you touch, you add a new action. If you touched left, right, left your character would act erratic. To combat this, assign a key player?.run(action, withKey:"moving") (Do not assign different keys for left and right, use only 1 key for moving in any direction)
2)You are touching based on left or right of the player, when the player is all the way to the left, even touching the left side of the screen will move him right. You may want to switch this to simply if x is negative go left, if x is positive go right (Assuming the center of your scene is (0,0)
3)Not sure when move(left:Bool) is getting called, I am not seeing it in the code.
This is not even needed, I would avoid having this bool check all together.
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))
}
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
tapBegin: this method receive the label of a button and start some action.
E.g. start running.
tapEnd: this method receive the label of a button and stop some action.
E.g. stop running.
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.
started pressing the right of the screen
started pressing the left of the screen
removed finger from the right of the screen
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.
Issue
For me it was a bit foolish but there's an option that I miss to enable in my storyboard in order to handle multiple touch in my GameScene of type SKScene.
When one of the overwritten functions is being called, you should have more than 1 touch contained in touches.
Those functions are indeed:
touchesBegan
touchesMoved
touchesEnded
touchesCancelled
... and the way to retrieve the touches is indeed to browse through them. However, if you are only able to retrieve one touch only, follow the steps bellow.
Debugging
Here's what you can do to see if you have multiple touches in your set of touches:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for (i, t) in touches.enumerated() {
print(i, t.location(in: self))
self.touchMoved(toPoint: t.location(in: self))
}
}
Output:
There you should find a 0 for the first touch and a 1 when you touch with 2 fingers... and so on.
Solution
I case you encounter the same problem than me and have only the touch t at index 0 but no other touch, you should see upper in the hierarchy of views if all of them are enabled for multi-touch. In my case it was the view containing the GameScene of type SKScene that did not have the option Multiple Touch enabled. See below:
Currently I'm making a game with swift spritekit and want the character look left when the finger moves the character to the left in touchesMoved. Since, I started development with swift and spritekit just a few days ago I find it hard to implement this action. How can I detect left or right in the code below ?
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
playerSprite.position.x = touch.locationInNode(self).x
}
You can check if the current x-position of your touch is bigger or smaller than the position before.
To achieve that, you should create a variable to store the location of your last touch. For example:
var lastXTouch:CGFloat = -1
Then in the touchesMoved-method you check the location and check if the previous location was more on the left or more on the right side:
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if lastXTouch > location.x{
//Finger was moved to the left. Turn sprite to the left.
}else{
//Finger was moved to the right. Turn sprite to the right.
}
lastXTouch = location.x
playerSprite.position.x = touch.locationInNode(self).x
}
Put in a gesture recognizer when you want to be able to detect the swipe:
var leftSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipe:"))
leftSwipe.direction = .Left
var rightSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipe:"))
rightSwipe.direction = .Right
self.view.addGestureRecognizer(leftSwipe)
self.view.addGestureRecognizer(rightSwipe)
Then, you need to implement the handler method that is called - handleSwipe:
func handleSwipe(sender:UISwipeGestureRecognizer){
if (sender.direction == .Left){
//swiped left
//change your texture here on the sprite node to make it look left
}
if (sender.direction == .Right){
//swipe right
//change texture here on sprite to make it look right
}
}