Get touch location inside the bounds of the screen with swift? - ios

I have a scene that is larger than the size of the device screen, and there is a camera that follows the player around the scene. I want to move the player to the right if the user touches on the right side of the screen and to the left if they touch on the left side. But since my scene is larger than my phone screen, in the touchesBegan method there are problems when the user gets to the far edges of the scene. If the player goes to the left of the scene, even if the user touches on the right side of the screen it still registers as being a "left touch" because the entirety of the phone screen is filled only by the left side of the scene.
Here is what I have in my touchesBegan method:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = true
let touch = touches.first
if let location = touch?.location(in: self) {
print(location)
if location.x < 0 {
player.physicsBody?.applyImpulse(CGVector(dx: -8, dy: 0))
} else {
player.physicsBody?.applyImpulse(CGVector(dx: 8, dy: 0))
}
}
}
I tried changing the touch?.location(in: self) part but that seems to have no effect. Is there a way to do record where the user touches relative to the bounds of the SCREEN, not the scene?

You can use UIScreen.main.focusedView instead of self.

Related

Swift iOS: Detect when a user drags/slides finger over a UILabel?

I have a project where I’m adding three UILabels to the view controller’s view. When the user begins moving their finger around the screen, I want to be able to determine when they their finger is moving over any of these UILabels.
I’m assuming a UIPanGestureRecognizer is what I need (for when the user is moving their finger around the screen) but I’m not sure where to add the gesture. (I can add a tap gesture to a UILabel, but this isn’t what I need)
Assuming I add the UIPanGestureRecognizer to the main view, how would I go about accomplishing this?
if gesture.state == .changed {
// if finger moving over UILabelA…
// …do this
// else if finger moving over UILabelB…
// …do something else
}
You can do this with either a UIPanGestureRecognizer or by implementing touchesMoved(...) - which to use depends on what else you might be doing.
For pan gesture, add the recognizer to the view (NOT to the labels):
#objc func handlePan(_ g: UIPanGestureRecognizer) {
if g.state == .changed {
// get the location of the gesture
let loc = g.location(in: view)
// loop through each label to see if its frame contains the gesture point
theLabels.forEach { v in
if v.frame.contains(loc) {
print("Pan Gesture - we're panning over label:", v.text)
}
}
}
}
For using touches, no need to add a gesture recognizer:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let t = touches.first {
// get the location of the touch
let loc = t.location(in: view)
// loop through each label to see if its frame contains the touch point
theLabels.forEach { v in
if v.frame.contains(loc) {
print("Touch - we're dragging the touch over label:", v.text)
}
}
}
}

Sprite-Kit: color xor logic with two sprites. black + black = white

Do you know the puzzle game „voi“? That is a game which works with color-XOR-logic. That means: black + black = white.
https://www.youtube.com/watch?v=Aw5BdVcAtII
Is there any way to do the same color logic with two sprite nodes in sprit kit?
Thanks.
Of course, it's possible to do that in Sprite Kit.
Problem:
Let's say you have 2 black squares, squareA and squareB. The user can drag these two squares wherever he wants to. He can drag only one square at a time. You want to color the intersect area to white whenever the two squares intersect.
Initial Setup:
At the top of your scene, there are a few variables that we need to create:
private var squareA: SKSpriteNode?
private var squareB: SKSpriteNode?
private var squares = [SKSpriteNode]()
private var selectedShape: SKSpriteNode?
private var intersectionSquare: SKShapeNode?
squareA and squareB are just the 2 squares that we initially have on screen.
squares is an array and it will store all the squares that are showing on screen.
selectedShape will help us keeping track of the square that is currently being dragged.
intersectionSquare is a white square that represents the intersection area between the two black squares.
Then initialize squareA and squareB, and add them to the squares array like so:
squareA = SKSpriteNode(color: .black, size: CGSize(width: 190.0, height: 190.0))
if let squareA = self.squareA {
squareA.position = CGPoint(x: -200, y: 200)
squareA.name = "Square A"
squares.append(squareA)
self.addChild(squareA)
}
// Do the same for squareB or any other squares that you have on screen..
Note: As you can see, I gave it a name here just to make it easier to differentiate them during the testing phase.
Detect when user is dragging a square:
Now, you need to detect when the user is dragging a square. To do this, you can use:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
These are just helper methods that are going to make our life easier.
Then, you need to setup touchDown, touchMoved and touchUp methods:
func touchDown(atPoint pos : CGPoint) {
let touchedNode = self.nodes(at: pos)
guard let selectedSquare = touchedNode.first as? SKSpriteNode else {
return
}
selectedShape = selectedSquare
}
func touchMoved(toPoint pos : CGPoint) {
guard let selectedSquare = self.selectedShape else {
return
}
selectedSquare.position = pos
checkIntersectionsWith(selectedSquare)
}
func touchUp(atPoint pos : CGPoint) {
selectedShape = nil
}
To explain you in more details what is going on here:
In the touchDown method:
Well, we need the user to be able to drag only one square at a time. Using the nodes(at:) method, it's easy to know which square was touched, and we can know set our selectedShape variable to be equal to the square that was touched.
In the touchMoved method:
Here we are basically just moving the selectedShape to the position the user moves his finger at. We also call the checkIntersectionsWith() method that we will setup in a second.
In the touchUp method:
The user released his finger from the screen, so we can set the selectedShape to nil.
Change the color of the intersection frame:
Now the most important part to make your game actually look like the one you want to make, is how to change the color of the intersection frame to white when two black squares are intersecting ?
Well, you have different possibilities here, and here is one possible way of doing it:
private func checkIntersectionsWith(_ selectedSquare: SKSpriteNode) {
for square in squares {
if selectedSquare != square && square.intersects(selectedSquare) {
let intersectionFrame = square.frame.intersection(selectedSquare.frame)
intersectionSquare?.removeFromParent()
intersectionSquare = nil
intersectionSquare = SKShapeNode(rect: intersectionFrame)
guard let interSquare = self.intersectionSquare else {
return
}
interSquare.fillColor = .white
interSquare.strokeColor = .clear
self.addChild(interSquare)
} else if selectedSquare != square {
intersectionSquare?.removeFromParent()
intersectionSquare = nil
}
}
}
Every time the checkIntersectionsWith() method is called, we are iterating through the nodes that are inside our squares array, and we check, using the frame's intersection() method, if the selected square intersects with any of these (except itself). If it does, then we create a white square, named intersectionSquare, and set its frame to be equal to the intersection frame.
And to save up your memory usage, you can delete the square from the scene and set intersectionSquare to nil if there is no intersection at all.
Final result:
The final result would look like this:
That's just a rapid draft that I made to show you on you could approach the problem, and obviously there are many things that you could add or improve (apply this to a situation where you have not only 2 but many squares on screen, or create a kind of magnetism effect for when your user release his finger from the screen, etc) but I hope at least it will put you on the right track for your project :)

