SpriteKit collision detection not working as expected - swift

So I have this code inside my GameScene class (child of SKScene):
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
addChild(balloon)
addChild(monkey)
monkey.physicsBody = SKPhysicsBody(rectangleOf: monkey.frame.size)
monkey.physicsBody?.isDynamic = false
monkey.physicsBody?.categoryBitMask = 0
monkey.physicsBody?.contactTestBitMask = 1//Balloon.categoryBitMask
balloon.physicsBody = SKPhysicsBody(rectangleOf: balloon.frame.size)
balloon.physicsBody?.isDynamic = false
balloon.physicsBody?.categoryBitMask = 1
balloon.physicsBody?.contactTestBitMask = 0
balloon.start()
I can see that the balloon and the monkey node have touched each other in the simulator, however, nothing happens. I have also conformed to the SKPhysicsContactDelegate protocol, like so:
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
print("CONTACT!!!")
}
}
Edit: I set isDynamic = true and it printed "CONTACT!!" as expected, however I dont want the objects to be affecting each other's position

Related

No collisions being detected in subclassed nodes

I'm writing a game. It is not detecting any collisions, even though I can see the physics bodies as I have that view turned on.
To set-up the physics world in the game scene, I've coded the following above the class declaration:
struct PhysicsCategory {
static let None: UInt32 = 0
static let Chicken: UInt32 = 0b1
static let Edge: UInt32 = 0b10
}
Then, within the actual class of the scene:
override func didMove(to view: SKView) {
setupNodes()
setupTrial()
let playableRect = CGRect(x: 0, y: 0, width: size.width/2, height: size.height/2)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: playableRect)
self.physicsWorld.contactDelegate = self
self.physicsBody!.categoryBitMask = PhysicsCategory.Edge
self.physicsBody!.contactTestBitMask = PhysicsCategory.Chicken
// This is important for handling all the custom events
enumerateChildNodes(withName: "//*", using: { node, _ in
// we need to limit this to chickens only
if let customNode = node as? CustomNodeEvents {
customNode.didMoveToScene()
}
})
}
Here is the code to detect collisions:
func didBegin(_ contact: SKPhysicsContact) {
print("something happened")
let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == PhysicsCategory.Chicken | PhysicsCategory.Edge {
print("it works!")
}
}
The nodes I'd like to animate are chickens. I want the game to detect when they collide with the edges of the world above.
My chicken subclass is this:
class TargetNode: SKSpriteNode, CustomNodeEvents, InteractiveNode {
func didMoveToScene() {
isUserInteractionEnabled = true
anchorPoint = CGPoint(x: 0, y: 0)
let playableRect = CGRect(x: self.anchorPoint.x, y: self.anchorPoint.y, width: self.size.width, height: self.size.height)
physicsBody = SKPhysicsBody(edgeLoopFrom: playableRect)
physicsBody!.isDynamic = true
physicsBody!.categoryBitMask = PhysicsCategory.Chicken
physicsBody!.contactTestBitMask = PhysicsCategory.Edge | PhysicsCategory.Chicken
physicsBody!.velocity = CGVector(dx: 100, dy: 0)
}
}
EDIT: The nodes are being added to the scene using this method in the game scene file.
func generateItems(targetNumber: Int, target: Bool) {
let movingItems = true
for _ in 0...(targetNumber - 1) {
if (target) {
let name = createTarget()
let targetNode = TargetNode(imageNamed: name)
targetNode.name = name
fgNode.addChild(targetNode)
targetNode.position = generateRandomLocation()
//if movingItems { animateTargets(targetNode) }
}
}
setting your chicken to a SKPhysicsBody(edgeLoopFrom :_) is a bad idea. instead try SKPhysicsBody(rectangleOf :_) to draw a rectangle shape around your chicken, the type of shape you want to draw can vary, check the docs for more information.
Moreover to check weather the chickens have made contact with the playableRect
remove self.physicsBody!.contactTestBitMask = PhysicsCategory.Chicken because we don't want to check if the rect has made contact with the chicken we want to know if the chicken has made contact with the playable Rect. So keep contactTestBitMask on the chicken.
remove : | PhysicsCategory.Chicken from your chicken subclass, unless you want to check weather the chickens collide with each other as well.
lastly: check for collisions, if the chicken makes contact with the playableRect
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == PhysicsCategory.Chicken && contact.bodyB.categoryBitMask == PhysicsCategory.Edge {
print("Chickens have collided with edge")
}
}

Touch Sprite, make it jump up then fall down again(repeat as many times as spritenode is tapped.)

