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.
Related
Here is a video of the issue I am having. As you can see in the video, I move a gray ball to try and collide with a red ball. When the two objects collide no bouncing occurs, the red ball just moves. I've tried playing around with the densities of the red balls, such as making the red ball densities 0.00001. There was no difference in the collision behavior.
How can I change the collision behavior so there is bouncing?
Here are the properties of the gray ball:
func propertiesGrayBall() {
gray = SKShapeNode(circleOfRadius: frame.width / 10 )
gray.physicsBody = SKPhysicsBody(circleOfRadius: frame.width / 10 )
gray.physicsBody?.affectedByGravity = false
gray.physicsBody?.dynamic = true
gray.physicsBody?.allowsRotation = false
gray.physicsBody?.restitution = 1
}
Here are the properties of the red ball:
func propertiesRedBall {
let redBall = SKShapeNode(circleOfRadius: self.size.width / 20
redBall.physicsBody = SKPhysicsBody(self.size.width / 20)
redBall.physicsBody?.affectedByGravity = false
redBall.physicsBody?.dynamic = true
redBall.physicsBody?.density = redBall.physicsBody!.density * 0.000001
redBall.physicsBody?.allowsRotation = false
redBall.physicsBody?.restitution = 1
}
Here is how I move the gray ball.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if fingerIsOnGrayBall {
let touch = touches.first
var position = touch!.locationInView(self.view)
position = self.convertPointFromView(position)
grayBall.position = position
}
}
Major Edits
The ball originally had attachments. I deleted them to simplify the problem. That's why the comments might not add up with the code.
If you move a node (with a physics body) by settings its position, directly or with an SKAction, the node will not be a part of the physics simulation. Instead, you should move the node by applying a force/impulse or by setting its velocity. Here's an example of how to do that:
First, define a variable to store the position of the touch
class GameScene: SKScene {
var point:CGPoint?
Then, delete the following statement from your code
redBall.physicsBody?.density = redBall.physicsBody!.density * 0.000001
Lastly, add the following touch handlers
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (node.name == "RedBall") {
point = location
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
if point != nil {
point = location
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
point = nil
}
override func update(currentTime: CFTimeInterval) {
if let location = point {
let dx = location.x - redBall.position.x
let dy = location.y - redBall.position.y
let vector = CGVector(dx: dx*100, dy: dy*100)
redBall.physicsBody?.velocity = vector
}
}
I have the following function which spawns squares and adds them to an array of squares. This adds new squares indefinitely until the function is stopped. The array of squares is declared in the SKScene like so: var rsArray = [RedSquare]().
func spawnRedSquares() {
if !self.gameOver {
let rs = RedSquare()
var rsSpawnRange = self.frame.size.width/2
rs.position = CGPointMake(rsSpawnRange, CGRectGetMaxY(self.frame) + rs.sprite.size.height * 2)
rs.zPosition = 3
self.addChild(rs)
self.rsArray.append(rs)
let spawn = SKAction.runBlock(self.spawnRedSquares)
let delay = SKAction.waitForDuration(NSTimeInterval(timeBetweenRedSquares))
let spawnThenDelay = SKAction.sequence([delay, spawn])
self.runAction(spawnThenDelay)
}
}
I'm trying to use the touchesBegan() function to detect when a specific square in the array is tapped and then access the properties of the square. I can't figure out how to determine which square is being touched. How would I go about doing this?
Give each of the squares you spawn a unique name and check that name in touchesBegan. You can use a counter and do
rs.name = "square\(counter++)"
In touchesBegan you can retrieve the name of the touched node and check it against the names of the nodes in the array.
First you have to give the rs node a name. For example
rs.name = "RedSquare"
Then you can use the nodeAtPoint function to find the node at a particular touch point. If the node is a RedSquare, you can modify it.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let touchPoint = touch.locationInNode(self)
let node = self.nodeAtPoint(touchPoint)
if node.name == "RedSquare" {
// Modify node
}
}
}
I was able to answer my own question by experimenting, and decided that I would post the answer in case anyone else had a similar problem. The code that I needed inside the touchesBegan() function was the following:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let rsCurrent = self.nodeAtPoint(location)
for RedSquare in rsArray {
let rsBody = RedSquare.sprite.physicsBody
if rsBody == rsCurrent.physicsBody? {
//Action when RedSquare is touched
}
}
}
}
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.
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.
I am new to Xcode and would like to check how do I code it such that I can use touch to move my object only in the y-coordinate.
Currently, I have programmed it to follow my touch, however it will follow both the x and y coordinate.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
if moving.speed > 0 {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
bird.physicsBody?.velocity = CGVectorMake(0, 0)
bird.position = location
}
}else if canRestart {
self.resetScene()
}
}
I have tried searching for a few links online but I don't seem to understand it quite well.
If you are just trying to move the "bird" up and down along the y axis from it's current x position, then use only the y value from the location:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
if moving.speed > 0 {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
// Just duplicate our old position
var newPosition = bird.position
// and change only the y value, leaving the x value as whatever it was initially set to. X will never change
newPosition.y = location.y
bird.physicsBody?.velocity = CGVectorMake(0, 0)
bird.position = newPosition
}
}else if canRestart {
self.resetScene()
}
}
Check out the documentation on CGGeometry and, specifically, CGPoint.