Pedro Cabaço
Pedro Cabaço

Reputation: 123

Drag and drop SKNode without touching it

I have an SKNode on SpriteKit, which I basically want to be able to drag around the screen, but without having to touch it! Imagine I press the screen anywhere. I then want my SKNode to keep its distance to my finger, so that when I drag it I can see it.

I have this working but the object snaps to the touch.

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

        circle.position = location


    }
}


override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches{
        let location = touch.locationInNode(self)

        circle.position = location

    }
}

Upvotes: 1

Views: 84

Answers (2)

Fattie
Fattie

Reputation: 12592

It's a classic basic problem in game engineering.

There are two solutions...


First solution: when finger first goes down, make a note of the "grab" delta, being the delta from the position of the object, to, the finger.

When the finger moves each time to new position P, subtract the "grab" delta from P before setting the position of the object to P.

"It's that easy"


Second solution: each time the finger moves, don't bother at all with the position P of the finger.

Instead, calculate the delta the finger moved from the previous frame.

(So, simply store the previous position each time so you can calculate that - some systems give you the previous position as a property since it's so common, indeed some systems just give you the delta automatically as a property!)

Then just move the object by that delta.

"It's that easy"


Here is precisely the first solution, in iOS/SpriteKit

class FingerFollower: SKSpriteNode {
    
    var grab: CGVector = CGVector.zero
    // "grab" is the usual term for the delta from the object
    // to where the finger "grabbed" it...
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let t: UITouch = touches.first! as UITouch
        let l = t.location(in: parent!)
       
        grab = (l - position).vector
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let t: UITouch = touches.first! as UITouch
        let loc = t.location(in: parent!)
        
        position = loc - grab
    }
    
    // NOTE this code uses the absolutely obvious overrides for
    // subtraction etc between vectors, which you will need in 100%
    // of spritekit projects (Apple forgot about them)
}

Here is precisely the second solution, in iOS/SpriteKit

class FingerFollower: SKSpriteNode {
    
    func setup() {
        
        // NOTE, you MUST have a "setup" call in your sprite subclasses;
        // apple forgot to include a "didAppear" for SKNodes
        isUserInteractionEnabled = true
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        // note that Apple do in fact include the "previous location"
        // in sprite kit touches. (In systems where they don't do that,
        // you just make a note of it each time.)
        
        let prev = t.previousLocation(in: parent!)
        let quickDelta = loc - prev
        position = position + quickDelta
    }
}

Upvotes: 2

aignetti
aignetti

Reputation: 491

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?){

        let touch = touches.anyObject() as UITouch!
        let touchLocation = touch.locationInNode(self)
        let previousLocation = touch.previousLocationInNode(self)
        let distanceX = touchLocation.x - previousLocation.x
        let distanceY = touchLocation.y - previousLocation.y

        circle.position = CGPointMake(circle.position.x + distanceX, circle.position.y + distanceY)
}

Upvotes: 1

Related Questions