im trying to create a "button touch" effect for one of my sprites, it works well but then I tap with 2 or more fingers at the same time, i get really weird results, here is my code:
let buttonPressAction = SKAction.scaleBy(0.8, duration: 0)
var button = SKNode()
override func didMoveToView(view: SKView) {
//assign sprite to node
button = self.childNodeWithName("button") as! SKSpriteNode!
}
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if button.containsPoint(location) {
button.runAction(buttonPressAction)
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
button.runAction(buttonPressAction.reversedAction())
}
Try changing the SK scale action from
...scaleBy
to
...scaleTo
to ensure it will always scale to the same size. With scaleBy it will scale it by 0.8, not to 0.8. That most likely causes the weird results on multiple touches because you are scaling by 0.8 for each finger/tap.
I never used reverseAction before so I am not sure if that might cause issues. If it does just reset the button by scaling it back to 1
...scaleTo(1, duration: 0)
As as side note you can just say
for touch in touches
instead of
for touch: AnyObject in touches
Related
At the moment I have a game in which the sprite can be controlled and moved along the x axis (with a fixed y position) using the accelerometer of the device. I wish to change this so the user can control the sprite by dragging on the screen like in popular games such as snake vs.block.
I've already tried using the touches moved method which gives the correct effect, although the sprite first moves to the location of the users touch which I don't want.
Below is the environment I've been using to experiment, the touches moved gives the correct type of control I want although I can't seem to figure out how to stop the sprite first moving to the location of the touch before the swipe/drag
import SpriteKit
import GameplayKit
var player = SKSpriteNode()
var playerColor = UIColor.orange
var background = UIColor.black
var playerSize = CGSize(width: 50, height: 50)
var touchLocation = CGPoint()
var shape = CGPoint()
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.backgroundColor = background
spawnPlayer()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
player.position.x = touchLocation.x
}
}
func spawnPlayer(){
player = SKSpriteNode(color: playerColor, size: playerSize)
player.position = CGPoint(x:50, y: 500)
self.addChild(player)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
In summary, I'm looking for the same method of controlling a sprite as in snakes vs blocks
Hey so you have the right idea, you need to store a previous touchesMovedPoint. Then use it to determine how far your finger has moved each time touchesMoved has been called. Then add to the player's current position by that quantity.
var lastTouchLoc:CGPoint = CGPoint()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
let dx = touchLocation.x - lastTouchLoc.x
player.position.x += dx // Change the players current position by the distance between the previous touchesMovedPoint and the current one.
lastTouchLoc.x = touchLocation.x // Update last touch location
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
// When releasing the screen from point "E" we don't want dx being calculated from point "E".
// We want dx to calculate from the most recent touchDown point
let initialTouchPoint = touch.location(in: self)
lastTouchLoc.x = initialTouchPoint.x
}
}
You want to use SKAction.move to perform what you want.
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return hypot(lhs.x.distance(to: rhs.x), lhs.y.distance(to: rhs.y))
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
let speed = 100/1 //This means 100 points per second
let duration = distance(player.position,touchLocation) / speed //Currently travels 100 points per second, adjust if needed
player.run(SKAction.move(to:touchLocation, duration:duration),forKey:"move")
}
}
What this does is it will move your "snake" to the new location at a certain duration. To determine duration, you need to figure out how fast you want your "snake" to travel. I currently have it set up to where it should take 1 second to move 100 points, but you may want to adjust that as needed.
I'm trying to implement swipe gestures left and right into a game in SpriteKit where the player collects falling objects spawning from the top of the screen. The problem I'm facing is trying to keep a continuous movement of the player while the finger is on the screen until the touch is ended and the player stays where the last touch ended. There might be a better way to implement this other than swipe gestures hence why I'm asking you guys! Any help would be great, thank you all!
This is not something you want to use a swipe (I think you are using pan) gesture for. What you want to do is override the touchesBegan, touchesMoved, and touchesEnded calls on the scene, and plan your movement according to these 3 methods.
You are probably going to want to use SKAction.move(to:duration:) with these methods, and figure out the math to keep a constant speed.
E.G.
func movePlayer(to position:CGPoint)
{
let distance = player.position.distance(to:position)
let move = SKAction.move(to:position, duration: distance / 100) // I want my player to move 100 points per second
//using a key will cancel the last move action
player.runAction(move,withKey:"playerMoving")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first
let position = touch.location(in node:self)
movePlayer(to:position)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first
let position = touch.location(in node:self)
movePlayer(to:position)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let position = touch.location(in node:self)
movePlayer(to:position)
}
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
}
}
How can I recognize continuous user touch in Swift code? By continuous I mean that the user has her finger on the screen. I would like to move a sprite kit node to the direction of user's touch for as long as the user is touching screen.
The basic steps
Store the location of the touch events (touchesBegan/touchesMoved)
Move sprite node toward that location (update)
Stop moving the node when touch is no longer detected (touchesEnded)
Here's an example of how to do that
Xcode 8
let sprite = SKSpriteNode(color: SKColor.white, size: CGSize(width:32, height:32))
var touched:Bool = false
var location = CGPoint.zero
override func didMove(to view: SKView) {
/* Add a sprite to the scene */
sprite.position = CGPoint(x:0, y:0)
self.addChild(sprite)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touched = true
for touch in touches {
location = touch.location(in:self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
location = touch.location(in: self)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Stop node from moving to touch
touched = false
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (touched) {
moveNodeToLocation()
}
}
// Move the node to the location of the touch
func moveNodeToLocation() {
// Compute vector components in direction of the touch
var dx = location.x - sprite.position.x
var dy = location.y - sprite.position.y
// How fast to move the node. Adjust this as needed
let speed:CGFloat = 0.25
// Scale vector
dx = dx * speed
dy = dy * speed
sprite.position = CGPoint(x:sprite.position.x+dx, y:sprite.position.y+dy)
}
Xcode 7
let sprite = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(32, 32))
var touched:Bool = false
var location = CGPointMake(0, 0)
override func didMoveToView(view: SKView) {
self.scaleMode = .ResizeFill
/* Add a sprite to the scene */
sprite.position = CGPointMake(100, 100)
self.addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Start moving node to touch location */
touched = true
for touch in touches {
location = touch.locationInNode(self)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Update to new touch location */
for touch in touches {
location = touch.locationInNode(self)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Stop node from moving to touch
touched = false
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (touched) {
moveNodeToLocation()
}
}
// Move the node to the location of the touch
func moveNodeToLocation() {
// How fast to move the node
let speed:CGFloat = 0.25
// Compute vector components in direction of the touch
var dx = location.x - sprite.position.x
var dy = location.y - sprite.position.y
// Scale vector
dx = dx * speed
dy = dy * speed
sprite.position = CGPointMake(sprite.position.x+dx, sprite.position.y+dy)
}
The most difficult thing about this process is tracking single touches within a multitouch environment. The issue with the "simple" solution to this (i.e., turn "istouched" on in touchesBegan and turn it off in touchesEnded) is that if the user touches another finger on the screen and then lifts it, it will cancel the first touch's actions.
To make this bulletproof, you need to track individual touches over their lifetime. When the first touch occurs, you save the location of that touch and move the object towards that location. Any further touches should be compared to the first touch, and should be ignored if they aren't the first touch. This approach also allows you to handle multitouch, where the object could be made to move towards any finger currently on the screen, and then move to the next finger if the first one is lifted, and so on.
It's important to note that UITouch objects are constant across touchesBegan, touchesMoved, and touchesEnded. You can think of a UITouch object as being created in touchesBegan, altered in touchesMoved, and destroyed in touchesEnded. You can track the phase of a touch over the course of its life by saving a reference to the touch object to a dictionary or an array as it is created in touchesBegan, then in touchesMoved you can check the new location of any existing touches and alter the object's course if the user moves their finger (you can apply tolerances to prevent jitter, e.g., if the x/y distance is less than some tolerance, don't alter the course). In touchesEnded you can check if the touch in focus is the one that ended, and cancel the object's movement, or set it to move towards any other touch that is still occurring. This is important, as if you just check for any old touch object ending, this will cancel other touches as well, which can produce unexpected results.
This article is in Obj-C, but the code is easily ported to Swift and shows you what you need to do, just check out the stuff under "Handling a Complex Multitouch Sequence": https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/multitouch_background/multitouch_background.html
Below is the code to drag the node around on X position (left and right), it is very easy to add Y position and do the same thing.
let item = SKSpriteNode(imageNamed: "xx")
var itemXposition = 50
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// updates itemXposition variable on every touch
for touch in touches {
let location = touch.location(in: self)
itemXposition = Int(location.x)
}
}
// this function is called for each frame render, updates the position on view
override func update(_ currentTime: TimeInterval) {
spaceShip.position = CGPoint(x: self.itemXposition , y: 50 )
}