Identify which sprite node was hit - swift

I would like to be able to identify which exact target sprite node is hit when the user flings another sprite at a group of target sprites. Here is how I set up my sprites under the didMoveToView function (only including the relevant lines of code here)
let flingerTexture = SKTexture(imageNamed: "flinger")
flingerNode.position = CGPoint(x: 768, y: 440)
flingerNode.physicsBody = SKPhysicsBody(texture: flingerTexture, size: flingerNode.size)
flingerNode.physicsBody?.dynamic = true
flingerNode.physicsBody!.categoryBitMask = PhysicsCategory.Flinger
flingerNode.physicsBody!.collisionBitMask = PhysicsCategory.Edge | PhysicsCategory.Bubble | PhysicsCategory.Ball
flingerNode.physicsBody?.contactTestBitMask = PhysicsCategory.Ball
let rotationConstraint = SKConstraint.zRotation(SKRange(lowerLimit: -π/4, upperLimit: π/4))
flingerNode.constraints = [rotationConstraint]
addChild(flingerNode)
// -------------Setup targets---------------
let range: Range<Int> = 1...10
for numbers in range {
let ballNode: BallNode = BallNode(imageNamed: "\(numbers)a")
let positionX = CGFloat.random(min: size.width / 6, max: size.width * 5/6)
let positionY = CGFloat.random(min: size.height * 4/9, max: size.height * 8/9)
ballNode.position = CGPoint(x: positionX, y: positionY)
ballNode.name = "Ball"
ballNode.ballIndex = Int(numbers)
index = ballNode.ballIndex
ballNode.ballHit = false
addChild(ballNode)
ballNode.physicsBody = SKPhysicsBody(circleOfRadius: 100)
ballNode.physicsBody!.affectedByGravity = false
ballNode.physicsBody?.dynamic = true
ballNode.physicsBody!.restitution = 0.5
ballNode.physicsBody!.friction = 0.0
ballNode.physicsBody!.categoryBitMask = PhysicsCategory.Ball
ballNode.physicsBody!.collisionBitMask = PhysicsCategory.Ball | PhysicsCategory.Bubble | PhysicsCategory.Edge | PhysicsCategory.Flinger | PhysicsCategory.Wall
ballNode.physicsBody?.contactTestBitMask = PhysicsCategory.Flinger
ballNode.userData = NSMutableDictionary()
ballArray.append(ballNode.ballIndex)
}
I am able to detect the collision, but am unable to retrieve the additional userData that would identify which exact ballNode was struck. When I tried the following code, it only returns an output of "nil".
func didBeginContact(contact: SKPhysicsContact!) {
let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == PhysicsCategory.Flinger | PhysicsCategory.Ball {
println(ballNode.userData)
}
}

I am assuming that PhysicsCategory.Flinger is less than PhysicsCategory.Ball. Then in didContactBegan you can use this code.
func didBeginContact(contact: SKPhysicsContact) {
var body1 : SKPhysicsBody!
var body2 : SKPhysicsBody!
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
body1 = contact.bodyA
body2 = contact.bodyB
}
else {
body1 = contact.bodyB
body2 = contact.bodyA
}
if body1.categoryBitMask == PhysicsCategory.Flinger &&
body2.categoryBitMask == PhysicsCategory.Ball {
if let ballNode = body2.node {
println(ballNode.userData)
}
}
}
The conditions have to be reversed if PhysicsCategory.Flinger is greater than PhysicsCategory.Ball.
if body1.categoryBitMask == PhysicsCategory.Ball &&
body2.categoryBitMask == PhysicsCategory.Flinger {
if let ballNode = body1.node {
println(ballNode.userData)
}
}

Thanks so much for pointing me in the right direction #rakeshbs! Your didBeginContact method works - the problem was I wasn't adding the userData correctly. I resolved this by adding the following line to my didMoveToView function:
ballNode.userData = ["ballNumber" : ballNode.ballIndex]

Related

SpriteKit - Collision Off after HIT