I created a project where I have a ball and when the view loads, it falls down, which is good. I'm trying to get the ball to jump back up and fall down again when the Spritenode is tapped.
--This Question was edited--
Originally, I was able to get it to work when sprite.userInteractionEnabled = false. I had to turn this statement true in order to get the score to change.
Now I can't get the balls to fall and be tapped to jump. When I turn ball.physicsBody?.dynamic = true, the balls will fall due to gravity. How do I tap the sprite itself and make it jump.
GameScene.swift (For those who want to try the code for themselves.)
import SpriteKit
class GameScene: SKScene {
var ball: Ball!
private var score = 0 {
didSet { scoreLabel.text = "\(score)" }
}
override func didMoveToView(view: SKView) {
let ball = Ball()
scoreLabel = SKLabelNode(fontNamed:"Geared-Slab")
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPoint( x: self.frame.midX, y: 3 * self.frame.size.height / 4 )
scoreLabel.fontSize = 100.0
scoreLabel.zPosition = 100
scoreLabel.text = String(score)
self.addChild(scoreLabel)
ball.position = CGPoint(x:self.size.width / 2.0, y: 440)
addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 120)
ball.physicsBody?.dynamic = true
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.restitution = 3
ball.physicsBody?.friction = 0
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.usesPreciseCollisionDetection = true
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "Ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
Before, it was a SKNode being tapped using CGVectorMake(impulse, velocity) now, it's a SKSpriteNode, and I tried using SKAction, but it either does not work, or I'm putting it in the wrong place(touches begin).
I tested your code and it seems that using firstBall.userInteractionEnabled = true is the cause. Without it, it should work. I did some research (here for example), but can't figure out what's the reason of this behavior with userInteractionEnabled. Or for what reason do you use userInteractionEnabled?
Update due to update of question
First ball.physicsBody?.restitution = 3 defines the bounciness of the physics body. The default value is 0.2 and the property must be between 0.0 ans 1.0. So if you set it to 3.0 it will cause some unexpected effects. I just deleted it to use the default value of 0.2.
Second, to make the ball jump after tap and increase the score I put
physicsBody?.velocity = CGVectorMake(0, 600)
physicsBody?.applyImpulse(CGVectorMake(0, 1100))
in the touchesBegan method of the Ball class
Result

Ending a game when two nodes collide

I have two separate nodes with their own physics bodies, and when they collide, an SKScene with the high score and replay button should present itself. This is how my scene is called:
func didBeginContact(contact: SKPhysicsContact) {
gameOver()
print("gameOver")
}
And this is how my physics bodies for my nodes are set up:
func createDown(position: CGPoint) -> SKNode {
let circleNode = SKSpriteNode()
let circle = SKSpriteNode(imageNamed: "first#2x")
circleNode.position = CGPointMake(position.x, position.y)
circleNode.physicsBody = SKPhysicsBody(circleOfRadius: 30)
circleNode.physicsBody?.dynamic = false
circle.size = CGSize(width: 75, height: 75)
circleNode.addChild(circle)
circleNode.name = "circleNode"
circle.name = "CIRCLE"
let up = SKAction.moveByX(0, y: -9000, duration: 100)
physicsBody?.categoryBitMask = blackCategory
circleNode.runAction(up)
return circleNode
}
func playerPhysics() {
player.physicsBody = SKPhysicsBody(circleOfRadius: 30)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = blackCategory
}
And here is my gameOver function:
func gameOver() {
gameEnd = true
let reveal = SKTransition.fadeWithDuration(1)
let scene = GameOver(size: self.scene!.size)
view!.presentScene(scene, transition: reveal)
}
Am I missing something? Will post more code if necessary.
Just use intersectsNode and call gameOver() when the two nodes collide.
if yourNode.intersectsNode(yourSecondNode) {
gameOver()
}
SKNode Class Reference: SKNode
You need to add the physics world as a contact delegate for your methods to work.
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
And like Whirlwind said, you need to set your categoryBitMask and contactTestBitMask for the circle node.
Then everything should work. I hope I could help.

Detecting collision on SpriteKit from two objects

I am having some trouble detecting the collision of two objects when i shoot from my cannon. The goal is to shoot a bullet from it and remove as soon as it colides with the obstacle. It seems there is no collision going on at all in the game, since i cannot receive the "Collision" message i set. Take a look at the following code:
class GameScene: SKScene, SKPhysicsContactDelegate {
enum BodyType: UInt32 {
case bullet = 1
case line = 2
case breaker = 4
}
i've enumerated the components so i can set them in the category Bitmask. After that, i did a setup from shooting when a touch begins.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let bullet = SKSpriteNode(imageNamed: "bullet.png")
bullet.position = self.shooterTriangle.position
var actionRotate = SKAction.rotateToAngle(CGFloat(2 * -M_PI), duration: NSTimeInterval(0.1))
let actionMove = SKAction.moveTo(CGPoint(x: 500, y: size.height * 0.5), duration: NSTimeInterval(0.5))
let removeBullet = SKAction.removeFromParent()
bullet.runAction(SKAction.sequence([actionRotate]))
bullet.runAction(SKAction.sequence([actionMove, removeBullet]))
addChild(bullet)
// Bullet Physics Start ****
bullet.physicsBody? = SKPhysicsBody(circleOfRadius: bullet.size.width/2.0)
bullet.physicsBody?.dynamic = true
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.allowsRotation = false
bullet.physicsBody?.categoryBitMask = BodyType.bullet.rawValue
bullet.physicsBody?.contactTestBitMask = BodyType.line.rawValue
bullet.physicsBody?.collisionBitMask = BodyType.line.rawValue
}
}
Then i`ve add the line i want the bullet to colide with in a loadGameComponents function...
func loadGameComponents() {
//Load line....
let line = SKSpriteNode(imageNamed: "Line_1.png")
line.position = CGPoint(x: size.width * 0.95, y: size.height * (2.0))
addChild(line)
var actualSpeedDuration = 2.2
let lineActionMove = SKAction.moveTo(CGPoint(x: size.width * 0.95, y: size.height * 0.6), duration: NSTimeInterval(actualSpeedDuration))
line.runAction(SKAction.sequence([lineActionMove]))
// Line Physics starts! *****
line.physicsBody? = SKPhysicsBody(rectangleOfSize: CGSize(width: line.size.width, height: line.size.height))
line.physicsBody?.dynamic = false
line.physicsBody?.affectedByGravity = false
line.physicsBody?.allowsRotation = false
line.physicsBody?.categoryBitMask = BodyType.line.rawValue
line.physicsBody?.contactTestBitMask = BodyType.bullet.rawValue
line.physicsBody?.collisionBitMask = 0
Finally, i've defined the contactDelegate = self in my didLoad function and included the didBeginContact method
func didBeginContact(contact: SKPhysicsContact) {
println("Contact!!!")
}
I am not receiving the message at all, but i am not sure what is going wrong!
Thanks for the patience! :)
Looks like the main problem is that i used to place the optional indicator (?) when declaring the physics body of an sprite. correction should be made in this lines...
line.physicsBody? = SKPhysicsBody(rectangleOfSize: CGSize(width: line.size.width, height: line.size.height))
bullet.physicsBody? = SKPhysicsBody(circleOfRadius: bullet.size.width/2.0)
Removing them solved my problem :)
I also included the view.showPhysics = true in my didMoveToView method so it is easy to see the boundaries between the sprites.

didBeginContact not working properly

I created a struct PhysicsCatagory for each of the different objects I want interacting with each other
struct PhysicsCatagory {
static let Blade : UInt32 = 1
static let Laser : UInt32 = 2
}
above my class GameScene
class GameScene: SKScene, SKPhysicsContactDelegate {
and I initialized an SKSpriteNode blade with its physicsBody in the didMoveToView method
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
Blade.position = CGPointMake(self.size.width / 2, (self.size.height / 14))
Blade.anchorPoint = CGPointMake(0.5, -0.13)
Blade.physicsBody = SKPhysicsBody(rectangleOfSize: Blade.size)
Blade.physicsBody?.categoryBitMask = PhysicsCatagory.Blade
Blade.physicsBody?.contactTestBitMask = PhysicsCatagory.Laser
Blade.physicsBody?.dynamic = false
self.addChild(Blade)
}
As well as a SKSpriteNode laser and its physicsbody in the method shootLaser
func shootLaser(){
var Laser = SKSpriteNode(imageNamed: "Laser.png")
Laser.position = Enemy.position
Laser.zPosition = -5
Laser.physicsBody = SKPhysicsBody(rectangleOfSize: Laser.size)
Laser.physicsBody?.categoryBitMask = PhysicsCatagory.Laser
Laser.physicsBody?.contactTestBitMask = PhysicsCatagory.Blade
Laser.physicsBody?.dynamic = false
let action = SKAction.moveBy(laserVector, duration: 0.7)
let actionDone = SKAction.removeFromParent()
Laser.runAction(SKAction.sequence([action,actionDone]))
self.addChild(Laser)
}
But when they collide in the simulation, the didBeginContact method is not called and "Hello" is not printed
func didBeginContact(contact: SKPhysicsContact) {
NSLog("Hello")
}
Why isn't the didBeginContact method being called when they collide? Thanks in advance (:
Sprite Kit does not check for contacts between non-dynamic physics bodies because they aren't expected to move. If you don't want your sprites to fall off the screen due to gravity, set the physics body's affectedByGravity property to false and set dynamic = true.