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
}
}
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
}
}
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)
}
Related
Essentially, what I want is for when I touch a node, I want to be able to move it across the screen. The problem is that whenever I move my finger too fast, the node just stops following it.
The spriteNodes in particular that I'm trying to do this with have physics bodies and animating textures so I tried to do the same code with a completely plain spriteNode and I've encountered the same problem.
The code that I have here is pretty simple so I'm not sure if this is a problem with what I've written or if it's just a lag problem that I can't fix. It's also basically the same all throughout touchesBegan, touchesMoved and touchesEnded
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "activeRedBomb"{
node.position = pos
}
if node.name == "activeBlackBomb"{
node.position = pos
}
if node.name == "test"{
node.position.x = pos.x
node.position.y = pos.y
}
}
What's happening is that if you move your finger too fast, then at some point, the touch location will no longer be on the sprite, so you code to move the node won't fire.
What you need to do is set a flag in touchesBegan() to indicate that this sprite is touched, move the sprite to the location of the touch in touchesMoved() if the flag is set and then reset the flag in touchesEnded().
Here's roughly what you need to add for this:
import SpriteKit
class GameScene: SKScene {
var bombIsTouched = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if activeRedBomb.contains(touch.location(in: self)) {
bombIsTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
activeRedBomb.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
bombIsTouched = false
}
}
I would like to get the location of the touch in the UI, and use that location to determine what direction the sprite runs. For example, if the touch is on the left side of the screen, the user runs left, and if the touch is on the right side of the screen, the user runs right.
Try something like this
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let position = touch.locationInView(view)
print(position)
}
}
#KnightOfDragon's answer is the right one here.
You should use directly
func locationInNode(_ node: SKNode) -> CGPoint
More about Touch events in SpriteKit over here: documentation
As KnightOfDragon mentioned for SpriteKit the correct way would be
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self) // self is the current SKScene
let node = nodeAtPoint(location)
// To get the touched half of the screen I do this
if location.x < CGRectGetMidX(self.frame) {
// left half touched, do something
}
if location.x > CGRectGetMidX(self.frame) {
// right half touched, do something
}
}
}
I have this node I can rotate if my finger is on it. The problem Im having is that when Im rotating the node and my finger slides off the node and onto the view it stops rotating. How would I still be able to have that touch if my finger isn't on the node anymore?
For example I have a UISlider in my app and if I have my finger on my slider and I move down and then try to move the slider to the left and right it still works. How would I be able to do the same for my node where the touch event is still there? Let me know if you don`t get what I'm saying. Here is the code I'm using to rotate my node:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "circle"
//lets user rotate when there is one finger on node.
let dy = circle.position.y - location.y
let dx = circle.position.x - location.x
let angle2 = atan2(dy, dx)
circle.zRotation = angle2
}
You should store the last node in a instance var and then using that try to keep rotating even if it didnt touch it.
class MyClass {
var lastNodeSelected:SKNode?
override func touchesStart(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "circle"
lastNodeSelected = node
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if lastNodeSelected != nil {
let touchedNode = lastNodeSelected!
let dy = touchedNode.position.y - location.y
let dx = touchedNode.position.x - location.x
let angle2 = atan2(dy, dx)
touchedNode.zRotation = angle2
}
override func touchesEnd(touches: Set<UITouch>, withEvent event: UIEvent?) {
lastNodeSelected = nil
In the scene of a finger you can move the object. But if you just speed up the movement of the finger on the screen - the object remains in place. Is it possible to accelerate the speed of its movement? Duration is already set to 0
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
let node = self.nodeAtPoint(touchLocation)
if (node.name == "circle") {
let moveAction = SKAction.moveTo(touchLocation, duration: 0)
figureUser.runAction(moveAction)
}
}
Your problem is not the speed, your problem is you are moving so fast on the screen, that your touch is not recognizing a node underneath it anymore because the node is physically not underneath it. How you handle this problem is on the touch begin event, you check for a node, and assign this node to a variable. Then on the touch move event, update the new variable. Finally on touch end, clear the variable. Note, you would need to handle this code for things like multi touch
var movingNode : SKNode?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
let node = self.nodeAtPoint(touchLocation)
if (node.name == "circle") {
movingNode = node
let moveAction = SKAction.moveTo(touchLocation, duration: 0)
figureUser.runAction(moveAction)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
let moveAction = SKAction.moveTo(touchLocation, duration: 0)
//at this point I am lost, how does node even relate here,
//is figureUser suppose to be node?
figureUser.runAction(moveAction)
}
When I scroll through on this scene with my code, it is extremely glitchy looking. Everything kind of jumps around very quickly until you let go. Is there a better way of doing this?
var lastTouch: CGPoint!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch : UITouch = touches.first!
lastTouch = touch.locationInNode(self)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch : UITouch = touches.first!
let touchLocation : CGPoint = touch .locationInNode(self)
self.camera!.position = CGPointMake(self.camera!.position.x + (lastTouch!.x - touchLocation.x), self.camera!.position.y + (lastTouch!.y - touchLocation.y))
lastTouch = touchLocation;
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch : UITouch = touches.first!
let touchLocation : CGPoint = touch .locationInNode(self)
self.camera!.position = CGPointMake(self.camera!.position.x + (lastTouch!.x - touchLocation.x), self.camera!.position.y + (lastTouch!.y - touchLocation.y))
print(touches.count)
lastTouch = touchLocation;
}
I can upload a gif/video if really necessary. Is this just a bug with SKCameraNode as it is so new? If anyone knows what I am doing wrong I'd love to hear. Thanks!
EDIT: Click here to see a video of the issue
I have found the answer, as it was a very simple mistake. As the camera node moved, the location in which you are touching would also move. I created a node that moves with the camera and get the touch's location in that node.