I am writing a spritekit game and I have a problem, I would like the framework not to detect collisions after it is detected. For example, the player is dealt damage and becomes insensitive to attacks, e.g. for 20 seconds. Now when a collision is detected, it is detected all the time (e.g. 50 times)
Is there a way to temporarily disable collisions ??
Player code :
func createPlayer() {
var walkFrames: [SKTexture] = []
var walkFrames2: [SKTexture] = []
var walkFrames3: [SKTexture] = []
var walkFrames4: [SKTexture] = []
// let spaceShipTexture = SKTexture(imageNamed: "p2.png")
let playerAnimatedAtlas = SKTextureAtlas(named: "walk")
let playerAnimatedAtlas_idle = SKTextureAtlas(named: "idle")
let playerAnimatedAtlas_jump = SKTextureAtlas(named: "jump")
let playerAnimatedAtlas_attack = SKTextureAtlas(named: "attack")
var _: [SKTexture] = []
for i in 0...6 {
let playerTextureName = "1_entity_000_WALK_00\(i).png"
walkFrames.append(playerAnimatedAtlas.textureNamed(playerTextureName))
}
for z in 0...6 {
let playerTextureName_idle = "1_entity_000_IDLE_00\(z).png"
walkFrames2.append(playerAnimatedAtlas_idle.textureNamed(playerTextureName_idle))
}
for p in 0...6 {
let playerTextureName_jump = "1_entity_000_JUMP_00\(p).png"
walkFrames3.append(playerAnimatedAtlas_jump.textureNamed(playerTextureName_jump))
}
for k in 0...6 {
let playerTextureName_attack = "1_entity_000_ATTACK_00\(k).png"
walkFrames4.append(playerAnimatedAtlas_attack.textureNamed(playerTextureName_attack))
}
playerWalkingFrames = walkFrames
let firstFrameTexture = playerWalkingFrames[0]
player.position = CGPoint(x: -1050, y: -90 )
//player.setScale(0.090)
player.size = CGSize(width: 170, height: 120)
// player.physicsBody = SKPhysicsBody(rectangleOf:CGSize(width: 35, height: 100))
let spaceShipTexture = SKTexture(imageNamed: "p2.png")
let texturedSpaceShip = SKSpriteNode(texture: spaceShipTexture)
player.physicsBody = SKPhysicsBody(texture: spaceShipTexture , size: CGSize(width: player.size.width,
height: player.size.height));
player.physicsBody?.usesPreciseCollisionDetection = true
//player.physicsBody = SKPhysicsBody(texture: play, size: player.size)
player.physicsBody?.allowsRotation = false
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.collisionBitMask = enemy1Category
//player.physicsBody?.contactTestBitMask = enemy1Category
playerWalkingFrames_idle = walkFrames2
playerWalkingFrames_jump = walkFrames3
playerWalkingFrames_attack = walkFrames4
self.addChild(player)
}
collision code :
func didBegin(_ contact: SKPhysicsContact) {
let collison: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collison == playerCategory | enemy1Category{
print("COLLISION") }
else {
//print("Brak Kolizji" )
}
if collison == playermieczCategory | enemy1Category {
print("miecz")
}
if collison == playermieczCategory | piratkaCategory {
print("miecz_Piratka")
// health -= 0.2
// piratka.isHidden = true
}
if collison == playerCategory | piratkaCategory{
// if collison == nil {return}
player.physicsBody?.contactTestBitMask = 0
// czarny.physicsBody?.applyImpulse(CGVector(dx: 10, dy: 0))
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(5)) {
// self.playerhealth -= 0.01
// print("nico")
print("HIT3")
// }
}

How to correctly set up collisions in spritekite Swift?

I am creating a game in which balls spawn on top and after they fall down and bounce off the ground(this works),I am working on collisions now, my bullets hit the ball and kind of bounces off. If you need any additional code I will post it. (Edited)
import Foundation
import SpriteKit
Did move to view
struct PhysicsCategory {
static let none: UInt32 = 0b0
static let player: UInt32 = 0b1
static let ball: UInt32 = 0b10
static let bullet: UInt32 = 0b100
static let ground: UInt32 = 0b1000
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
let sceneBody = SKPhysicsBody(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
player.position = CGPoint(x: self.size.width/2, y: self.size.height / 8)
var timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(spawnBullet), userInfo: nil, repeats: true)
player.size = CGSize(width: 100, height: 100)
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = true
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
ball.physicsBody?.restitution = 1
ball.physicsBody?.linearDamping = 0
bullet.size = CGSize(width: 30, height: 30)
bullet.zPosition = -5
bullet.position = CGPoint(x: player.position.x, y: player.position.y)
let action = SKAction.moveTo(y: self.size.height + 30, duration: 1.0)
bullet.run(SKAction.repeatForever(action))
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.name = "bullet"
player.physicsBody?.categoryBitMask = PhysicsCategory.player
player.physicsBody?.collisionBitMask = PhysicsCategory.none
player.physicsBody?.contactTestBitMask = PhysicsCategory.ball
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet
bullet.physicsBody?.collisionBitMask = PhysicsCategory.none
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.ball
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bullet
ball.physicsBody?.collisionBitMask = PhysicsCategory.ground
ground.physicsBody?.categoryBitMask = PhysicsCategory.ground
ground.physicsBody?.collisionBitMask = PhysicsCategory.ball
ground.physicsBody?.contactTestBitMask = PhysicsCategory.none
player.name = "player"
addChild(player)
ballSpawner(delay: 2.0)
spawnGround()
}
//didBegin function
func didBegin(_ contact: SKPhysicsContact) {
// print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
I THINK THIS PART IS WRONG
Is my case right? Because when I try to write a bullet and ball it gives me an error - " Binary operator '|' cannot be applied to two 'SKSpriteNode' operands". So only default case is called.
switch contactMask {
//This case never gets called, i can't see it in the console when i run it
case PhysicsCategory.bullet | PhysicsCategory.ball:
let bullet = contact.bodyA.categoryBitMask ==
PhysicsCategory.bullet ? contact.bodyA.node : contact.bodyB.node
let ball = contact.bodyA.categoryBitMask ==
PhysicsCategory.ball ? contact.bodyA.node : contact.bodyB.node
collisionBulletAndBall(ball: (ball)!, bullet: (bullet)!)
print("bullet and ball have contacted.")
case PhysicsCategory.bullet:
print("bullet contact")
case PhysicsCategory.ball & PhysicsCategory.bullet:
print("collision ball with bullet")
default:
print("Some other contact occurred")
}
}
func gameOver() {
print("game over")
}
func collisionBulletAndBall(ball: SKNode, bullet: SKNode) {
ball.removeFromParent()
bullet.removeFromParent()
print("collision")
}
#objc func spawnBullet() {
var bullet = SKSpriteNode(imageNamed: "bullet")
bullet.size = CGSize(width: 30, height: 30)
bullet.zPosition = -5
bullet.position = CGPoint(x: player.position.x, y: player.position.y)
let action = SKAction.moveTo(y: self.size.height + 30, duration: 1.0)
bullet.run(SKAction.repeatForever(action))
bullet.name = "bullet"
self.addChild(bullet)
bullets.append(bullet)
}
func spawnScoreBall() {
var ballSize = CGSize(width: 30, height: 30)
//Random Size
let randomSize = arc4random() % 3
switch randomSize {
case 1:
ballSize.width *= 1.2
ballSize.height *= 1.2
case 2:
ballSize.width *= 1.5
ballSize.height *= 1.5
default:
break
}
var ball = SKSpriteNode(imageNamed: "ball")
ball.size = ballSize
let y = size.height-ballSize.height/2
//Random x
var randomX = CGFloat(arc4random() % UInt32(size.width - ballSize.width))
randomX -= size.width/2-ballSize.width*4
ball.position = CGPoint(x: randomX, y: y)
//Moving logic
// let moveDownAction = SKAction.moveBy(x: 0, y: -size.height + ball.size.height, duration: 2.0)
// let destroyAction = SKAction.removeFromParent()
// let sequenceAction = SKAction.sequence([moveDownAction, destroyAction])
// ball.run(sequenceAction)
//Rotation
var rotateAction = SKAction.rotate(byAngle: 1, duration: 1)
let randomRotation = arc4random() % 2
if randomRotation == 1 {
rotateAction = SKAction.rotate(byAngle: -1, duration: 1)
}
let repeatForeverAction = SKAction.repeatForever(rotateAction)
ball.run(repeatForeverAction)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.restitution = 1
ball.physicsBody?.linearDamping = 0
ball.zPosition = 3
blocks.append(ball)
self.addChild(ball)
}
Spawn ground action
func spawnGround() {
ground = SKSpriteNode(color: UIColor.white, size: CGSize(width: self.frame.size.width, height: 50))
ground.position = CGPoint(x: self.size.width/2, y: 0)
}
player and bullet don't appear to have physics bodies (there is no player.physicsBody = SKPhysicsBody... and bullet.physicsBody = SKPhysicsBody...
ball's physicsbody is assigned way after you assign the physics body's attributes:
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bullet | PhysicsCategory.ground | PhysicsCategory.player
ball.physicsBody?.collisionBitMask = PhysicsCategory.bullet | PhysicsCategory.ground | PhysicsCategory.player
and then later
ball.physicsBody = SKPhysicsBody...
so all those ball.physicsBody assignments did nothing, as physicsBody? was 'nil'
Edit : Your didBegin looks a bit messy. Try this:
func didBegin(_ contact: SKPhysicsContact) {
print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case PhysicsCategory.bullet | PhysicsCategory.ball:
let bullet = contact.bodyA.categoryBitMask == bullet ? contact.bodyA.node : contact.bodyB.node
let ball = contact.bodyA.categoryBitMask == ball ? contact.bodyA.node : contact.bodyB.node
ball.removeFromParent()
bullet.removeFromParent()
print("bullet and ball have contacted.")
default:
print("Some other contact occurred")
}
I removed the setting of the ball's physics body's isDynamic and affectedByGravity properties as you are removing it.

