In my game I have a node on the bottom of the screen that id like to move along the x axis using touch. I'd like for my node to move left or right depending on the direction of the drag, and to also move the same distance as the drag. So if the user drags from left to right (CGPoint(x: 200, y: 500)toCGPoint(x:300, y: 500)) the node would move 100 to the right. This is what I've tried to do, but it didn't work. If anyone has a way to fix this I'd really appreciate it
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
firstTouch = touchLocation
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
secondTouch = touchLocation
if gameStarted {
let change = secondTouch.x - firstTouch.x
let move = SKAction.moveToX(greenGuy.position.x + change, duration: 0.1)
greenGuy.runAction(move)
}
}
Update your touchesMoved with the following code:
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
secondTouch = touchLocation
if gameStarted {
let change = secondTouch.x - firstTouch.x
//Update greenGuys position
greenGuy.position = CGPoint(x: greenGuy.position.x + change, y:greenGuy.position.y)
//Update the firstTouch
firstTouch = secondTouch
}
The reason not using an SKAction as I commented before is because we don't know how much time will pass between two calls of touchesMoved method, so we don't know exactly what time to input in SKAction duration.
You have a pretty good start. First, change:
let move = SKAction.moveToX(greenGuy.position.x + change, duration: 0.1)
to:
let move = SKAction.moveByX(greenGuy.position.x + changeInX, duration: moveDuration)
If you want 2-dimensional movement, use the SKAction moveByX:ChangeInX y:ChangeInY duration:moveDuration instead. Now, you have a few more variables that are based on the duration/distance of the swipe. The duration that you will choose for moveDuration will be up to you, and it will be the product of some coefficient and the swipe distance.
To get the swipe distance:
Id advise you to ditch the touches method and use a UIGestureRecognizer. The one you need is UIPanGestureRecognizer.
Here's a useful link detailing its use: UISwipeGestureRecognizer Swipe length .
Essentially it has different states that get set when the user starts or ends a swipe/drag motion. Then, you could take the locationInView at those moments and calculate the distance between them :D
Hope I helped. I know touchesMoved is also a way to do it, but Ive had problems with it in the past (unnecessary lagging and uncertainty) and gesture recognizers are much more intuitive and easier to use.
Related
I have tried too many equations to get the right answer, maybe something wrong with the code or my understanding of the idea. However, here is my code:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let coord = touch.location(in:view)
self.linegu.transform = CGAffineTransform.init(rotationAngle: atan((coord.y - (self.view.center.y + (self.view.center.y)/2.5)) / (coord.x - self.view.center.x)))
}
}
linegu is the UIImageView I'm using and I position it in the middle of the view by using this code:
let Ypos = (self.view.center.y/2.5);
self.linegu.center = self.view.center
self.linegu.center.y = (self.view.center.y + Ypos)
It doesn't matter where the image view is located. And you should throw away your "magic number" 2.5. You are way overthinking this.
Assume arrow is the image view, and that it starts out life horizontal with the arrow pointing to the right. And assume that the background view has a tap gesture recognizer on it. (We use a tap gesture recognizer for simplicity; it looks like you might eventually want to use a pan gesture recognizer. But for now, let's just get this thing working.)
Then here is how the tap gesture's action handler would look:
let c = self.arrow.center
let p = t.location(in: self.arrow.superview)
let angle = atan2(p.y - c.y, p.x - c.x)
self.arrow.transform = CGAffineTransform(rotationAngle:angle)
It's as simple as that.
Why do you use this weird formula?
CGAffineTransform.init(rotationAngle: atan((coord.y - (self.view.center.y + (self.view.center.y)/2.5)) / (coord.x - self.view.center.x)))
And where does this 2.5 divider come from?
What about just using .center or .frame to position the UIImageView?
Like:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: view)
imageView.center = location // may need to apply some offset here, depending on where exactly the arrow points in the arrow image
}
}
I am working on an iOS app and I want to have the effect of an action while the user's finger slides into a particular area. I first thought UIButton with touch dragged in will work, but it still requires a touch before the "drag". So is there a way to do it? Thanks so much!
You could use UITouch with touch began. Have your particular area view as a UIView IBOutlet. Lets call it particularAreaView
Maybe something like this:
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.allObjects[0] as UITouch //get the latest touch
let touchLocation = touch.locationInView(self.view)
let particularAreaViewFrame = self.view.convertRect(particularAreaView.frame, fromView: self.view)
//Check if touch began is in your particularAreaViewFrame
if CGRectContainsPoint(particularAreaViewFrame, touchLocation) {
tiggerYourActionHere()
}
}
If you need it to consider touches that is moving into the area, you will have to override touchesMoved: withEvent: as well. Then maybe refactor the above into a function that use in both places.
i build a wall and the bricks are not moving but i need to have one of them calling some info. this code below is inside a function may be should have this brick global
let brk = SKSpriteNode(imageNamed: imgName)
if (imgName == "brickinfo"){
brk.name = "brickinfo"
}
brk.position = CGPoint(x:CGRectGetMinX(self.frame)+brkx, y: CGRectGetMinY(self.frame)+brky)
if (imgName == "brickinfo"){
let singleTap = UITapGestureRecognizer(target: self, action: Selector("ShowInfo"))
singleTap.numberOfTapsRequired = 1
brk.userInteractionEnabled = true
//brk.addGestureRecognizer(ShowInfo) //do not exist
}
brk.xScale = 0.2
brk.yScale = 0.2
addChild(brk)
i am using the same code for other moving images which mean they have SKAction so when i click on them, touches began is fired. they both using the same code, add a name, addchild().
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
//675
//30
print(self.nodeAtPoint(location).name)
if let theName = self.nodeAtPoint(location).name {
but the name of "brickinfo" is always coming back nil.
Can this brick image fired touchesbegan?
Thank you
ok i was not looking for gesture, i thought i needed to add gesture to trigger the image, but i only needed to have this image responding with its name. After 3-4 days looking at many different ways to do it, i found the solution and it is pretty easy, the zposition was not set up for this specific brick. Now of course this bring its own problem. Sorry for the title of the question. Does xcode comes with a zposition layer view in 3D?
Thank you
I'm trying to implement 2 distinct touch based behaviours. The first behaviour's logic is like this:
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
moveViewTo(touchLocation) //moves my view to touchLocation
}
Now, for my second behaviour, I want to rotate the view based on where I have my finger on the screen. For this, I tried this:
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
rotateViewTo(touchLocation) //rotates my view to face touchLocation
}
Now, I want these two distinct behaviours to work concurrently. In particular, I want the first touch to change the position of the view, and the second touch to rotate the view.
Rotating is possible only if there are two touches in the view, and changes direction based on the second touch.
Is there any way to differentiate which of the touches are first and which are second? I can't find a way to differentiate from the touches: Set<NSObject> because by nature Set is an unordered collection.
I believe your only option is to store your first touch as a var. My swift isn't strong enough to write out the code for you but here are the steps.
Loop through your touches instead of just grabbing the first one.
Check if there is a firstTouch (your var for tracking first touch)
If there isn't a firstTouch assign the first touch and do your position logic
If there is a first touch check to see if each touch is the firstTouch. If it isn't then do your rotation logic.
On touches ended and canceled loop through each touch. If it is the firstTouch set your var to nil.
Sorry I can't write all the code out for you, but hopefully that gets you going in the right direction.
UPDATE: I have solved the problem, and figured out a more simplified way to do this then the answer provided. My solution was to make the velocity of the SPACESHIP equal the distance it was from my finger touch. For faster movement, you can multiply this velocity by a constant. In this case, I used 16. I also got rid of setting lastTouch to nil in the touchesEnd event. That way, the ship will still stop even when I release my finger.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if let touch = lastTouch {
myShip.physicsBody.velocity = CGVector(dx: (lastTouch!.x - myShip.position.x) * 16, dy: 0)
}
}
===============================
I have a SPACESHIP node with movement restricted to the X-Axis. When the user PRESSES and HOLDS somewhere on the screen, I want the SPACESHIP to be able to move to the finger's x-coordinate, and not stop moving toward the finger until the finger is RELEASED. If the SPACESHIP is close to the users finger and the users finger is still pressed down, I want it to gradually slow down and stop. I also want this smooth motion to be applied when the SPACESHIP changes direction, starts, and stops.
I am trying to figure out the best way to do this.
So far, I have created the node and it moves correctly, but there is a problem: If I press on the screen and hold down, the ship will eventually cross over my finger and keep moving. This is because the logic to change direction of the ship is only triggered if I move my finger. So essentially, moving my finger over the ship to change the ships' direction works, but if the ship crosses over my still finger, it does't change direction
I need the SPACESHIP node to recognize when it has crossed over my still finger, and either change its direction or stop based on how close it is to my finger.
Here is the relevant code:
Part 1: When the user presses down, find out where the touch is coming from and move myShip (SPACESHIP) accordingly using velocity
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
if (touchLocation.x < myShip.position.x) {
myShip.xVelocity = -200
} else {
myShip.xVelocity = 200
}
}
Part 2 When the user moves their finger, trigger an event that checks to see if the finger has now moved to the other side of the ship. If so, change direction of the ship.
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
//distanceToShip value will eventually be used to figure out when to stop the ship
let xDist: CGFloat = (touchLocation.x - myShip.position.x)
let yDist: CGFloat = (touchLocation.y - myShip.position.y)
let distanceToShip: CGFloat = sqrt((xDist * xDist) + (yDist * yDist))
if (myShip.position.x < touchLocation.x) && (shipLeft == false) {
shipLeft = true
myShip.xVelocity = 200
}
if (myShip.position.x > touchLocation.x) && (shipLeft == true) {
shipLeft = false
myShip.xVelocity = -200
}
}
Part 3 When the user releases their finger from the screen, I want the ship to stop moving.
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
myShip.xVelocity = 0
}
Part 4 Update event that changes the Ship's position
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let rate: CGFloat = 0.5; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
let relativeVelocity: CGVector = CGVector(dx:myShip.xVelocity - myShip.physicsBody.velocity.dx, dy:0);
myShip.physicsBody.velocity = CGVector(dx:myShip.physicsBody.velocity.dx + relativeVelocity.dx*rate, dy:0);
Thanks for reading, and looking forward to a response!
You can save yourself a lot of trouble by using: myShip.physicsBody.applyImpluse(vector). It works by acting as if you gave myShip a push in the direction vector points. If you calculate vector as the x distance from your last touch location to myShip, then it'll accelerate, decelerate, change direction, etc. pretty close to the way you're describing because it'll be giving it little pushes in the right direction on each update.
Basically you store the last touch location then, in your update function, you calculate the CGVector pointing from myShip to lastTouch and apply that as an impulse to your physics body.
Something like:
var lastTouch: CGPoint? = nil
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
// Be sure to clear lastTouch when touches end so that the impulses stop being applies
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
lastTouch = nil
}
override func update(currentTime: CFTimeInterval) {
// Only add an impulse if there's a lastTouch stored
if let touch = lastTouch {
let impulseVector = CGVector(touch.x - myShip.position.x, 0)
// If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
myShip.physicsBody.applyImpluse(impulseVector)
}
}
You'll also probably want to play with the linearDamping and angularDamping values on myShip.physicsBody. They'll help determine how fast myShip accelerates and decelerates.
I maxed out the values at 1.0 in my app:
myShip.physicsBody.linearDamping = 1.0
myShip.physicsBody.angularDamping = 1.0
If myShip doesn't stop fast enough for you, you can also try applying some breaking in your update function:
override func update(currentTime: CFTimeInterval) {
// Only add an impulse if there's a lastTouch stored
if let touch = lastTouch {
let impulseVector = CGVector(touch.x - myShip.position.x, 0)
// If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
myShip.physicsBody.applyImpluse(impulseVector)
} else if !myShip.physicsBody.resting {
// Adjust the -0.5 constant accordingly
let impulseVector = CGVector(myShip.physicsBody.velocity.dx * -0.5, 0)
myShip.physicsBody.applyImpulse(impulseVector)
}
}
For 2017 here's the easy way to do what is explained in the correct answer here.
There's no need to store the previous position, it is given to you...
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let t: UITouch = touches.first! as UITouch
let l = t.location(in: parent!)
let prev = t.previousLocation(in: parent!)
let delta = (l - prev).vector
physicsBody!.applyImpulse(delta)
}
That's it.
Two notes. (A) properly you should divide the delta distance by the deltaTime to get the correct impulse. If you're a hobbyist really just multiply by "about 100" and you'll be fine. (B) note that of course you will need an extension or function to convert CGPoint to CGVector, it's impossible to do anything without that.
In your thuchesBegan and touchesMoved store the touch location as the "target". In the update then check the position of your ship and reset the xVelocity to 0 if the ship has reached/passed the target.
Since you are only interested in the x coordinate you could also store just touchLocation.x. You can also reverse the velocity but I think that would look strange. Note that if the user moves the finger again, your ship will start moving again because the touchMoved will be triggered again.
On a side note, within touchesMoved you are also setting the shipLeft property but this is not set in your touchesBegan. If this property is used elsewhere you should sync its use.