Currently I have a Swift/Spritekit app that drops a sprite from the sky, they have collisions off so they can fall through the floor, however I am trying to make a statement that will detect when the player sprite touches the sprite falling from the sky then deletes that child and adds a point to the score var.
Update: 04/29/18 2:10 CST
As of now this is what I have
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var activePlayer:SKSpriteNode! = SKSpriteNode() //Sets active character
var bg:SKSpriteNode! = SKSpriteNode()
var bananaCollected = 0 //Defines banana var
var timer: Timer?
let bananaCat : UInt32 = 0x1 << 1
let playerCat : UInt32 = 0x1 << 2
func bananaDrop() { //Defines bananaDrop
let banana = SKSpriteNode(imageNamed:"banana")
//print("- Debug: [bananaDrop] successfully initiated -")
banana.name = "banana"
//Size of banana
banana.xScale = 0.25
banana.yScale = 0.25
//Defines physics properties
let physicsBody = SKPhysicsBody(circleOfRadius: 15)
//let physicsBody = SKPhysicsBody()
//physicsBody.pinned = true //Suspend in air
physicsBody.allowsRotation = false
physicsBody.affectedByGravity = true
//physicsBody.collisionBitMask = 0
banana.physicsBody?.categoryBitMask = bananaCat
banana.physicsBody?.collisionBitMask = 0
banana.physicsBody?.contactTestBitMask = playerCat
//categoryBitMask is what the physics category of the object is
//banana.physicsBody!.categoryBitMask = playerCategory
//collisionBitMask is what the physics category of objects that this cannot pass through are...multiple categories would be typed like... `cat1 | cat2`
//banana.physicsBody!.collisionBitMask = 0
//contactTestBitMask is what the physics category of the objects that we get alerted to upon contact
//activePlayer.physicsBody!.contactTestBitMask = obstacleCategory
banana.physicsBody = physicsBody
//Starting Location Defined
var x: CGFloat = 0 //Defines X
let y: CGFloat = 400 //Defines how high up banana drops
let bananaDrop = GKShuffledDistribution(lowestValue: 1, highestValue: 11)
//Drop locations defined (relation to X)
switch bananaDrop.nextInt() {
case 1:
x = -170
case 2:
x = -160
case 3:
x = -120
case 4:
x = -80
case 5:
x = -40
case 6:
x = 0
case 7:
x = 40
case 8:
x = 80
case 9:
x = 120
case 10:
x = 160
case 11:
x = 170
default:
fatalError("Case num outside range")
}
banana.position = CGPoint(x: x, y: y)
//Adds banana
self.addChild(banana)
}
func bDropF() { //Defintes Banana Drop Final
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in
self?.timer = timer
self?.timerTime()
})
}
//Stop droping bananas
deinit {
self.timer?.invalidate()
}
func timerTime() {
bananaDrop()
}
override func didMove(to view: SKView) {
print("- Debug: Game Scene Loaded -")
bDropF() //Calls banana drop
if let setupBG:SKSpriteNode = self.childNode(withName: "bg") as? SKSpriteNode {
bg = setupBG
bg.name = "bg"
bg.physicsBody?.affectedByGravity = false
bg.zPosition = -1
}
func didBeginContact(_ contact: SKPhysicsContact){ //Banana Collect
if let firstNode = contact.bodyA.node as? SKSpriteNode, let secondNode = contact.bodyB.node as? SKSpriteNode {
let object1: String = firstNode.name!
let object2: String = secondNode.name!
if (object1 == "player") || (object2 == "banana") {
print("colliding!")
}
}
}
//Player Definitions
if let randoPlayer:SKSpriteNode = self.childNode(withName: "player") as? SKSpriteNode { //Test for Char type
activePlayer = randoPlayer //Char Set
activePlayer.physicsBody?.isDynamic = true //Set dynamic
activePlayer.physicsBody?.affectedByGravity = true //Set dynamic gravity
activePlayer.name = "player"
print("Player Initiated")
print("Physics set :: Dynamic(true):AffectedByGravity(true)")
} else {
print("Failed to initiate player")
}
}
func moveActivePlayerR() {//Right Touch Player Movements Defined
let walkAnimation:SKAction = SKAction(named: "WalkRight")!
let moveAction:SKAction = SKAction.moveBy(x: 100, y: 0, duration: 0.5) //Move Right Side
//let moveRight:SKAction = SKAction.group([walkAnimation, moveAction]) //Depricated
let sound = SKAction.playSoundFileNamed("walk.wav", waitForCompletion: false)
let finalWalkR:SKAction = SKAction.group([walkAnimation, moveAction, sound])
activePlayer.run(finalWalkR)
}
func moveActivePlayerL() { //Left Touch Player Movements Defined
let walkAnimation:SKAction = SKAction(named: "WalkLeft")!
let moveAction:SKAction = SKAction.moveBy(x: -100, y: 0, duration: 0.5) //Move Left Side
//let moveRight:SKAction = SKAction.group([walkAnimation, moveAction]) //Depricated
let sound = SKAction.playSoundFileNamed("walk.wav", waitForCompletion: false)
let finalWalkL:SKAction = SKAction.group([moveAction, sound, walkAnimation])
activePlayer.run(finalWalkL)
}
func touchDown(atPoint pos : CGPoint) {
print("touch \( pos.x),\(pos.y)") //Debug print
if(pos.x > 0) { //if touched right side of screen
print("Right touch")
moveActivePlayerR()
} else if (pos.x < 0) { //if touched left side of screen
print("Left touch")
moveActivePlayerL()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}
}
However even with these categories setup it still appears that I am unable to get them to collide properly. When they touch each other there is nothing in my console indicating that they have collided. Please help!
You do not have to have a collisionBitMask set in order to detect contact, you just need to set the contactTestBitMask to the category of the obstacle you want to detect the collision with. You then check for the collision in the didBegin func. Ensure that you have the SKPhysicsContactDelegate set on your scene.
class GameScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self
when setting up your objects
//categoryBitMask is what the physics category of the object is
object.physicsBody!.categoryBitMask = playerCategory
//collisionBitMask is what the physics category of objects that this cannot pass through are...multiple categories would be typed like... `cat1 | cat2`
object.physicsBody!.collisionBitMask = 0
//contactTestBitMask is what the physics category of the objects that we get alerted to upon contact
object.physicsBody!.contactTestBitMask = obstacleCategory
the didBegin func in your scene
func didBeginContact(_ contact: SKPhysicsContact) {
if let firstNode = contact.bodyA.node as? SKSpriteNode, let secondNode = contact.bodyB.node as? SKSpriteNode {
let object1: String = firstNode.name!
let object2: String = secondNode.name!
if (object1 == "obstacle") || (object2 == "obstacle") {
//run some code because these 2 have collided
}
}
}
...or...
func didBeginContact(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == PhysicsCategory.obstacleCategory || contact.bodyB.categoryBitMask == PhysicsCategory.obstacleCategory {
//obstacle has hit player do something
}
}
Related
I have three nodes in my game-
The Player -
This is a boat that you drag around using a joystick.
let collisionPlayer : UInt32 = 0x1 << 1
let apple = SKSpriteNode(texture: texture)
apple.position = position
apple.physicsBody = SKPhysicsBody(circleOfRadius: apple.size.width / 2.0)
apple.physicsBody?.affectedByGravity = false
apple.physicsBody?.isDynamic = true
apple.setScale(0.1)
addChild(apple)
("apple" is the boat)
The Enemy Boat -
This is a CPU boat that randomly follows the player around.
let collisionNPC : UInt32 = 0x1 << 0
appleNPC.position = position
appleNPC.physicsBody = SKPhysicsBody(circleOfRadius: appleNPC.size.width / 2.0)
appleNPC.physicsBody?.isDynamic = true
appleNPC.physicsBody?.affectedByGravity = false
appleNPC.position = CGPoint(x: 600, y: 200)
appleNPC.setScale(0.1)
addChild(appleNPC)
The Bullet
(self-explanatory)
let collisionBullet : UInt32 = 0x1 << 2
(The following code is in TouchesEnded)
bullet?.name = "Bullet"
bullet?.position = (appleNode?.position)!
bullet?.setScale(0.05)
bullet?.zPosition = 1
bullet?.physicsBody = SKPhysicsBody(circleOfRadius: (bullet?.size.width)!/2)
bullet?.physicsBody?.isDynamic = true
bullet?.physicsBody?.affectedByGravity = false
bullet?.physicsBody?.usesPreciseCollisionDetection = true
To move the bullet I use the code:
// Your code with delay
if bulletNumbers > 0 {
let offset = CGPoint(x: touchLocation.x - (bullet?.position.x)! , y: touchLocation.y - (bullet?.position.y)!)
//Stops Bullet from shooting backwards
//Get the direction of where to shoot
let direction = offset
//Make it shoot far enough to be guaranteed off screen
let shootAmount = CGPoint(x: direction.x * 10, y: direction.y * 10)
//Add the shoot amount to the current position
let realDest = CGPoint(x: shootAmount.x + (bullet?.position.x)!, y: shootAmount.y + (bullet?.position.y)!)
//Create the actions
addChild(bullet!)
self.bulletNumbers -= 1
let distance = sqrt(pow(realDest.x - (appleNode?.position.x)!, 2) +
pow(realDest.y - (appleNode?.position.y)!, 2))
// run the sequence of actions for the firing
let duration = TimeInterval(distance / PlayerMissileSpeed)
let missileMoveAction = SKAction.move(to: realDest, duration: duration)
let when = DispatchTime.now() + 10 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) { self.bulletNumbers += 1
}
bullet?.run(missileMoveAction) {
self.bullet?.isHidden = true
self.bullet?.removeFromParent()
print("\(self.bulletNumbers)")
}}
else if bulletNumbers <= 0 {
print("reload")
let when = DispatchTime.now() + 2 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
}
}
}
func didBegin(_ contact: SKPhysicsContact) {
print("test")
}
func setRandomStickColor() {
let randomColor = UIColor.random()
moveAnalogStick.stick.color = randomColor
}
func setRandomSubstrateColor() {
let randomColor = UIColor.random()
moveAnalogStick.substrate.color = randomColor
}
override func update(_ currentTime: TimeInterval) {
/* Called before each frame is rendered */
super.update(currentTime)
let _:UInt32 = arc4random_uniform(1) //
appleNodeCpuSpeed = CGFloat(1)
var locationx : CGFloat
var locationy: CGFloat
locationx = (appleNode?.position.x)!
locationy = (appleNode?.position.y)!
let dx = locationx - (appleNodeNPC?.position.x)!
let dy = locationy - (appleNodeNPC?.position.y)!
let angle = atan2(dy, dx)
let vx = cos(angle) * appleNodeCpuSpeed
let vy = sin(angle) * appleNodeCpuSpeed
self.appleNodeNPC?.position.x += vx
self.appleNodeNPC?.position.y += vy
let when = DispatchTime.now() + 0.25 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
self.appleNodeNPC?.zRotation = angle + 90
}
//2
This works fine. The bullet is launched from the boat to the touch location, however, things start to go wrong when I tried to introduce collisions. Here is my code below:
appleNode?.physicsBody?.categoryBitMask = collisionPlayer
appleNode?.physicsBody?.collisionBitMask = collisionNPC
appleNode?.physicsBody?.contactTestBitMask = 0
appleNodeNPC?.physicsBody?.categoryBitMask = collisionNPC
appleNodeNPC?.physicsBody?.collisionBitMask = collisionPlayer
appleNodeNPC?.physicsBody?.contactTestBitMask = collisionBullet
bullet?.physicsBody?.categoryBitMask = collisionBullet
bullet?.physicsBody?.collisionBitMask = 0
bullet?.physicsBody?.contactTestBitMask = collisionNPC
physicsWorld.contactDelegate = self
view.showsPhysics = true
Then in my didBegin function I have:
func didBegin(_ contact: SKPhysicsContact) {
print("test")
}
Let's start from the beginning. I tap the screen and the bullet is launched. The bullet does not collide with the Player, which is what I originally wanted. However, when the bullet makes contact with the Enemy ship it DOES collide. It goes around the ship and keeps going on to its original destination. I want the bullet to contact the ship and nothing else - no collisions. I would hope to assume that my error here is because of my code for moving the bullet, but i'm not sure. Any help is appreciated.
Define unique categories, ensure your class is a SKPhysicsContactDelegate and make yourself the physics contact delegate:
//Physics categories
let appleCategory: UInt32 = 1 << 0
let enemyCategory: UInt32 = 1 << 1
let bulletCategory: UInt32 = 1 << 2
class GameScene: SKScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self
Assign the categories (usually in didMove(to view:) :
apple.physicsBody.catgeoryBitMask = appleCategory
enemy.physicsBody.catgeoryBitMask = enemyCategory
bullet.physicsBody.catgeoryBitMask = bulletCategory
(Make sure you've created physics bodies for each node)
Set up collisions:
apple.physicsBody?.collisionBitMask = 0 // apple/player collides with nothing
enemy.physicsBody?.collisionBitMask = 0 // enemy collides with nothing
bullet.physicsBody?.collisionBitMask = 0 // bullet collides with nothing
or even:
for node in [apple, enemy, bullet] {
node.physicsBody?.collisionBitMask = 0 // collides with nothing
}
Set up contacts
bullet.physicsBody?.collisionBitMask = enemyCategory // bullet contacts enemy
Make sure that at least one of the objects involved in each potential contact has the isDynamic property on its physics body set to true, or no contact will be generated. It is not necessary for both of the objects to be dynamic.
You should now get didBegin called when the bullet and the enemy make contact. You could code didBegin like 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 bulletCategory | enemyCategory:
print("bullet and enemy have contacted.")
let bulletNode = contact.bodyA.categoryBitMask == bulletCategory ? contact.bodyA.node : contact.bodyB.node
enemyHealth -= 10
bulletNode.removeFromParent
default:
print("Some other contact occurred")
}
}
I'm still a beginner in Swift and I'm having some trouble with collision detections in SpriteKit. I used this question from StackOverFlow which was great in showing my how to construct things neatly. But I'm having problems with my didBegin function, which does not even get called to at all. I'm hoping I missed something simple out that you guys can take a look at for me.
Thanks in advance.
Here is my PhysicsCatagoies struct:
import Foundation
import SpriteKit
struct PhysicsCatagories: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) { self.rawValue = rawValue }
static let None = PhysicsCatagories(rawValue: 0b00000) // Binary for 0
static let Player = PhysicsCatagories(rawValue: 0b00001) // Binary for 1
static let EnemyBullet = PhysicsCatagories(rawValue: 0b00010) // Binary for 2
static let PlayerBullet = PhysicsCatagories(rawValue: 0b00100) // Binary for 4
static let Enemy = PhysicsCatagories(rawValue: 0b01000) // Binary for 8
static let Boss = PhysicsCatagories(rawValue: 0b10000) // Binary for 16
}
extension SKPhysicsBody {
var category: PhysicsCatagories {
get {
return PhysicsCatagories(rawValue: self.categoryBitMask)
}
set(newValue) {
self.categoryBitMask = newValue.rawValue
}
}
}
And here is how I assigned my nodes in GameScene:
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody!.affectedByGravity = false
player.physicsBody!.categoryBitMask = PhysicsCatagories.Player.rawValue
player.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
player.physicsBody!.category = [.Enemy, .EnemyBullet, .Boss]
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCatagories.PlayerBullet.rawValue
bullet.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
bullet.physicsBody!.category = [.Enemy, .Boss]
enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.size)
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.categoryBitMask = PhysicsCatagories.Enemy.rawValue
enemy.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
enemy.physicsBody!.category = [.Player, .PlayerBullet]
enemyBullet.physicsBody = SKPhysicsBody(rectangleOf: enemyBullet.size)
enemyBullet.physicsBody!.affectedByGravity = false
enemyBullet.physicsBody!.categoryBitMask = PhysicsCatagories.EnemyBullet.rawValue
enemyBullet.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
enemyBullet.physicsBody!.category = [.Player]
boss.physicsBody = SKPhysicsBody(rectangleOf: boss.size)
boss.physicsBody!.affectedByGravity = false
boss.physicsBody!.categoryBitMask = PhysicsCatagories.Boss.rawValue
boss.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
boss.physicsBody!.category = [.Player, .PlayerBullet]
bulletSpecial.physicsBody = SKPhysicsBody(rectangleOf: bulletSpecial.size)
bulletSpecial.physicsBody!.affectedByGravity = false
bulletSpecial.physicsBody!.categoryBitMask = PhysicsCatagories.PlayerBullet.rawValue
bulletSpecial.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
bulletSpecial.physicsBody!.category = [.Enemy, .Boss]
Finally, this is my didBegin function, which does not seem to work at all:
func didBegin(_ contact: SKPhysicsContact) {
let contactCategory: PhysicsCatagories = [contact.bodyA.category, contact.bodyB.category]
switch contactCategory {
case [.Player, .Enemy]:
print("player has hit enemy")
case [.PlayerBullet, .Enemy]:
print("player bullet has hit enemy")
case [.PlayerBullet, .Boss]:
print("player bullet has hit boss")
case [.Player, .Boss]:
print("player has hit boss")
case [.Player, .EnemyBullet]:
print("player has hit enemy bullet")
default:
preconditionFailure("Unexpected collision type: \(contactCategory)")
}
}
I've not used the OptionSet technique for cagegoryBitMasks, so here's how I'd do it:
Define unique categories, ensure your class is a SKPhysicsContactDelegate and make yourself the physics contact delegate:
//Physics categories
let PlayerCategory: UInt32 = 1 << 0 // b'00001'
let EnemyBulletCategory: UInt32 = 1 << 1 // b'00010'
let PlayerBulletCategory: UInt32 = 1 << 2 // b'00100'
let EnemyCategory: UInt32 = 1 << 3 // b'01000'
let BossCategory: UInt32 = 1 << 4 // b'10000'
class GameScene: SKScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self
Assign the categories (usually in didMove(to view:) :
player.physicsBody.catgeoryBitMask = PlayerCategory
bullet.physicsBody.catgeoryBitMask = BulletCategory
enemy.physicsBody.catgeoryBitMask = EnemyCategory
enemyBullet.physicsBody.catgeoryBitMask = EnemyBulletCategory
boss.physicsBody.catgeoryBitMask = BossCategory
(not sure about bulletSpecial - looks the same as bullet)
Set up contact detection:
player.physicsBody?.contactTestBitMask = EnemyCategory | EnemyBulletCategory | BossCategory
bullet.physicsBody?.contactTestBitMask = EnemyCategory | BossCategory
enemy.physicsBody?.contactTestBitMask = PlayerCategory | PlayerBulletCategory
enemyBullet.physicsBody?.contactTestBitMask = PlayerCategory
boss.physicsBody?.contactTestBitMask = PlayerCategory | PlayerBulletCategory
Turn off collisions: (on by default)
player.physicsBody?.collisionBitMask = 0
bullet.physicsBody?.collisionBitMask = 0
enemy.physicsBody?.collisionBitMask = 0
enemyBullet.physicsBody?.collisionBitMask = 0
boss.physicsBody?.collisionBitMask = 0
Implement didBegin:
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 PlayerCategory | EnemyCategory:
print("player has hit enemy")
case PlayerBulletCategory | EnemyCategory:
print("player bullet has hit enemy")
case PlayerBulletCategory | BossCategory:
print("player bullet has hit boss")
case PlayerCategory | BossCategory:
print("player has hit boss")
case PlayerCategory | EnemyBulletCategory:
print("player has hit enemy bullet")
default:
print("Undetected collision occurred")
}
}
It's a bit late here, so hopefully I haven't made any stupid mistakes.
=======================
You could also include this function and then call it via checkPhysics() once you have set up all your physics bodies and collisions and contact bit masks. It will go through every node and print out what collides with what and what contacts what (it doesn't check the isDynamic property, so watch out for that):
//MARK: - Analyse the collision/contact set up.
func checkPhysics() {
// Create an array of all the nodes with physicsBodies
var physicsNodes = [SKNode]()
//Get all physics bodies
enumerateChildNodes(withName: "//.") { node, _ in
if let _ = node.physicsBody {
physicsNodes.append(node)
} else {
print("\(String(describing: node.name)) does not have a physics body so cannot collide or be involved in contacts.")
}
}
//For each node, check it's category against every other node's collion and contctTest bit mask
for node in physicsNodes {
let category = node.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let name = node.name != nil ? node.name : "Category \(category)"
if category == UInt32.max {print("Category for \(String(describing: name)) does not appear to be set correctly as \(category)")}
let collisionMask = node.physicsBody!.collisionBitMask
let contactMask = node.physicsBody!.contactTestBitMask
// If all bits of the collisonmask set, just say it collides with everything.
if collisionMask == UInt32.max {
print("\(name) collides with everything")
}
for otherNode in physicsNodes {
if (node != otherNode) && (node.physicsBody?.isDynamic == true) {
let otherCategory = otherNode.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let otherName = otherNode.name != nil ? otherNode.name : "Category \(otherCategory)"
// If the collisonmask and category match, they will collide
if ((collisionMask & otherCategory) != 0) && (collisionMask != UInt32.max) {
print("\(name) collides with \(String(describing: otherName))")
}
// If the contactMAsk and category match, they will contact
if (contactMask & otherCategory) != 0 {print("\(name) notifies when contacting \(String(describing: otherName))")}
}
}
}
}
It will produce output like:
Optional("shape_blueSquare") collides with Optional("Screen_edge")
Optional("shape_redCircle") collides with Optional("Screen_edge")
Optional("shape_redCircle") collides with Optional("shape_blueSquare")
Optional("shape_redCircle") notifies when contacting Optional("shape_purpleSquare")
Optional("shape_redCircle") collides with Optional("shape_greenRect")
Optional("shape_redCircle") notifies when contacting Optional("shape_greenRect")
Optional("shape_purpleSquare") collides with Optional("Screen_edge")
Optional("shape_purpleSquare") collides with Optional("shape_greenRect")
Category for Optional("shape_greenRect") does not appear to be set correctly as 4294967295
Optional("shape_greenRect") collides with Optional("Screen_edge")
Optional("shape_yellowTriangle") notifies when contacting Optional("shape_redCircle")
Optional("shape_yellowTriangle") collides with Optional("shape_greenRect")
Optional("shape_yellowTriangle") notifies when contacting Optional("shape_greenRect")
etc.
So I have adjusted my games ball type from a shape to an image which forced me to redo the physics of my game. I am new to swift and have struggled with fixing my collisions in my swift game.
class GameScene: SKScene, GameDelegate, SKPhysicsContactDelegate {
var ball: Ball!
let ballSpeedX = CGFloat(500)
//ball = Ball(imageNamed:"colorBall.png")
enum CollisionTypes: UInt32 {
case Floor = 1
case Ball = 2
}
// board
let boards = Boards.make(CollideType.BoardStart.rawValue)
var lastBoard: SKNode?
var boardSpeedY: CGFloat { get { return CGFloat(160) * accelerate }}
let boardDistanceY = CGFloat(300)
let boardYDistanceHide = CGFloat(30)
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
let flooeBody = SKPhysicsBody.init(edgeLoopFromRect: self.frame)
self.physicsBody = floorBody
self.physicsBody!.affectedByGravity = false
self.physicsBody!.usesPreciseCollisionDetection = true
self.physicsBody!.dynamic = true
self.physicsBody!.mass = 0
self.physicsBody!.friction = 0
self.physicsBody!.linearDamping = 0
self.physicsBody!.angularDamping = 0
self.physicsBody!.restitution = 1
self.physicsBody!.categoryBitMask = CollisionTypes.Floor.rawValue
self.physicsBody!.contactTestBitMask = CollisionTypes.Ball.rawValue
// Prepare the ball - physics engine.
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.frame.width/2)
ball.physicsBody!.affectedByGravity = true
ball.physicsBody!.restitution = 0.8
ball.physicsBody!.linearDamping = 0
ball.physicsBody!.friction = 0.3
ball.physicsBody!.dynamic = true
ball.physicsBody!.mass = 0.5
ball.physicsBody!.allowsRotation = true
ball.physicsBody!.categoryBitMask = CollisionTypes.Ball.rawValue
ball.physicsBody!.contactTestBitMask = CollisionTypes.Floor.rawValue
ball.physicsBody!.collisionBitMask = CollisionTypes.Floor.rawValue
ball.hidden = false
self.addChild(ball)
// scene
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody!.categoryBitMask = CollideType.Scene.toMask()
self.physicsBody!.dynamic = false
self.physicsBody!.friction = 0
// ceil
let ceil = SKShapeNode(rectOfSize: CGSize(width: self.frame.width, height: 2))
ceil.position.x = CGRectGetMidX(self.frame)
ceil.position.y = self.frame.height - CGRectGetMidY(ceil.frame)
ceil.physicsBody = SKPhysicsBody(rectangleOfSize: ceil.frame.size)
ceil.physicsBody!.categoryBitMask = CollideType.Ceil.toMask()
ceil.physicsBody!.dynamic = false
ceil.alpha = 0
self.addChild(ceil)
// floor
let floor = SKShapeNode(rectOfSize: CGSize(width: self.frame.width, height: 2))
floor.position.x = CGRectGetMidX(self.frame)
floor.position.y = CGRectGetMidY(floor.frame)
floor.physicsBody = SKPhysicsBody(rectangleOfSize: floor.frame.size)
//floor.physicsBody!.categoryBitMask = CollideType.Floor.toMask() two
floor.physicsBody!.dynamic = false
floor.alpha = 0
self.addChild(floor)
}
That is the scene and physics I attempted to set up and was directed with. Below is the collide errors which cause the app to crash upon touch.
func didBeginContact(contact: SKPhysicsContact) {
let bitMaskAAndB = contact.bodyA.categoryBitMask == CollisionTypes.Floor.rawValue && contact.bodyB.categoryBitMask == CollisionTypes.Ball.rawValue
let ballAndBoardMask = CollideType.Ball.toMask() | boards.usedCollideMasks
// ball and board error: CANNOT CONVERT VALUE OF TYPE BOOL TO EXPCTD ARG TYPE UIINT32
if bitMaskAAndB | ballAndBoardMask == ballAndBoardMask {
let boardNode: SKNode! = contact.bodyA.categoryBitMask == CollideType.Ball.toMask() ? contact.bodyB.node : contact.bodyA.node
let board = boardNode.bind as! BoardDelegate
board.didBeginContact(boardNode, ball: ball, contact: contact, game: self)
}
// ball and ceil => ERROR
else if bitMaskAAndB == CollideType.toMask([.Ball, .Ceil]) {
stopGame()
}
// ball and floor => stop game
else if bitMaskAAndB == CollideType.toMask([.Ball, .Floor]) {
stopGame()
}
}
func didEndContact(contact: SKPhysicsContact) {
let ballAndBoardMask = CollideType.Ball.toMask() | boards.usedCollideMasks
// ball and board, handle it by board delegate
if contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask | ballAndBoardMask == ballAndBoardMask {
let boardNode: SKNode! = contact.bodyA.categoryBitMask == CollideType.Ball.toMask() ? contact.bodyB.node : contact.bodyA.node
let board = boardNode.bind as! BoardDelegate
board.didEndContact(boardNode, ball: ball, contact: contact, game: self)
}
}
CollideType definition as below:
enum CollideType: Int {
case Scene = 0
case Ceil = 1
case Floor = 2
case Ball = 3
case BoardStart = 4
func toMask()-> UInt32 {
return UInt32(1 << self.rawValue)
}
static func toMask(masks: [CollideType])-> UInt32 {
var toMask = UInt32(0)
for type in masks {
toMask |= type.toMask()
}
return toMask
}
So the issue now is that you have two definition for your masks:
Remove this one:
enum CollisionTypes: UInt32 {
case Floor = 1
case Ball = 2
}
Use only this one:
enum CollideType: Int {
case Scene = 0
case Ceil = 1
case Floor = 2
case Ball = 3
case BoardStart = 4
func toMask()-> UInt32 {
return UInt32(1 << self.rawValue)
}
static func toMask(masks: [CollideType])-> UInt32 {
var toMask = UInt32(0)
for type in masks {
toMask |= type.toMask()
}
return toMask
}
Correct all code to match with CollideType definitions.
Correct this line in didBeginContact:
let bitMaskAAndB = contact.bodyA.categoryBitMask == CollisionTypes.Floor.rawValue && contact.bodyB.categoryBitMask == CollisionTypes.Ball.rawValue
with:
let bitMaskAAndB = contact.bodyA.categoryBitMask == CollideType.Floor.toMask() ? contact.bodyB.categoryBitMask : CollideType.Ball.toMask()
If you have correct all you will don't have yet this error:
// ball and ceil => ERROR
I'm using the scene editor in SpriteKit to place color sprites and assign them textures using the Attributes Inspector. My problem is trying to figure out how to reference those sprites from my GameScene file. For example, I'd like to know when a sprite is a certain distance from my main character.
Edit - code added
I'm adding the code because for some reason, appzYourLife's answer worked great in a simple test project, but not in my code. I was able to use Ron Myschuk's answer which I also included in the code below for reference. (Though, as I look at it now I think the array of tuples was overkill on my part.) As you can see, I have a Satellite class with some simple animations. There's a LevelManager class that replaces the nodes from the scene editor with the correct objects. And finally, everything gets added to the world node in GameScene.swift.
Satellite Class
func spawn(parentNode:SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50)) {
parentNode.addChild(self)
createAnimations()
self.size = size
self.position = position
self.name = "satellite"
self.runAction(satAnimation)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = PhysicsCategory.satellite.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.laser.rawValue
self.physicsBody?.collisionBitMask = 0
}
func createAnimations() {
let flyFrames:[SKTexture] = [textureAtlas.textureNamed("sat1.png"),
textureAtlas.textureNamed("sat2.png")]
let flyAction = SKAction.animateWithTextures(flyFrames, timePerFrame: 0.14)
satAnimation = SKAction.repeatActionForever(flyAction)
let warningFrames:[SKTexture] = [textureAtlas.textureNamed("sat8.png"),
textureAtlas.textureNamed("sat1.png")]
let warningAction = SKAction.animateWithTextures(warningFrames, timePerFrame: 0.14)
warningAnimation = SKAction.repeatActionForever(warningAction)
}
func warning() {
self.runAction(warningAnimation)
}
Level Manager Class
import SpriteKit
class LevelManager
{
let levelNames:[String] = ["Level1"]
var levels:[SKNode] = []
init()
{
for levelFileName in levelNames {
let level = SKNode()
if let levelScene = SKScene(fileNamed: levelFileName) {
for node in levelScene.children {
switch node.name! {
case "satellite":
let satellite = Satellite()
satellite.spawn(level, position: node.position)
default: print("Name error: \(node.name)")
}
}
}
levels.append(level)
}
}
func addLevelsToWorld(world: SKNode)
{
for index in 0...levels.count - 1 {
levels[index].position = CGPoint(x: -2000, y: index * 1000)
world.addChild(levels[index])
}
}
}
GameScene.swift - didMoveToView
world = SKNode()
world.name = "world"
addChild(world)
physicsWorld.contactDelegate = self
levelManager.addLevelsToWorld(self.world)
levelManager.levels[0].position = CGPoint(x:0, y: 0)
//This does not find the satellite nodes
let satellites = children.flatMap { $0 as? Satellite }
//This does work
self.enumerateChildNodesWithName("//*") {
node, stop in
if (node.name == "satellite") {
self.satTuple.0 = node.position
self.satTuple.1 = (node as? SKSpriteNode)!
self.currentSatellite.append(self.satTuple)
}
}
The Obstacle class
First of all you should create an Obstacle class like this.
class Obstacle: SKSpriteNode { }
Now into the scene editor associate the Obstacle class to your obstacles images
The Player class
Do the same for Player, create a class
class Player: SKSpriteNode { }
and associate it to your player sprite.
Checking for collisions
Now into GameScene.swift change the updated method like this
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let obstacles = children.flatMap { $0 as? Obstacle }
let player = childNodeWithName("player") as! Player
let obstacleNearSprite = obstacles.contains { (obstacle) -> Bool in
let distance = hypotf(Float(player.position.x) - Float(obstacle.position.x), Float(player.position.y) - Float(obstacle.position.y))
return distance < 100
}
if obstacleNearSprite {
print("Oh boy!")
}
}
What does it do?
The first line retrieves all your obstacles into the scene.
the second line retrieves the player (and does crash if it's not present).
Next it put into the obstacleNearSprite constant the true value if there is at least one Obstacle at no more then 100 points from Player.
And finally use the obstacleNearSprite to print something.
Optimizations
The updated method gets called 60 times per second. We put these 2 lines into it
let obstacles = children.flatMap { $0 as? Obstacle }
let player = childNodeWithName("player") as! Player
in order to retrieves the sprites we need. With the modern hardware it is not a problem but you should save references to Obstacle and Player instead then searching for them in every frame.
Build a nice game ;)
you will have to loop through the children of the scene and assign them to local objects to use in your code
assuming your objects in your SKS file were named Obstacle1, Obstacle2, Obstacle3
Once in local objects you can check and do whatever you want with them
let obstacle1 = SKSpriteNode()
let obstacle2 = SKSpriteNode()
let obstacle3 = SKSpriteNode()
let obstacle3Location = CGPointZero
func setUpScene() {
self.enumerateChildNodesWithName("//*") {
node, stop in
if (node.name == "Obstacle1") {
self.obstacle1 = node
}
else if (node.name == "Obstacle2") {
self.obstacle2 = node
}
else if (node.name == "Obstacle3") {
self.obstacle3Location = node.position
}
}
}
I'm trying to implement a simple scoring system into my game using this tutorial as a reference:
http://www.raywenderlich.com/87232/make-game-like-mega-jump-sprite-kit-swift-part-2
The problem is, if I try to implement as is, it crashes in the GameScene.swift on this line:
let another = whichNode as! GameObjectNode
Here are the main parts of the code where the player collects the coins. I can also invite you to my repo if you'd like to take a closer and better look. I know it can be hard from looking at the code I pasted up here.
GameObjectNode.swift:
enum CoinType: Int {
case Normal = 0
case Special
}
struct CollisionCategoryBitmask {
static let Player: UInt32 = 0x00
static let Coin: UInt32 = 0x01
static let Platform: UInt32 = 0x02
}
class GameObjectNode: SKNode {
func collisionWithPlayer(player: SKNode) -> Bool {
return false
}
func checkNodeRemoval(playerY: CGFloat) {
if playerY > self.position.y + 300.0 {
self.removeFromParent()
}
}
}
class CoinNode: GameObjectNode {
let coinSound = SKAction.playSoundFileNamed("StarPing.wav", waitForCompletion: false)
var coinType: CoinType!
override func collisionWithPlayer(player: SKNode) -> Bool {
// Boost the player up
player.physicsBody?.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: 400.0)
// Play sound
runAction(coinSound, completion: {
// Remove this Star
self.removeFromParent()
})
// Award score
GameState.sharedInstance.score += (coinType == .Normal ? 20 : 100)
// Award stars
GameState.sharedInstance.coins += (coinType == .Normal ? 1 : 5)
// The HUD needs updating to show the new stars and score
return true
}
}
GameState.swift
class GameState {
var score: Int
var highScore: Int
var coins: Int
init() {
// Init
score = 0
highScore = 0
coins = 0
// Load game state
let defaults = NSUserDefaults.standardUserDefaults()
highScore = defaults.integerForKey("highScore")
coins = defaults.integerForKey("coins")
}
func saveState() {
// Update highScore if the current score is greater
highScore = max(score, highScore)
// Store in user defaults
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "highScore")
defaults.setInteger(coins, forKey: "coins")
NSUserDefaults.standardUserDefaults().synchronize()
}
class var sharedInstance: GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
}
And the GameScene.swift:
import SpriteKit
import CoreMotion
import GameplayKit
struct PhysicsCategory {
static let None: UInt32 = 0
static let Player: UInt32 = 0b1 // 1
static let PlatformNormal: UInt32 = 0b10 // 2
static let PlatformBreakable: UInt32 = 0b100 // 4
static let CoinNormal: UInt32 = 0b1000 // 8
static let CoinSpecial: UInt32 = 0b10000 // 16
static let Edges: UInt32 = 0b100000 // 32
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Other Properties
...
var player: SKSpriteNode!
// HUD
var hudNode: SKNode!
var lblScore: SKLabelNode!
var lblCoins: SKLabelNode!
override func didMoveToView(view: SKView) {
....
// HUD
hudNode = SKNode()
hudNode.zPosition = 1000
cameraNode.addChild(hudNode)
// Coins
let coin = SKSpriteNode(imageNamed: "powerup05_1")
coin.position = convertPoint(CGPoint(x: 300, y: self.size.height-100), toNode: cameraNode)
coin.zPosition = 1000
hudNode.addChild(coin)
lblCoins = SKLabelNode(fontNamed: "ChalkboardSE-Bold")
lblCoins.fontSize = 70
lblCoins.fontColor = SKColor.whiteColor()
lblCoins.position = convertPoint(CGPoint(x: 375, y: self.size.height-100), toNode: cameraNode)
lblCoins.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Left
lblCoins.zPosition = 1000
lblCoins.text = String(format: "X %d", GameState.sharedInstance.coins)
hudNode.addChild(lblCoins)
// Score
// 4
lblScore = SKLabelNode(fontNamed: "ChalkboardSE-Bold")
lblScore.fontSize = 70
lblScore.fontColor = SKColor.whiteColor()
lblScore.position = convertPoint(CGPoint(x: self.size.width-325, y: self.size.height-100), toNode: cameraNode)
lblScore.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Right
lblScore.zPosition = 1000
lblScore.text = "0"
hudNode.addChild(lblScore)
}
func setupNodes() {
...
player = fgNode.childNodeWithName("Player") as! SKSpriteNode
}
func createStarAtPosition(position: CGPoint, ofType type: CoinType) -> CoinNode {
// 1
let node = CoinNode()
let thePosition = CGPoint(x: position.x * scaleFactor, y: position.y)
node.position = thePosition
node.name = "NODE_COIN"
// 2
node.coinType = type
var sprite: SKSpriteNode
if type == .Special {
sprite = SKSpriteNode(imageNamed: "CoinSpecial")
} else {
sprite = SKSpriteNode(imageNamed: "Coin")
}
node.addChild(sprite)
// 3
node.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width / 2)
// 4
node.physicsBody?.dynamic = false
node.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Coin
node.physicsBody?.collisionBitMask = 0
node.physicsBody?.contactTestBitMask = 0
return node
}
func didBeginContact(contact: SKPhysicsContact) {
let other = contact.bodyA.categoryBitMask == PhysicsCategory.Player ? contact.bodyB : contact.bodyA
var updateHUD = false
let whichNode = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
// Code crashes here
let another = whichNode as! GameObjectNode
updateHUD = another.collisionWithPlayer(player)
if updateHUD {
lblCoins.text = String(format: "X %d", GameState.sharedInstance.coins)
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
switch other.categoryBitMask {
case PhysicsCategory.CoinNormal:
if let coin = other.node as? SKSpriteNode {
emitParticles("CollectNormal", sprite: coin)
jumpPlayer()
runAction(soundCoin)
}
case PhysicsCategory.CoinSpecial:
if let coin = other.node as? SKSpriteNode {
emitParticles("CollectSpecial", sprite: coin)
boostPlayer()
runAction(soundBoost)
}
case PhysicsCategory.PlatformNormal:
if let platform = other.node as? SKSpriteNode {
if player.physicsBody!.velocity.dy < 0 {
platformAction(platform, breakable: false)
jumpPlayer()
runAction(soundJump)
}
}
case PhysicsCategory.PlatformBreakable:
if let platform = other.node as? SKSpriteNode {
if player.physicsBody!.velocity.dy < 0 {
platformAction(platform, breakable: true)
jumpPlayer()
runAction(soundBrick)
}
}
default:
break;
}
}
I dont understand the code you use in didBeganContact, but you can define the contact bodies in this way:
enum Ctg:UInt32
{
case Coin = 1
case Hero = 2
case Villain = 4
case Car = 8
}
var hero = SKSpriteNode()
hero.physicsBody = SKPhysicsBody(rectangleOfSize: hero.size)
hero.physicsBody?.categoryBitMask = Ctg.Hero.rawValue
hero.physicsBody?.contactTestBitMask = Ctg.Coin.rawValue | Ctg.Villian.rawValue
var coin = SKSpriteNode()
coin.physicsBody = SKPhysicsBody(rectangleOfSize: coin.size)
coin.physicsBody?.categoryBitMask = Ctg.Coin.rawValue
coin.physicsBody?.contactTestBitMask = Ctg.Hero.rawValue
func didBeginContact(contact: SKPhysicsContact)
{
var first = SKNode()
var sec = SKNode()
// this way you ensure that the first body is the most valuable Ctg (enum)
if contact.bodyA.node?.physicsBody?.categoryBitMask > contact.bodyB.node?.physicsBody?.categoryBitMask
{
first = contact.bodyA.node!
sec = contact.bodyB.node!
}
else
{
first = contact.bodyB.node!
sec = contact.bodyA.node!
}
// this part be sure that the category of first it is of most value that sec
if first.physicsBody!.categoryBitMask == Ctg.Hero.rawValue && sec.physicsBody!.categoryBitMask == Ctg.Coin.rawValue
{
hero.coins++
scene.labelCoins.text = String(coins)
}
if first.physicsBody!.categoryBitMask == Ctg.Villain.rawValue && sec.physicsBody!.categoryBitMask == Ctg.Hero.rawValue
{
gameOver = true
hero.removeFromParent()
lostGame()
}
}