Making SKEmitterNode look natural? - ios

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.

Related

No bouncing occurs when dragging an object to collide with another object in Sprite Kit

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
}
}

How do I make a SKSpriteNode that does not respond to touch if it's pixels are transparent?

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:

Slingshot Always Returns to Anchor Point When touchesMoved Stops

So I have a pretty basic slingshot that has two physics bodies attached to one another using a SKPhysicsSpringJoint. When touches begin, I create a projectile and spring. When touches are moved, I move the projectile accordingly to the user's touch position. When touches end, the projectile is launched.
The issue is whenever the user stops moving the projectile, but doesn't let go, the projectile moves towards its anchor point and swings around it until the user releases their touch. I've been working for hours trying to figure out why this is happening, any ideas?
Here are the main functions:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
//Add Cannon Ball
var uniqueID:Int = nodeArray.count
var uniqueString = "cannonBall\(uniqueID)"
var cannonBall = CannonBallNode(location: CGPoint(x: size.width/2, y: 200))
cannonBall.name = uniqueString
// 3 - Determine offset of location to projectile
let offset = touchLocation - cannonBall.position
// 4 - Bail out if you click above cannon
if (offset.y > 50) { return }
self.addChild(cannonBall)
println("\(nodeArray.count)")
if var cannon = childNodeWithName("cannon") {
if var heldCannonBall = childNodeWithName(uniqueString) {
heldCannonBall.physicsBody?.affectedByGravity = false
var spring = SKPhysicsJointSpring.jointWithBodyA(cannon.physicsBody, bodyB: cannonBall.physicsBody, anchorA: cannon.position, anchorB: cannonBall.position)
spring.damping = 0.4;
spring.frequency = 1.0;
// Add Physics
physicsWorld.addJoint(spring)
}
}
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
var uniqueID:Int = nodeArray.count
var uniqueString = "cannonBall\(uniqueID)"
if var heldCannonBall = childNodeWithName(uniqueString) {
// 3 - Determine offset of location to projectile
let offset = touchLocation - heldCannonBall.position
// 4 - Bail out if you are shooting down or backwards
if (offset.y > 0) { return }
heldCannonBall.position = touchLocation
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
var uniqueID:Int = nodeArray.count
var uniqueString = "cannonBall\(uniqueID)"
if var releasedCannonBall = childNodeWithName(uniqueString) {
var angle = CGFloat(M_PI_4)
var magnitude:CGFloat = 1;
releasedCannonBall.physicsBody?.applyImpulse(CGVectorMake(magnitude*cos(angle), magnitude*sin(angle)))
releasedCannonBall.physicsBody?.affectedByGravity = true
}
runAction(SKAction.sequence([
SKAction.runBlock{[self.physicsWorld.removeAllJoints()]},
SKAction.waitForDuration(0.1)]
))
nodeArray.append("\(uniqueString)")
}
}
Finally came across the solution! I needed to produce an update function to update the projectile's position as it was moved via the touchesMoved function. I created a global variable and had it store the location of the touch when it began and when it was moved. I threw it in the update function and voila, no more issues.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
//#1
var uniqueID:Int = nodeArray.count
var uniqueString = "cannonBall\(uniqueID)"
println("\(cannonBallLocation)")
if var releasedCannonBall = childNodeWithName(uniqueString) {
releasedCannonBall.position = cannonBallLocation
}
} //end func

SKNode containsPoint is not working

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)) {
}
}
}

Swift: detecting bodyAtPoint. Always nil

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.

Resources