I am making a game using SpriteKit and Swift, running Xcode 6. I have an SKLabelNode, let's call it myLabelNode for this example. When I call myLabelNode.removeFromParent() it removes the node from the scene, as it should. The node count drops by 1, and it isn't visible anywhere on the screen. However, when I click the spot where myLabelNode previously was, my program will still call out the function that should only happen when myLabelNode is touched. I also tried combining myLabelNode.removeFromParent() with myLabelNode.hidden = true, but it is still touchable, and calls the function even though it shouldn't. How should I fix this? Is there a different method I should be using? Is this supposed to happen?
Edit:
let lemonadeLabel = SKLabelNode(fontNamed: "Optima-ExtraBlack")
override func didMoveToView(view: SKView) {
lemonadeLabel.text = "Lemonade Stand"
lemonadeLabel.fontSize = 24
lemonadeLabel.fontColor = SKColor.yellowColor()
lemonadeLabel.position = CGPoint(x: size.width/2, y: size.height*0.66)
lemonadeLabel.zPosition = 2.0
addChild(lemonadeLabel)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if lemonadeLabel.containsPoint(location) {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
/*lemonadeLabel is now be removed,
however if I click the area where it
used to be, "lemonadeLabel pressed"
will print to the console*/
}
}
You are trying to determine if the constrainPoints' location are being touched. Even if you remove the label from the scene, it is still an object in memory, i.e: you could re-ad it later.. it still has all it's properties including position, etc..
I would try this instead:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
if nodeAtPoint(touch.locationInNode(self)) == lemonadeLabel {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
}
}
}
You basically determine if the lemonadeLabel is the node at that position, if yes you remove it. Since you compare with the added node in the scene, if it's gone, it will not be there for comparison ;)
Your labelNode may not be inside the SKScene anymore. This does not mean that it will not respond to the containsPoint function. The labelNode still has a position assigned to it and it can calculate if a point falls inside it using containsPoint function.
Instead you can try this.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) === lemonadeLabel {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
}
}
Related
I am making a SpriteKit game that has a button node that is at the highest zPosition. I am overriding the touchesBegan function to add functionality to the button. It works because that button is at the highest position in the z dimension. The .zPosition is set to the highest amount compared to any other nodes. I need to have another button in the scene but it needs to have a lower .zPosition because it needs to be covered by another node at one point. When I have a .zPosition that is lower than another node, the touchesBegan method does not trigger. It works if I have a higher .zPosition but that makes the other button node not work. How can I make the touchesBegan method work regardless of what the .zPosition is? Please ask me for more clarification. Thanks!
Once you get the point in touchesBegan did you try to test if the button node contains the point :
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) -> Bool {
if touches.count == 1 {
if let touch = touches.first {
let location = touch.location(in: self)
if myButtonNode.contains(location) {
...
You want to use the nodes(at:) method. This is an array of all nodes at a particular point, the point being where you touch.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
let touchedNodes = self.nodes(at: location) // Array of all nodes at the point
for node in touchedNodes { // Iterate through all nodes in the touchedNode array
if node.name == "your button node" {
... // Add your button code here
}
}
}
}
I'm trying to make an app something like this but i couldn't find out how to do it. i have tried many things but it didn't work or i couldn't make it work since i'm very new on swift.
this is the video reference: https://youtu.be/6cCV_YsbUq0?t=2s
Thanks for your time
i am not sure , but you can try with x and y position of screen touched, to find x and y position follow this code.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view)
print(position.x)
print(position.y)
}
}
I've two classes in separate files.
In the "main" file, where my scene is, I'm calling this function:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
character().spawnCharacter()
}
}
So, in my second file, character.swift I've this code:
import SpriteKit
public func spawnCharacter() -> SKSpriteNode{
let charNode = SKSpriteNode(imageNamed: "Hero")
charNode.name = "Hero"
charNode.postion = CGPointZero
charNode.zPosition = 3
addChild(charNode)
print("Node added")
return charNode
}
So, the print line is printed, but the node is never added.
I've been over my code a couple of times, but I can't crack it.
Any clues?
I am not entirely sure how your main file is organised, but I think the problem is here:
for touch in touches {
character().spawnCharacter() // <- SKSpriteNode is created but not added
}
You create sprite-node in spawnCharacter() but you do not use the returned object.
Potential fix would be:
for touch in touches {
let character = character().spawnCharacter()
scene.addChild(character)
}
My question: Is there a way to adjust the "sensitivity" of UIPanGestureRecognizer so that it turns on 'sooner', i.e. after moving a fewer number of 'pixels'?
I have a simple app with a UIImageView, and pinch and pan gesture recognizers tied to this so that the user can zoom in and draw on the image by hand. Works fine.
However, I notice the stock UIPanGestureRecognizer doesn't return a value of UIGestureRecognizerState.Changed until the user's gesture has moved about 10 pixels.
Example: Here's a screenshot showing several lines that I've attempted to draw shorter & shorter, and there is a noticeable finite length below which no line gets drawn because the pan gesture recognizer never changes state.
IllustrationOfProgressivelyShorterLines.png
...i.e., to the right of the yellow line, I was still trying to draw, and my touches were being recognized as touchesMoved events, but the UIPanGestureRecognizer wasn't firing its own "Moved" event and thus nothing was getting drawn.
(Note/clarification: That image takes up the entirety of my iPad's screen, so my finger is physically moving more than an inch even in the cases where no state change occurs to the recognizer. It's just that we're 'zoomed in' in terms of the tranformation generated by the pinch gesture recognizer, so a few 'pixels' of the image take up a significant amount of the screen.)
This is not what I want. Any ideas on how to fix it?
Maybe some 'internal' parameter of UIPanGestureRecognizer I could get at if I sub-classed it or some such? I thought I'd try to sub-class the recognizer in a manner such as...
class BetterPanGestureRecognizer: UIPanGestureRecognizer {
var initialTouchLocation: CGPoint!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
initialTouchLocation = touches.first!.locationInView(view)
print("pan: touch begin detected")
print(self.state.hashValue) // this lets me check the state
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
print("pan: touch move detected")
print(self.state.hashValue) // this remains at the "began" value until you get beyond about 10 pixels
let some_criterion = (touches.first!.isEqual(something) && event.isEqual(somethingElse))
if (some_criterion) {
self.state = UIGestureRecognizerState.Changed
}
}
}
...but I'm not sure what to use for some_criterion, etc.
Any suggestions?
.
Other alternatives that could work, but that I'd rather not have to do:
I could simply attach my UIPanGestureRecognizer to some parent,
non-zoomed view, and then use affine transforms & such to remap the
points of the pan touches onto the respective parts of the image.
So why am I not doing that? Because the code is written so that
lots of other objects hang off the image view and they all get the
same gesture recognizers and....everything works just great without
my having keep track of anything (e.g. affine transformations), and the problem only shows up if you're really-really zoomed in.
I could abandon UIPanGestureRecognizer, and effectively just write my own using touchesBegan and touchesMoved (which is kind of
what I'm doing), however I like how UIPanGestureRecognizer
differentiates itself from, say, pinch events, in a way that I don't
have to worry about coding up myself.
I could just specify some maximum zoom beyond which the user can't go. This fails to implement what I'm going for, i.e. I want to allow for fine-detail level of manipulation.
Thanks.
[Will choose your answer over mine (i.e., the following) if merited, so I won't 'accept' this answer just yet.]
Got it. The basic idea of the solution is to change the state whenever touches are moved, but use the delegate method regarding simultaneous gesture recognizers so as not to "lock" out any pinch (or rotation) gesture. This will allow for one- and/or multi-fingered panning, as you like, with no 'conflicts'.
This, then, is my code:
class BetterPanGestureRecognizer: UIPanGestureRecognizer, UIGestureRecognizerDelegate {
var initialTouchLocation: CGPoint!
override init(target: AnyObject?, action: Selector) {
super.init(target: target, action: action)
self.delegate = self
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
initialTouchLocation = touches.first!.locationInView(view)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
if UIGestureRecognizerState.Possible == self.state {
self.state = UIGestureRecognizerState.Changed
}
}
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
if !(shouldRecognizeSimultaneouslyWithGestureRecognizer is UIPanGestureRecognizer) {
return true
} else {
return false
}
}
}
Generally setting that "shouldRecognizeSimultaneouslyWithGestureRecognizer" delegate to true always is what many people may want. I make the delegate return false if the other recognizer is another Pan, just because I was noticing that without that logic (i.e., and making the delegate return true no matter what), it was "passing through" Pan gestures to underlying views and I didn't want that. You may just want to have it return true no matter what. Cheers.
Swift 5 + small improvement
I had a case when accepted solution conflicted with basic taps on toolbar which also had this betterPanGesture so I added minimum horizontal offset parameter to trigger state changing to .changed
class BetterPanGestureRecognizer: UIPanGestureRecognizer {
private var initialTouchLocation: CGPoint?
private let minHorizontalOffset: CGFloat = 5
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
self.initialTouchLocation = touches.first?.location(in: self.view)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
if self.state == .possible,
abs((touches.first?.location(in: self.view).x ?? 0) - (self.initialTouchLocation?.x ?? 0)) >= self.minHorizontalOffset {
self.state = .changed
}
}
}
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.