Basic Swift SpriteKit Collisions using PhysicsBodys - swift

The problem: I seem to be having a little trouble getting my player to collide with a coin, and adding +1 to a coinLabel upon the collision. The player should continue moving after coming in contact with the coin.
What I have now: With the code I have now, the player travels through the coin, but there is no collision that takes place and +1 isn't added to the coin label.
I am still learning the swift language, so I appreciate any help that is given.
Code:
struct ColliderType {
static let playerCategory: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
​
​static let coinCategory: UInt32 = 0x1 << 2
​
​static let bodyA: UInt32 = 0x1 << 4
​
​static let bodyB: UInt32 = 0x1 << 8
}
​override func didMoveToView(view: SKView) {
​
var coinInt = 0
​
​
​
​self.physicsWorld.gravity = CGVectorMake(0.0, -7.0)
physicsWorld.contactDelegate = self
player = SKSpriteNode(imageNamed: "player")
player.zPosition = 1
player.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 5.12)
player.physicsBody?.dynamic = true
player.physicsBody?.allowsRotation = false
self.addChild(player)
generateCoins()
​
​
coin = SKSpriteNode( imageNamed: "coin")
coin.physicsBody? = SKPhysicsBody(circleOfRadius: coin.size.height / 10)
coin.physicsBody?.dynamic = false
coin.physicsBody?.allowsRotation = false
coin.zPosition = 1
​self.addChild(coin)
​
player.physicsBody?.categoryBitMask = ColliderType.playerCategory
​player.physicsBody?.contactTestBitMask = ColliderType.boundary
player.physicsBody?.collisionBitMask = ColliderType.coinCategory | ColliderType.boundary
coin.physicsBody?.categoryBitMask = ColliderType.coinCategory
coin.physicsBody?.contactTestBitMask = ColliderType.playerCategory
coin.physicsBody?.collisionBitMask = ColliderType.playerCategory
func didPlayerCollideWithCoin(player: SKSpriteNode, coin: SKSpriteNode) {
self.coin.removeFromParent()
self.coin += 1
coinLabel.text = "\(coinInt)"
}
​
​
​
​func generateCoins() {
if(self.actionForKey("spawning") != nil){return}
let coinTimer = SKAction.waitForDuration(7, withRange: 2)
let spawnCoin = SKAction.runBlock {
self.coin = SKSpriteNode( imageNamed: "coin")
self.coin.physicsBody = SKPhysicsBody(circleOfRadius: self.coin.size.height / 10)
self.coin.name = "coin"
self.coin.physicsBody?.dynamic = false
self.coin.physicsBody?.allowsRotation = false
var coinPosition = Array<CGPoint>()
coinPosition.append((CGPoint(x:340, y:103)))
coinPosition.append((CGPoint(x:340, y:148)))
coinPosition.append((CGPoint(x:340, y:218)))
coinPosition.append((CGPoint(x: 340, y:343)))
let spawnLocation = coinPosition[Int(arc4random_uniform(UInt32(coinPosition.count)))]
let action = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 4.4))
self.coin.runAction(action)
self.coin.position = spawnLocation
self.addChild(self.coin)
print(spawnLocation)
}
let sequence = SKAction.sequence([coinTimer, spawnCoin])
self.runAction(SKAction.repeatActionForever(sequence), withKey: "spawning")
}
​​
func didBeginContact(contact:SKPhysicsContact) {
let bodyA: SKPhysicsBody = contact.bodyA
let bodyB: SKPhysicsBody = contact.bodyB
if ((bodyA.categoryBitMask == ColliderType.playerCategory) && (bodyB.categoryBitMask == ColliderType.coinCategory)){
didPlayerCollideWithCoin(bodyA.node as! SKSpriteNode, coin: bodyB.node as! SKSpriteNode)
}
​
​}

