How can I loop through the circle UIBezierPath coordinates so I can move the dot object over the circular path when the user moves their finger on the screen? (kind of like a virtual combination lock)
let blueDotCategoryName = "blue"
var blueDot = SKSpriteNode()
//...
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let node = self.nodeAtPoint(touchLocation)
if node.name == blueDotCategoryName {
fingerIsOn = true
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if fingerIsOn {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let prevTouchLocation = touch.previousLocationInNode(self)
let blueDot = self.childNodeWithName(blueDotCategoryName) as SKSpriteNode
// var newPosition = coordinates in circle UIBezierPath
// blueDot.position = ...
}
/*
var circle = UIBezierPath()
circle.addArcWithCenter(CGPoint(x: 0, y: 0), radius: (self.frame.width / 6.7), startAngle: 0, endAngle: 360, clockwise: true)
*/
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
fingerIsOn = false
}
There is no built-in method to do this for you, so you'll have to write it yourself. It will probably be easier if you create the bezier path as a series of line segments (and if they're reasonably close enough to each other, it will be indistinguishable from the original path).
Having said that, given your description (a "virtual combination lock"), I wonder if there aren't some third party complex gesture recognizers that might simplify the whole process for you:
$1 Unistroke Recognizer - A unistroke recognizer of complex gestures.
You might also consider GLGestureRecognizer, an Objective-C implementation.
$N Multistroke Recognizer - A multistroke version of the $1 Unistroke Recognizer
This is discussed in some detail in Complex Gesture Recognition in iOS.
These might jump start your efforts.
Related
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)
}
I am creating a game for iOS using sprite kit written in swift. I have a bunch of SKSpriteNode on a screen that can be dragged around. They are various shapes such as a person, an apple, a book etc. The problem is, say the person is in front of the apple but you can see the apple behind the person through the transparent pixels in the persons image (png). When you go to touch the apple to move it, the person gets selected because of those transparent pixels instead of the apples.
How do I make a SKSpriteNode that does not respond to touch if it's pixels are transparent?
If you want to select a sprite that is partially hidden by another sprite, you can use nodesAtPoint:CGPoint to obtain an array of nodes that are under the user's touch. You can then iterate over the nodes in the array to find and select the node closest to the touch point. Here's a quick example of how to do that in Swift:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touch = touches.first as? UITouch {
let location = touch.locationInNode(self)
var closest:CGFloat?
let nodes = nodesAtPoint(location)
for node in nodes as! [SKNode] {
if let sprite = node as? SKSpriteNode {
// Calculate the distance from the node to the touch
let dx = location.x - node.position.x
let dy = location.y - node.position.y
let distance = dx*dx + dy*dy
// Find the closest
if closest == nil || distance < closest! {
closest = distance
selected = sprite
}
}
}
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if let sprite = selected {
if let touch = touches.first as? UITouch {
let location = touch.locationInNode(self)
sprite.position = location
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
selected = nil
}
Here's a clip of shapes being moved by touch with the above code:
In my SpriteKit game I'm trying to add a SKEmitterNode to a SKSpriteNode.
As I move my finger across the screen the fire in this case should move with it.
override func touchesBegan(touches: NSSet, withEvent event:UIEvent) {
var touch = touches.anyObject() as UITouch!
var touchLocation = touch.locationInNode(self)
let body = self.nodeAtPoint(touchLocation)
if body.name == FireCategoryName {
println("Began touch on body")
firePosition = body.position
isFingerOnGlow = true
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if isFingerOnGlow{
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
firePosition = CGPointMake(firePosition.x + distanceX, firePosition.y + distanceY)
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
isFingerOnGlow = false
}
I have firePosition like this, allowing it to change position on the SKSpriteNode fire, and the SKEmitterNode bloss.
var bloss = SKEmitterNode()
var fire = SKSpriteNode()
var firePosition : CGPoint = CGPointMake(100, 200) { didSet { fire.position = firePosition ; bloss.position = firePosition } }
As expected the two show up at the same place but even though the bloss node has its .targetNode set to fire it doesn't look natural. With natural I mean that if the targetNode moves the emitter will spread particles on the path the body is moving on. For me the emitter just burns upwards and doesn't change form or anything. When I tried letting firePosition only set the sprites position and having a constant position for the emitter, it spread it's particles to different sides according to how I moved the sprite. Sorry if the explanation was vague... Is there a way to fix this?
set the bloss emitterNode's targetNode property to the scene.
bloss.targetNode = self
it doesn't look natural because you made the targetNode the sprite. the emitter is always in the same position relative to the sprite, so you dont get that wavy effect i think you're going for.
I'm trying to create a button that switches the scene. I know the code to switch the scene but the code I'm using to do it with a button isn't working.
UPDATE: I have the code working on the first scene, but when I use the same code on another scene (i switched the button and scene) it doesnt work.
anyone know why?
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let gamestartScene = GameStartScene(size: self.frame.size)
var location = CGPoint(x: 0, y: 0)
var touch = touches.anyObject() as UITouch
location = touch.locationInView(self.view)
if menuButton.containsPoint(location){
self.removeChildrenInArray([menuButton,replayButton,highScoreLabel,scoreLabela])
self.view?.presentScene(gamestartScene)
score = 0
}
}
Use locationInNode instead of locationInView
let location = touch.locationInNode(self)
The origin of UIView coordinates and the SKScene coordinates are different. The origin (0,0) of the UIView is at the top left. The origin of the SKScene is at the bottom left. So the functions locationInNode and locationInView will return different results.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if(button.containsPoint(location)) {
}
}
}
I'm struggling to discover why I cannot detect a bodyAtPoint with SpriteKit. bodyAtPoint always returns nil, even when it appears I'm always tapping the sprite node.
Here's the code:
...
let spaceship = SKSpriteNode(color: UIColor.blueColor(), size: CGSizeMake(100, 100))
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody = borderBody
self.physicsBody.friction = 0.0
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0)
spaceship.name = "spaceship"
spaceship.position = CGPointMake(400, 300)
var bodySize = CGSizeMake(spaceship.size.width / 1.15, spaceship.size.height / 1.15);
spaceship.physicsBody = SKPhysicsBody(rectangleOfSize: bodySize)
spaceship.physicsBody.dynamic = false
spaceship.physicsBody.restitution = 1.0
spaceship.physicsBody.friction = 0.0
spaceship.physicsBody.linearDamping = 0.0
spaceship.physicsBody.allowsRotation = false
self.addChild(spaceship)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
super.touchesBegan(touches, withEvent: event)
var touch : UITouch! = touches.anyObject() as UITouch
var touchLocation : CGPoint! = touch.locationInNode(self)
if self.physicsWorld.bodyAtPoint(touchLocation) {
NSLog("true")
} else {
NSLog("false")
}
}
...
RESULTS:
spaceship.physicsBody outputs:
<SKPhysicsBody> type:<Rectangle> representedObject:[<SKSpriteNode> name:'spaceship' texture:['nil'] position:{400, 300} size:{100, 100} rotation:0.00]
touchLocation output:
(411.943664550781,553.014099121094)
self.physicsWorld.bodyAtPoint(touchLocation) is always:
nil
... Therefore the conditional always returns false.
Can anybody explain where I'm going wrong? I want to ultimately detect a touch on a sprite node and perform an action.
EDIT:
Even if I simplify to the following I still always get false:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
...
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.physicsWorld.bodyAtPoint(location) {
NSLog("true")
} else {
NSLog("false")
}
}
}
...
If you want to use exactly bodyAtPoint, you cannot access to the name of the node. The problem is if you want a pixel perfect touch detection, for example, you need to detect the shape not the rect of the sprite with alpha part.
The trick is to use the categoryBitMask. You can assign the category 0x1 << 1, for example, to an object and then ask for its category. Category is an Uint32 with the same function like name, mainly for physics collision detection, but you can use it for recognize a physicsBody:
let nodo = self.physicsWorld.bodyAtPoint(punto)
if (nodo?.categoryBitMask == 0x1 << 1) {
}
So easy! Hope it helps you!
With many thanks to #eXhausted for the answer.
The trick is to call nodeAtPoint and access nodeAtPoint.name.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location: CGPoint! = touch.locationInNode(self)
let nodeAtPoint = self.nodeAtPoint(location)
if nodeAtPoint.name {
println("NODE FOUND: \(nodeAtPoint.name)")
} else {
println("NULL")
}
}
}
I had a similar problem as nodes approached another node that had an attached particle emitter. Seems the nodeAtPoint() detected the emitter even though it was hidden at the time.
I solved this by using the alternative nodesAtPoint() and iterating through the nodes until I found the node I really wanted to detect.
Not sure if the z order if the particle emitter is bringing it to the top of the stack or not, but not wanting to fiddle with the z ordering at this stage, finding all touched nodes at the touch point worked for me.