Node not being removed from parent (spritekit)

Create enemy
touchesBegan and didBegin contact function
My enemy node is not being removed from the scene every time my sword node touches it. I'm just wondering if anyone could explain to me what I'm doing wrong?
(UPDATE BELOW)
import SpriteKit
import GameplayKit
import AVFoundation
class LevelTwo: SKScene, SKPhysicsContactDelegate{
var levelBg = SKSpriteNode(imageNamed: "level2")
var hero = SKSpriteNode()
var enemy = SKSpriteNode()
var sword = SKSpriteNode()
var health1 = SKSpriteNode(imageNamed: "playerhplv2")
var health2 = SKSpriteNode(imageNamed: "playerhplv2")
var health3 = SKSpriteNode(imageNamed: "playerhplv2")
var musicPath = URL(fileURLWithPath: Bundle.main.path(forResource: "gameMusic", ofType: "mp3")!)
var musicGamePlayer = AVAudioPlayer()
var runMonster = SKAction()
var waitMonster = SKAction()
var sequenceMonster = SKAction()
var repeatMonster = SKAction()
enum CollisionNum: UInt32{
case swordNum = 1
case enemyNum = 2
case playerNum = 4
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
///music
do{
musicGamePlayer = try AVAudioPlayer(contentsOf: musicPath)
musicGamePlayer.prepareToPlay()
musicGamePlayer.numberOfLoops = -1
musicGamePlayer.play()
}
catch{
print(error)
}
//bg
levelBg.position = CGPoint(x: 0, y: 0)
levelBg.zPosition = 1
levelBg.size = levelBg.texture!.size()
levelBg.setScale(1.25)
self.addChild(levelBg)
//hero
let playerTexture = SKTexture(imageNamed: "main")
hero = SKSpriteNode(texture: playerTexture)
hero.position = CGPoint(x: 0, y: 0)
hero.zPosition = 2
hero.setScale(0.6)
hero.physicsBody = SKPhysicsBody(texture: playerTexture, size: CGSize(width: hero.size.width, height: hero.size.height))
hero.physicsBody!.categoryBitMask = CollisionNum.playerNum.rawValue
hero.physicsBody!.collisionBitMask = CollisionNum.enemyNum.rawValue //player is allowed to bump into rocks and skulls
hero.physicsBody!.contactTestBitMask = CollisionNum.enemyNum.rawValue // same as collisions
hero.physicsBody!.isDynamic = false
self.addChild(hero)
//health1
health1.position = CGPoint(x: 130, y: 150)
health1.zPosition = 3
health1.setScale(0.75)
self.addChild(health1)
//health2
health2.position = CGPoint(x: 230, y: 150)
health2.zPosition = 3
health2.setScale(0.75)
self.addChild(health2)
//health3
health3.position = CGPoint(x: 320, y: 150)
health3.zPosition = 3
health3.setScale(0.75)
self.addChild(health3)
runMonster = SKAction.run(addMonster)
waitMonster = SKAction.wait(forDuration: 0.3)
sequenceMonster = SKAction.sequence([runMonster,waitMonster])
repeatMonster = SKAction.repeatForever(sequenceMonster)
run(repeatMonster)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locale = touch.location(in: self)
hero.position.x = locale.x
hero.position.y = locale.y
}
}
func addMonster(){
//random position based off the bg size
let monsterHigherX = Int(levelBg.size.width)
let monsterHigherY = Int(levelBg.size.height)
let monsterLowerX = monsterHigherX * -1
let monsterLowerY = monsterHigherY * -1
let randomLocaleX = Int(arc4random_uniform(UInt32(monsterHigherX - monsterLowerX))) + monsterLowerX
let randomLocaleY = Int(arc4random_uniform(UInt32(monsterHigherY - monsterLowerY))) + monsterLowerY
let movementEnemy = SKAction.moveBy(x: -5, y: -5, duration: 0.2)
let movementForever = SKAction.repeatForever(movementEnemy)
let enemyTexture = SKTexture(imageNamed: "boss0")
enemy = SKSpriteNode(texture: enemyTexture)
enemy.zPosition = 2
enemy.setScale(0.5)
enemy.position = CGPoint(x: randomLocaleX, y: randomLocaleY)
enemy.physicsBody = SKPhysicsBody(texture: enemyTexture, size: CGSize(width: enemy.size.width, height: enemy.size.height))
enemy.physicsBody!.isDynamic = true
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.categoryBitMask = CollisionNum.enemyNum.rawValue
enemy.physicsBody!.collisionBitMask = CollisionNum.swordNum.rawValue
enemy.physicsBody!.contactTestBitMask = CollisionNum.swordNum.rawValue
enemy.run(movementForever)
self.addChild(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let swordTexture = SKTexture(imageNamed: "blade-0")
sword = SKSpriteNode(texture: swordTexture)
sword.setScale(0.50)
sword.zPosition = 2
sword.position = hero.position
sword.physicsBody = SKPhysicsBody(texture: swordTexture, size: CGSize(width: sword.size.width, height: sword.size.height))
sword.physicsBody!.velocity = CGVector(dx: 1200, dy:0)
sword.physicsBody!.isDynamic = true
sword.physicsBody!.affectedByGravity = true
sword.physicsBody!.usesPreciseCollisionDetection = true
sword.physicsBody!.categoryBitMask = CollisionNum.swordNum.rawValue
sword.physicsBody!.collisionBitMask = CollisionNum.enemyNum.rawValue
sword.physicsBody!.contactTestBitMask = CollisionNum.enemyNum.rawValue
self.addChild(sword)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == CollisionNum.swordNum.rawValue | CollisionNum.enemyNum.rawValue {
enemy.removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
}
}
(Ive attached my whole level2 class) Thank you so much for the suggestion; however, when I tried implementing this I still run into the same problem (im running this on the iphone simulator) Im wondering whether the error is with my enum or my implementation of my physics with my nodes
You do not want to remove "enemy" because "enemy" is always the last monster you added. You need to check which contactBody is the enemy so you can remove it. You can do that by guaranteeing which node you want to associate as A, and which you want to associate as B by looking at the categoryBitMask value:
func didBegin(_ contact: SKPhysicsContact) {
//This guarantees the lower categoryBitMask (Providing you are only using one) is in A
let bodyA = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
if bodyA.categoryBitMask == CollisionNum.swordNum.rawValue && bodyB.categoryBitMask == CollisionNum.enemyNum.rawValue {
bodyB.node.removeFromParent()
}
}
Of course this will lead to problems with multiple collisions, so instead you may want to do:
var removeNodes = SKNode()
func didBegin(_ contact: SKPhysicsContact) {
//This guarantees the lower categoryBitMask (Providing you are only using one) is in A
let bodyA = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
if bodyA.categoryBitMask == CollisionNum.swordNum.rawValue && bodyB.categoryBitMask == CollisionNum.enemyNum.rawValue {
bodyB.node.moveToParent(removeNodes)
}
}
func didFinishUpdate(){
removeNodes.removeAllChildren()
}
Try this:
func didBegin(_ contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == CollisionNum.swordNum.rawValue | CollisionNum.enemyNum.rawValue {
enemy.removeFromParent()
}
}
You were only testing if bodyA is equal to the enemy, however, bodyA may be equal to the sword instead.

Not detecting collision

I'm getting some difficulty detecting collision in my SpriteKit Game. I simply want to detect collision between the missiles and the enemies and boats.
I have the ColliderType:
struct ColliderType {
static let Boat: UInt32 = 1
static let Enemy: UInt32 = 2
static let Wall: UInt32 = 3
static let Bullet: UInt32 = 4
}
class GameplayScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode()
var enemy = SKSpriteNode()
var boat = SKSpriteNode()
var missile = SKSpriteNode()
The didBegin contact:
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Missile" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Missile" && secondBody.node?.name ==
"Enemy" {
incrementScore()
secondBody.node?.removeFromParent()
} else if firstBody.node?.name == "Missile" &&
secondBody.node?.name == "Boat" {
incrementScore()
}
}
I have added the "physicsWorld.contactDelegate = self" in the didMove toview
I have also added physicsBodies to all the relevant spritenodes:
func fireMissile() {
let missile = SKSpriteNode(color: .yellow, size: CGSize(width: 20,
height: 5))
missile.name = "Missile"
missile.position = CGPoint(x: player.position.x + 28, y:
player.position.y + 10)
missile.zPosition = 2
missile.physicsBody = SKPhysicsBody(rectangleOf: missile.size)
missile.physicsBody?.isDynamic = false
missile.physicsBody?.categoryBitMask = ColliderType.Bullet
missile.physicsBody?.collisionBitMask = ColliderType.Enemy |
ColliderType.Boat
missile.physicsBody?.contactTestBitMask = ColliderType.Enemy |
ColliderType.Boat
self.addChild(missile)
}
func createEnemies() {
let enemy = SKSpriteNode(imageNamed: "Enemy1")
enemy.name = "Enemy"
enemy.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.height
/ 2)
enemy.physicsBody?.categoryBitMask = ColliderType.Enemy
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = false
enemy.zPosition = 3
enemy.position.y = self.frame.height + 100
enemy.position.x = CGFloat.randomBetweenNumbers(firstNum: -347.5,
secondNum: -85)
self.addChild(enemy)
}
func createBoat() {
let boat = SKSpriteNode(imageNamed: "Boat")
boat.name = "Boat"
boat.anchorPoint = CGPoint(x: 0.5, y: 0.5)
boat.physicsBody = SKPhysicsBody(circleOfRadius: boat.size.height /
2)
boat.physicsBody?.categoryBitMask = ColliderType.Boat
boat.physicsBody?.affectedByGravity = false
boat.physicsBody?.isDynamic = false
boat.zPosition = 3
boat.position.y = self.frame.height + 100
boat.position.x = CGFloat.randomBetweenNumbers(firstNum: 0,
secondNum: 0)
self.addChild(boat)
}
Firstly, some of your categories are wrong:
static let Wall: UInt32 = 3
static let Bullet: UInt32 = 4
This effectively defines Wall as being both a Boat and an Enemy. Change them to:
static let Wall: UInt32 = 4
static let Bullet: UInt32 = 8
(Categories should always be unique powers of 2 - 1, 2, 4, 8, 16 etc).
The rest looks ok, so try that and let us know if it’s working.
Edit:
OK - just noticed that all of your physics bodies have their isDynamic property set to false - this means that, among other things, the body will not trigger contacts. so if you want missile to generate contacts with either enemy or boat, then either missile should be dynamic or both enemy and boat should be dynamic (only 1 of the 2 objects involved in a contact needs to by dynamic).