You could try leaving your contactTestBitmasks the same but remove the collisionBitmasks between the player and coin:
player.physicsBody?.categoryBitMask = ColliderType.playerCategory
​player.physicsBody?.contactTestBitMask = ColliderType.boundary
player.physicsBody?.collisionBitMask = ColliderType.boundary
coin.physicsBody?.categoryBitMask = ColliderType.coinCategory
coin.physicsBody?.contactTestBitMask = ColliderType.playerCategory
This way, when the player collides with a coin it will register, but it wont "bounce off" and will continue moving in the same direction.
*Note: Using this MAY require you to use the
func didBeginContact(contact: SKPhysicsContact) {
method instead of
func didPlayerCollideWithCoin(player: SKSpriteNode, coin: SKSpriteNode) {
But I recommend trying it with the didPlayerCollideWithCoin() method first.
In case you need it, it is implemented like this:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == playerCategory && secondBody.categoryBitMask == coinCategory {
print("Your player passes through the coin")
score = score + 1
}
}
For more details see this tutorial from Ray Wenderlich:
https://www.raywenderlich.com/123393/how-to-create-a-breakout-game-with-sprite-kit-and-swift

Related

Swift programming. Possible problem with SKPhysicscontact

I'm working on a project / game and I'm stuck at adding new enemies to the game and to program them. This is how my code looked before I started changing and only had one type of "alien". I want to create two new types, one that you lose 5 points from hitting and one 10. In the code now, there is only one of three and it gives +5 points. How would you guys add the two new? I think I'm overthinking it and that's why I messed up.
import SpriteKit
import GameplayKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate {
var starfield:SKEmitterNode!
var player:SKSpriteNode!
var scoreLabel:SKLabelNode!
var score:Int = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
var gameTimer:Timer!
var possibleAliens = ["alien"]
//This is the "alien you earn +5 point from shooting. Im trying to get 2 new aliens to the scene, one that you lose -5 points from shooting and one you lose -10 on.
//I have tried to rewrite the codes to the two new, but they still earn +5 points from shooting all 3 types. What would you guys do and how?
let alienCategory:UInt32 = 0x1 << 1
let photonTorpedoCategory:UInt32 = 0x1 << 0
let motionManger = CMMotionManager()
var xAcceleration:CGFloat = 0
override func didMove(to view: SKView) {
starfield = SKEmitterNode(fileNamed: "Starfield")
starfield.position = CGPoint(x: 0, y: 1472)
starfield.advanceSimulationTime(10)
self.addChild(starfield)
starfield.zPosition = -1
player = SKSpriteNode(imageNamed: "shuttle")
player.position = CGPoint(x: self.frame.size.width / 2, y: player.size.height / 2 + 20)
self.addChild(player)
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
scoreLabel = SKLabelNode(text: "Score: 0")
scoreLabel.position = CGPoint(x: 100, y: self.frame.size.height - 60)
scoreLabel.fontName = "AmericanTypewriter-Bold"
scoreLabel.fontSize = 36
scoreLabel.fontColor = UIColor.white
score = 0
self.addChild(scoreLabel)
gameTimer = Timer.scheduledTimer(timeInterval: 0.75, target: self, selector: #selector(addAlien), userInfo: nil, repeats: true)
motionManger.accelerometerUpdateInterval = 0.2
motionManger.startAccelerometerUpdates(to: OperationQueue.current!) { (data:CMAccelerometerData?, error:Error?) in
if let accelerometerData = data {
let acceleration = accelerometerData.acceleration
self.xAcceleration = CGFloat(acceleration.x) * 0.75 + self.xAcceleration * 0.25
}
}
}
func addAlien () {
possibleAliens = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: possibleAliens) as! [String]
let alien = SKSpriteNode(imageNamed: possibleAliens[0])
let randomAlienPosition = GKRandomDistribution(lowestValue: 0, highestValue: 414)
let position = CGFloat(randomAlienPosition.nextInt())
alien.position = CGPoint(x: position, y: self.frame.size.height + alien.size.height)
alien.physicsBody = SKPhysicsBody(rectangleOf: alien.size)
alien.physicsBody?.isDynamic = true
alien.physicsBody?.categoryBitMask = alienCategory
alien.physicsBody?.contactTestBitMask = photonTorpedoCategory
alien.physicsBody?.collisionBitMask = 0
self.addChild(alien)
let animationDuration:TimeInterval = 6
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: position, y: -alien.size.height), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
alien.run(SKAction.sequence(actionArray))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
fireTorpedo()
}
func fireTorpedo() {
self.run(SKAction.playSoundFileNamed("torpedo.mp3", waitForCompletion: false))
let torpedoNode = SKSpriteNode(imageNamed: "torpedo")
torpedoNode.position = player.position
torpedoNode.position.y += 5
torpedoNode.physicsBody = SKPhysicsBody(circleOfRadius: torpedoNode.size.width / 2)
torpedoNode.physicsBody?.isDynamic = true
torpedoNode.physicsBody?.categoryBitMask = photonTorpedoCategory
torpedoNode.physicsBody?.contactTestBitMask = alienCategory
torpedoNode.physicsBody?.collisionBitMask = 0
torpedoNode.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(torpedoNode)
let animationDuration:TimeInterval = 0.3
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: player.position.x, y: self.frame.size.height + 10), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
torpedoNode.run(SKAction.sequence(actionArray))
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & photonTorpedoCategory) != 0 && (secondBody.categoryBitMask & alienCategory) != 0 {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
}
}
func torpedoDidCollideWithAlien (torpedoNode:SKSpriteNode, alienNode:SKSpriteNode) {
let explosion = SKEmitterNode(fileNamed: "Explosion")!
explosion.position = alienNode.position
self.addChild(explosion)
self.run(SKAction.playSoundFileNamed("explosion.mp3", waitForCompletion: false))
torpedoNode.removeFromParent()
alienNode.removeFromParent()
self.run(SKAction.wait(forDuration: 2)) {
explosion.removeFromParent()
}
score += 5
}
override func didSimulatePhysics() {
player.position.x += xAcceleration * 50
if player.position.x < -20 {
player.position = CGPoint(x: self.size.width + 20, y: player.position.y)
}else if player.position.x > self.size.width + 20 {
player.position = CGPoint(x: -20, y: player.position.y)
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
I wouldn't recommend following this answer if your game becomes more complex. Normally I would say put the aliens in subclasses initialized with their own categorybitmasks and other properties, but to avoid modifying your code too much I just changed the addAlien() method and didBeginContact method. Each alien has its own categoryBitMask and textureName.
let alienCategories:[UInt32] = [(0x1 << 1),(0x1 << 2),(0x1 << 3)]
let alienTextureNames:[String] = ["alien1","alien2","alien3"]
This will make a random Alien using the values in the collections above
func addAlien () {
//Random index between 0 and 2 changing the texture and category bitmask
let index = Int.random(in: 0...2)
let textureName = alienTextureNames[index]
let alien = SKSpriteNode(imageNamed: textureName)
//Use Int.random() if you don't want a CGFLoat
let xPos = CGFloat.random(in: 0...414)
let yPos = frame.size.height + alien.size.height
alien.position = CGPoint(x: xPos, y: yPos)
alien.physicsBody = SKPhysicsBody(rectangleOf: alien.size)
alien.physicsBody?.isDynamic = true
alien.physicsBody?.categoryBitMask = alienCategories[index]
alien.physicsBody?.contactTestBitMask = photonTorpedoCategory
alien.physicsBody?.collisionBitMask = 0
self.addChild(alien)
let moveAction = SKAction.moveTo(y: -alien.size.height, duration: 6)
alien.run(moveAction, completion: { alien.removeFromParent() })
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == photonTorpedoCategory && secondBody.categoryBitMask == alienCategories[0] {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
score += 5
}else if firstBody.categoryBitMask == photonTorpedoCategory && secondBody.categoryBitMask == alienCategories[1] {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
score -= 5
}else if firstBody.categoryBitMask == photonTorpedoCategory && secondBody.categoryBitMask == alienCategories[2] {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
score -= 10
}
}
Change the torpedoNode.physicsBody?.contactTestBitMask = alienCategory line in the fireTorpedo method to alien.physicsBody?.contactTestBitMask = (alienCategories[0] | alienCategories[1] | alienCategories[2])

SpriteKit Particle File position

I'm having problem with the positioning of the explosion particle in my SpriteKit game. When the missiles collide with the enemies I call the "func explosion", but the explosion is not where the killed enemy is, but rather in the middle of the screen. I have set the "explosion.position to match with the "enemy.position" but still the explosion happens in the middle of the screen. How can I change this so that the explosion position is the same as the position of the killed enemy?
func spawnExplosion() {
let explosionEmmiter = Bundle.main.path(forResource:
"ParticleExplosion", ofType: "sks")
let explosion = NSKeyedUnarchiver.unarchiveObject(withFile:
explosionEmmiter as! String) as! SKEmitterNode
explosion.position = CGPoint(x: enemy.position.x, y:
enemy.position.y)
explosion.zPosition = 3
explosion.targetNode = self
self.addChild(explosion)
let wait = SKAction.wait(forDuration: 0.5)
let removeExplosion = SKAction.run {
explosion.removeFromParent()
}
self.run(SKAction.sequence([wait, removeExplosion]))
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Enemy" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Enemy" && secondBody.node?.name ==
"Missile" {
spawnExplosion()
}
func createEnemiesLeft() {
let enemyTexture1 = SKTexture(imageNamed: "Enemy1")
let enemyTexture2 = SKTexture(imageNamed: "Enemy2")
let enemyTexture3 = SKTexture(imageNamed: "Enemy3")
let enemyTexture4 = SKTexture(imageNamed: "Enemy4")
let enemyTexture5 = SKTexture(imageNamed: "Enemy5")
let enemyTexture6 = SKTexture(imageNamed: "Enemy6")
let enemyTexture7 = SKTexture(imageNamed: "Enemy7")
let enemyTexture8 = SKTexture(imageNamed: "Enemy8")
let enemyTexture9 = SKTexture(imageNamed: "Enemy9")
let enemyTexture10 = SKTexture(imageNamed: "Enemy10")
let animation = SKAction.animate(with: [enemyTexture1,
enemyTexture2, enemyTexture3, enemyTexture4, enemyTexture5,
enemyTexture6, enemyTexture7, enemyTexture8, enemyTexture9,
enemyTexture10], timePerFrame: 0.1)
let makeEnemyRun = SKAction.repeatForever(animation)
let enemy = SKSpriteNode(texture: enemyTexture1)
enemy.name = "Enemy"
enemy.setScale(0.1)
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.zPosition = 3
enemy.position.y = self.frame.height + 100
enemy.position.x = CGFloat.randomBetweenNumbers(firstNum: -347.5,
secondNum: -85)
enemy.run(makeEnemyRun)
self.addChild(enemy)
let destination = self.frame.height * 0.5
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
enemy.run(SKAction.sequence([move, remove]), withKey:
"MoveEnemiesLeft")
}
func spawnEnemiesLeft() {
let spawn = SKAction.run({ () -> Void in
self.createEnemiesLeft()
})
let delay = SKAction.wait(forDuration: TimeInterval(1))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey:
"SpawnEnemiesLeft")
}
func createEnemiesRight() {
let enemyTexture1 = SKTexture(imageNamed: "Enemy1")
let enemyTexture2 = SKTexture(imageNamed: "Enemy2")
let enemyTexture3 = SKTexture(imageNamed: "Enemy3")
let enemyTexture4 = SKTexture(imageNamed: "Enemy4")
let enemyTexture5 = SKTexture(imageNamed: "Enemy5")
let enemyTexture6 = SKTexture(imageNamed: "Enemy6")
let enemyTexture7 = SKTexture(imageNamed: "Enemy7")
let enemyTexture8 = SKTexture(imageNamed: "Enemy8")
let enemyTexture9 = SKTexture(imageNamed: "Enemy9")
let enemyTexture10 = SKTexture(imageNamed: "Enemy10")
let animation = SKAction.animate(with: [enemyTexture1,
enemyTexture2, enemyTexture3, enemyTexture4, enemyTexture5,
enemyTexture6, enemyTexture7, enemyTexture8, enemyTexture9,
enemyTexture10], timePerFrame: 0.1)
let makeEnemyRun = SKAction.repeatForever(animation)
let enemy = SKSpriteNode(texture: enemyTexture1)
enemy.name = "Enemy"
enemy.setScale(0.1)
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.zPosition = 3
enemy.position.y = self.frame.height + 100
enemy.position.x = CGFloat.randomBetweenNumbers(firstNum: 85,
secondNum: 347.5)
enemy.run(makeEnemyRun)
self.addChild(enemy)
let destination = self.frame.height * 0.5
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
enemy.run(SKAction.sequence([move, remove]), withKey:
"MoveEnemiesRight")
}
func spawnEnemiesRight() {
let spawn = SKAction.run({ () -> Void in
self.createEnemiesRight()
})
let delay = SKAction.wait(forDuration: TimeInterval(1))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey:
"SpawnEnemiesRight")
}
}
Ok, you are creating a lot of enemy and you must get the collision position. so:
func spawnExplosion(pos:CGPoint) {
let explosionEmmiter = Bundle.main.path(forResource:
"ParticleExplosion", ofType: "sks")
let explosion = NSKeyedUnarchiver.unarchiveObject(withFile:
explosionEmmiter as! String) as! SKEmitterNode
explosion.position = pos
explosion.zPosition = 3
explosion.targetNode = self
self.addChild(explosion)
let wait = SKAction.wait(forDuration: 0.5)
let removeExplosion = SKAction.run {
explosion.removeFromParent()
}
self.run(SKAction.sequence([wait, removeExplosion]))
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Enemy" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Enemy" && secondBody.node?.name ==
"Missile" {
spawnExplosion(pos: firstBody.node.position)
}

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).