How to replicate iOS home screen behaviour?

I am currently working on a game in SpriteKit, where I need to move a sprite in response to touch (i.e when user swipes or pans anywhere in SKView.
I want to get the direction of pan (for swipe I know how to do it),so that the sprite will move according to pan (I have a path defined for the sprite if user pans or according to swipe if user swipes), the way touch in iOS appdrawer works i.e it responds to slightest of swipes and also pans (i.e when you pan forwards or backwards, it makes a decision whether you want to move to the next screen or not).
Is there any documentation or so? (I have gone through the UIGestureRecognizer documentation, but I haven't been able to find a way to implement it.)
I use something similar on my MenuScene, I have 3 pages setup that the user can scroll through to get various game data. But I don't want the slightest touch to move the screen, it would be to jarring for the user. So I just watch the finger movements in the Touches functions and check if the movement is greater that an amount I designate as the minimum move amount and if it is greater than I scroll the page. In your case you could handle it as; if it is greater than the minimum move amount treat as a pan else treat it as a swipe
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
initialTouch = touch.location(in: self.view!)
moveAmtY = 0
moveAmtX = 0
initialPosition = menuScroller.position
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
let movingPoint: CGPoint = touch.location(in: self.view!)
moveAmtX = movingPoint.x - initialTouch.x
moveAmtY = movingPoint.y - initialTouch.y
//their finger is on the page and is moving around just move the scroller and parallax backgrounds around with them
//Check if it needs to scroll to the next page when they release their finger
menuScroller.position = CGPoint(x: initialPosition.x + moveAmtX, y: initialPosition.y)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//they havent moved far enough so just reset the page to the original position
if fabs(moveAmtX) > 0 && fabs(moveAmtX) < minimum_detect_distance {
resetPages()
}
//the user has swiped past the designated distance, so assume that they want the page to scroll
if moveAmtX < -minimum_detect_distance {
moveLeft()
}
else if moveAmtX > minimum_detect_distance {
moveRight()
}
}

Detecting tap outside/away from a sprite

I have a game where there is a green circle that spawns randomly at random locations on a scene, and every time the circle is tapped it changes location, with a slight possibility to change to red. When the circle is red, I want the user to tap away screen real estate that is not the red circle. How do I detect a tap not on the circle? My circle is a SKShapeNode. I handle touches with the touchesBegan function.
To determine if the user's touch is inside or outside of a circle, 1) compute the distance between the touch and the center of the circle and 2) compare if that distance is less than or equal to the radius of the circle. Here's an example of how to do that:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let dx = location.x - circle.position.x
let dy = location.y - circle.position.y
let distance = sqrt(dx*dx + dy*dy)
if (distance <= CGFloat(radius)) {
print ("inside of circle")
}
else {
print ("outside of circle")
}
}
}

Move a node to finger using Swift + SpriteKit

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.

Resources