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
}
}
}
}
Related
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:
I'm working a little game for iOS.
I've a SKSpriteNode in my scene - when I remove it with "removeFromParent" and touch the area it was last in, I still get the function.
My code is as following:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if tapToPlayNode.containsPoint(location){
tapToPlayNode.removeFromParent()
startNewGame()
}
}
}
func startNewGame(){
//Starts a new game with resetted values and characters in position
println("Ready.. set.. GO!")
//Shows the ui (value 1)
toggleUiWithValue(1)
}
In other words, I get "Ready.. set.. GO!" output when I touch the area even after it was deleted.
Any clues?
Bests,
Your tapToPlayNode is still retained by self and is removed from it's parent.
You should make it optional var tapToPlayNode:SKSpriteNode?and nil it after remove it from it's parent like this:
if let playNode = self.tapToPlayNode {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if playNode.containsPoint(location) {
playNode.removeFromParent()
startNewGame()
self.tapToPlayNode = nil // il it here!
break
}
}
}
You can also avoid to keep a reference of your tapToPlayNode and give it a name when initializing it like this:
node.name = #"tapToPlayNodeName"
// Add node to scene and do not keep a var to hold it!
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
// Retrieve the ode here
let tapToPlayNode = container.childNodeWithName("tapToPlayNodeName")!
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if tapToPlayNode.containsPoint(location){
tapToPlayNode.removeFromParent()
startNewGame()
}
}
}
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.
Im dragging a SKSpriteNode around with the following method
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
var touchedNode = nodeAtPoint(location)
if touchedNode.isKindOfClass(Card) {
touchedNode.position = location
}
}
}
the if statement is confirming that it;s pif a specific class, how can I now call methods and access properties this node?
You can use this syntax:
if let cardNode = touchedNode as Card {
cardNode.position = location
// cardNode is of type card. You can access all its
// methods and properties
}
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.