How to detect contact regarding sprite texture

I have a bullet that should be fired at block. Bullet has 6 different random textures to mimic different bullets. And block has 3 different textures chosen randomly to look like there are 3 different blocks. I want to specify in code that if bullet texture is red, and block texture is red then score should increase, but if bullet is red and block is green it will be the game over. I don't really know how to tell game to do so in didBeginContact.
By now I have this:
In the GameScene & didMoveToView:
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let CgyBlock : UInt32 = 0b1
static let Bullet : UInt32 = 0b10
}
bullet.physicsBody = SKPhysicsBody(texture: bullet.texture, size: self.bullet.size)
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Bullet
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.CgyBlock
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.usesPreciseCollisionDetection = true
In didBeginContact:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & PhysicsCategory.CgyBlock != 0) &&
(secondBody.categoryBitMask & PhysicsCategory.Bullet != 0))
//and here I suppose I need to implement somehow something like
// && (bullet.texture = "redBullet") && (CgyBlock.texture = "greenBlock" || "blackBlock")
{
gameOver()
}
But I know it will not work. I also tried to make a switch statement inside curly brackets and it didn't work either. How to implement that?
Update: This is how a block is made:
var cgyBlock = SKSpriteNode()
let cgyArray = ["cyanBox", "greenBox", "yellowBox"]
func addCgyLine () {
cgyBlock = SKSpriteNode(imageNamed: "cyanBox")
var randomCGY = Int(arc4random_uniform(3))
cgyBlock.texture = SKTexture(imageNamed: cgyArray[randomCGY])
cgyBlock.physicsBody = SKPhysicsBody(texture: cgyBlock.texture, size: cgyBlock.size)
cgyBlock.physicsBody?.dynamic = true
cgyBlock.physicsBody?.categoryBitMask = PhysicsCategory.CgyBlock
cgyBlock.physicsBody?.contactTestBitMask = PhysicsCategory.Bullet
cgyBlock.physicsBody?.collisionBitMask = PhysicsCategory.None
cgyBlock.position = CGPointMake(size.width + cgyBlock.size.width/2, CGRectGetMidY(self.frame) + 60)
addChild(cgyBlock)
let actionMove = SKAction.moveTo(CGPoint(x: -cgyBlock.size.width/2, y: CGRectGetMidY(self.frame) + 60), duration: 3)
let actionDone = SKAction.removeFromParent()
cgyBlock.runAction(SKAction.sequence([actionMove, actionDone]))
SKActionTimingMode.EaseOut
}
And then I do runAction in didMoveToView.
Bullets:
var cannon = SKSpriteNode(imageNamed: "cannon")
var bulletInCannon = SKSpriteNode()
var bullet = SKSpriteNode()
let bulletArray = ["redBullet","magentaBullet", "blueBullet", "cyanBullet", "greenBullet", "yellowBullet"]
//didMoveToView:
var randomBullet = Int(arc4random_uniform(6))
bulletInCannon = SKSpriteNode(imageNamed: bulletArray[randomBullet])
bulletInCannon.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
addChild(bulletInCannon)
//touchesEnded:
var randomBullet = Int(arc4random_uniform(6))
bullet = SKSpriteNode(texture: bulletInCannon.texture)
bullet.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
bullet.name = bulletArray[randomBullet]
bullet.physicsBody = SKPhysicsBody(texture: bullet.texture, size: self.bullet.size)
bullet.physicsBody?.dynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Bullet
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.CgyBlock
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.usesPreciseCollisionDetection = true
addChild(bullet)
bulletInCannon.texture = SKTexture(imageNamed: bulletArray[randomBullet])
Few ways to go :
You can use node's userData property.
bullet.userData = ["type" : "white"]
To access it:
println(bullet.userData?["type"])
You can create custom Bullet class which is subclass of SKSpriteNode and create property called "type", and in didBeginContact to access that property.
class Bullet: SKSpriteNode {
var type:String = ""
init(type:String) {
self.type = type //later you are accessing this with bulletNode.type
//This is just an simple example to give you a basic idea what you can do.
//In real app you should probably implement some kind of security check to avoid wrong type
let texture = SKTexture(imageNamed: type)
super.init(texture: texture, color: nil, size: texture.size())
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can use bullet.name property as well, and on creation to set it appropriately based on bullet/block colour. Later in didBeginContact you will check bullet.name to find out the bullet type. Same goes for blocks.
func spawnBulletWithType(type:String) -> SKSpriteNode{
//set texture based on type
//you can pass here something like white_bullet
let atlas = SKTextureAtlas(named: "myAtlas")
//here, if passed white_bullet string, SpriteKit will search for texture called white_bullet.png
let bullet = SKSpriteNode(texture:atlas.textureNamed(type))
bullet.name = type // name will be white_bullet, and that is what you will search for in didBeginContact
bullet.physicsBody = SKPhysicsBody(texture: bullet.texture, size: bullet.size)
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Bullet
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.CgyBlock
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.usesPreciseCollisionDetection = true
return bullet
}
EDIT:
Based on your recent comments you could probably go with this:
let bulletArray = ["redBullet","magentaBullet", "blueBullet", "cyanBullet", "greenBullet", "yellowBullet"]
//didMoveToView:
var randomBullet = Int(arc4random_uniform(6))
let bulletType = bulletArray[randomBullet]
bulletInCannon.name = bulletType
bulletInCannon = SKSpriteNode(imageNamed: bulletType )
bulletInCannon.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
addChild(bulletInCannon)
//touchesEnded:
var randomBullet = Int(arc4random_uniform(6))
bullet = SKSpriteNode(texture: bulletInCannon.texture)
bullet.name = bulletInCannon.name
bullet.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
bullet.physicsBody = SKPhysicsBody(texture: bullet.texture, size: self.bullet.size)
bullet.physicsBody?.dynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Bullet
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.CgyBlock
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.usesPreciseCollisionDetection = true
addChild(bullet)
let bulletType = bulletArray[randomBullet]
bulletInCannon.texture = SKTexture(imageNamed: bulletType)
bulletInCannon.name = bulletType
First you need to define a class for bullets and Blocks
Then you may define a TextureTypes to store your texture types (red,green,…) and set whatever your random method generates into a class variable of this type.
Then you should manage the contact and find out what are the nodes for BodyA and BodyB. after that it is easy to do what ever you like according to the texturetype of your node,
to clarify the code I have defined Textures as a new Type
enum TextureTypes: String {
case Red,Green
var description:String {
switch self {
case Red:return “Red"
case Green:return “Green”
case Green:return “Blue"
}
}
}
Blockclass and BulletClass both hasto inherit from SKNode cause they are a node!
class BlockClass:SKNode {
var NodesTexture : TextureTypes = TextureTypes.Red
}
class BulletClass:SKNode {
var NodesTexture : TextureTypes = TextureTypes.Red
}
write the following codes into your didBeginContact method to detect the TextureType of your Node
if (contact.bodyA.categoryBitMask == PhysicsCategory.Bullet) &&
(contact.bodyB.categoryBitMask == PhysicsCategory.CgyBlock)
{
Ablock = (BlockClass *) contact.bodyB.node;
Abullet = (BulletClass *) contact.bodyA.node;
}
if (contact.bodyA.categoryBitMask == PhysicsCategory.CgyBlock) &&
(contact.bodyB.categoryBitMask == PhysicsCategory.Bullet)
{
Ablock = (BlockClass *) contact.bodyA.node;
Abullet = (BulletClass *) contact.bodyB.node;
if ( Ablock.NodesTexture = TextureTypes.Red )
{
NSLOG(“A Red Block Detected”)
}
}
Do not forget to define your blocks and bullets of type BlocksClass and BulletClass

didBeginContact not called : this instance is unique to me

So, I am still experimenting with Sprite Kit for my first time ever, and I would like to test for collision. So, I searched around a bit in Apple's documentation, around Stack Overflow, online tutorials, and other forums. However, I was unable to find something a tip or code that makes what I am doing work. So, here are the relevant pieces of code:
This is the code for an obstacle:
func createObstacle(){
var ball = SKShapeNode(circleOfRadius: 20)
var width = UInt32(self.frame.width)
var random_number = arc4random_uniform(width)
ball.position = CGPointMake(CGFloat(random_number), frame.height+20)
ball.strokeColor = SKColor.blackColor()
ball.glowWidth = 1.0
ball.fillColor = SKColor.darkGrayColor()
ball.physicsBody = SKPhysicsBody(circleOfRadius: 20)
ball.physicsBody!.affectedByGravity = true
ball.physicsBody?.categoryBitMask = 6
ball.physicsBody?.dynamic = true
self.addChild(ball)
}
This is relevant code for the thing that it would collide with:
let circle = SKShapeNode(circleOfRadius: 20)
circle.physicsBody = SKPhysicsBody(circleOfRadius: 20)
circle.fillColor = SKColor.blueColor()
circle.strokeColor = SKColor.blueColor()
circle.glowWidth = 1.0
circle.physicsBody?.categoryBitMask = 4
circle.physicsBody?.dynamic = true
circle.physicsBody?.affectedByGravity = false
And this is the code for contact:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody!
var secondBody: SKPhysicsBody!
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask == 4 && secondBody.categoryBitMask == 6) || (firstBody.categoryBitMask == 6 && secondBody.categoryBitMask == 4)){
println("HI")
}else{println("NO")}
}
Sadly, nothing is being printed at all, so something's wrong. Any idea why this doesn't work?
Your class should have delegate SKPhysicsContactDelegate.
class GameScene: SKScene, SKPhysicsContactDelegate {
In didMoveToView write this:
physicsWorld.contactDelegate = self
EDIT
Define CategoryBitMask like this
struct PhysicsCategory {
static let circleCategory : UInt32 = 0b1 // 1
static let ballCategory : UInt32 = 0b10 // 2
}
Give CategoryBitMask to circle and ball
circle.physicsBody?.categoryBitMask = PhysicsCategory.circleCategory
ball.physicsBody?.categoryBitMask = PhysicsCategory.ballCategory
Then check contact like this:
(func didBeginContact(contact: SKPhysicsContact) {
if ((contact.bodyA.categoryBitMask == 0b1 && contact.bodyB.categoryBitMask == 0b10 ) || ( contact.bodyA.categoryBitMask == 0b1 && contact.BodyB.categoryBitMask == 0b1 ))
println("Contact")
}
}
Sorry for typos didnt used editor