SpriteKit won't recognise contact

bellow is my code which for some reason won't recognise contact between two sprites, in this case, ninja and left or right wall. So my question is what am I missing? I add collision categories and assigned them accordingly
func didBegin(_ contact: SKPhysicsContact) {
let nodeB = contact.bodyB.node!
if nodeB.name == "RIGHT_WALL" {
print("right wall touched")
RightWallTouched = true
self.CurrentScore = self.CurrentScore + 1
}
if nodeB.name == "LEFT_WALL" {
print("left wall touched")
RightWallTouched = false
self.CurrentScore = self.CurrentScore + 1
}
self.CurrentScoreLabel.text = "\(self.CurrentScore)"
if nodeB.name == "SPIKE" {
self.GameStarted = false
if(CurrentScore > HighScore){
defaults.set(CurrentScore, forKey: self.HighScoreStorageKey)
defaults.synchronize()
self.HighScoreLabel.text = "\(self.defaults.integer(forKey: self.HighScoreStorageKey))"
}
endGame()
}
}
func CreateNinja(){
let ninjaTexture = SKTexture(imageNamed: "ninja-right.png")
ninjaTexture.filteringMode = .nearest
self.ninja = SKSpriteNode(texture: ninjaTexture)
self.ninja!.physicsBody = SKPhysicsBody(texture: ninjaTexture, size: ninja!.size)
self.ninja?.name = "NINJA"
self.ninja!.physicsBody?.isDynamic = false
self.ninja!.physicsBody?.allowsRotation = false
self.ninja!.physicsBody?.restitution = 0.6
self.ninja!.physicsBody?.mass = 0.430 // m = 430g
self.ninja!.position = CGPoint(x: self.frame.size.width / 2 , y: self.frame.size.height/5)
self.ninja!.physicsBody?.categoryBitMask = ninjaCollisionCategory
self.ninja!.physicsBody?.contactTestBitMask = 0
self.ninja!.physicsBody?.collisionBitMask = wallCollisionCategory
self.addChild(self.ninja!)
}
func CreateWall(){
let topwall = SKSpriteNode()
topwall.name = "TOP_WALL"
topwall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.frame.size.width, height: 5))
topwall.physicsBody?.categoryBitMask = wallCollisionCategory
topwall.physicsBody?.isDynamic = false
topwall.physicsBody?.restitution = 0.6
topwall.position = CGPoint(x: self.frame.size.width / 2.0 , y: self.frame.size.height)
topwall.physicsBody?.friction = 0
topwall.zPosition = 4
self.addChild(topwall)
let leftwall = SKSpriteNode(color: UIColor.black, size: CGSize(width: 20, height: self.frame.size.height))
leftwall.name = "LEFT_WALL"
leftwall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: self.frame.size.height))
leftwall.physicsBody?.categoryBitMask = wallCollisionCategory
leftwall.physicsBody?.contactTestBitMask = 0
leftwall.physicsBody?.isDynamic = false
leftwall.position = CGPoint(x: 0 , y: self.frame.size.height / 2.0)
leftwall.zPosition = 4
leftwall.physicsBody?.restitution = 1.0
leftwall.physicsBody?.friction = 0
self.addChild(leftwall)
let rightwall = SKSpriteNode(color: UIColor.black, size: CGSize(width: 20, height: self.frame.size.height))
rightwall.name = "RIGHT_WALL"
rightwall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: self.frame.size.height))
rightwall.physicsBody?.categoryBitMask = wallCollisionCategory
rightwall.physicsBody?.contactTestBitMask = 0
rightwall.physicsBody?.isDynamic = false
rightwall.physicsBody?.restitution = 1.0
rightwall.physicsBody?.friction = 0
rightwall.position = CGPoint(x: self.frame.size.width , y: self.frame.size.height / 2.0)
rightwall.zPosition = 4
self.addChild(rightwall)
}
A more common way to do the collision function would be something like this
func didBegin(_ contact: SKPhysicsContact) {
let firstBody: SKPhysicsBody
let secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
// Ninja hit wall or wall hit ninja
if (firstBody.categoryBitMask == ninjaCollisionCategory) && (secondBody.categoryBitMask == wallCollisionCategory) {
// Do something for all walls
...
// you can also check the name for some specific wall e.g.
if secondBody.node?.name == "RIGHT_WALL" {
// hit right wall
}
}
}
You also need to set the delegate in didMoveToView
physicsWorld.contactDelegate = self
and should probably set your ninja sprite physics body to dynamic
ninja?.physicsBody?.isDynamic = true // You can also remove this line as its set to true by default
Finally, as good practice, try to follow the Swift guidelines, your functions and properties should start with small letters not capital letters e.g
var currentScore = 0
func createNinja() { ... }
Hope this helps