Swift/SpriteKit Access A Variable Of An Object + Remove A Node - swift

So I have two objects that should lose health points at an collision.
func addPlayer(xPos: CGFloat, yPos: CGFloat){
playerNode = SKSpriteNode(imageNamed: "player")
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: width/2)
playerNode.physicsBody!.affectedByGravity = false
playerNode.physicsBody!.categoryBitMask = PhysicsCategory.Player
playerNode.physicsBody!.contactTestBitMask = PhysicsCategory.Wall | PhysicsCategory.Zombie
playerNode.physicsBody!.collisionBitMask = PhysicsCategory.Wall | PhysicsCategory.Zombie
playerNode.name = "Player"
player = Player(node: playerNode, healthPoints: 100, attack: 10)
playerNode.position.x = xPos
playerNode.position.y = yPos
playerNode.size = CGSize(width: width, height: width)
addChild(playerNode)
}
func addZombie(xPos: CGFloat, yPos: CGFloat){
zombieNode = SKSpriteNode(imageNamed: "zombie")
zombieNode.physicsBody = SKPhysicsBody(circleOfRadius: width/2)
zombieNode.physicsBody!.affectedByGravity = false
zombieNode.physicsBody!.categoryBitMask = PhysicsCategory.Zombie
zombieNode.physicsBody!.contactTestBitMask = PhysicsCategory.Zombie | PhysicsCategory.Player | PhysicsCategory.Wall
zombieNode.physicsBody!.collisionBitMask = PhysicsCategory.Zombie | PhysicsCategory.Player | PhysicsCategory.Wall
zombieNode.name = "Zombie"
zombie = Zombie(node: zombieNode, healthPoints: 50, attack: 5)
Zombies.append(zombie!)
zombieNode.position.x = xPos
zombieNode.position.y = yPos
zombieNode.size = CGSize(width: width, height: width)
addChild(zombieNode)
}
When a collision appears this function get activated:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if(firstBody.name == "Player" && secondBody.name == "Zombie"){
changeHealthPointsForZombieWithNode(secondBody, points: player!.attack)
} else if(firstBody.name == "Zombie" && secondBody.name == "Player"){
changeHealthPointsForPlayer(secondBody, points: zombie!.attack)
print(player!.healthPoints)
}
}
func changeHealthPointsForZombieWithNode(node: SKSpriteNode, points: Int) {
for zombie in Zombies {
if zombie.node == node {
zombie.healthPoints -= points
print(zombie.healthPoints)
if(zombie.healthPoints <= 0){
zombieNode.removeFromParent()
}
return
}
}
}
func changeHealthPointsForPlayer(node: SKSpriteNode, points: Int) {
player!.healthPoints -= points
if(player!.healthPoints <= 0){
playerNode.removeFromParent()
gameOver = true
}
}
I want to subtract the health points of the zombie depending on the attack of the player and other way around. When the player hits the zombie the zombie should lose life points. When the zombie hits the player the player should lose life points. Every player/zombie got health points and an attack value. How do you access them in my didBeginContact function? I also have multiple zombies stored here var Zombies:[Zombie] = [] because every zombie should have its universal values. How is it possible that just the zombie I attack loses health points.
PS: My class Playerlooks like this:
class Player{
var node: SKSpriteNode
var xPos = CGFloat()
var yPos = CGFloat()
var healthPoints: Int
var attack: Int
init(node: SKSpriteNode, healthPoints: Int, attack: Int){
self.node = node
self.healthPoints = healthPoints
self.attack = attack
}
}

First you can do is compare your firstBody or secondBody object (one that would be zombie after checking) with node field in your Zombie object in loop like this:
func changeHelthPointsForZombieWithNode(node: SKSpriteNode, andPoints points: Int) {
for zombie in Zombies {
if zombie.node == node {
zombie.healthPoints -= points
return
}
}
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if(firstBody.name == "Player" && secondBody.name == "Zombie"){
changeHelthPointsForZombieWithNode(secondBody, andPoints: Player.attack)
} else if(firstBody.name == "Zombie" && secondBody.name == "Player"){
changeHelthPointsForZombieWithNode(firstBody, andPoints: Player.attack)
}
}
This will be work if Zombie is a class type object, if it struct object you will need to use for var i = 0; i < Zombies.count; ++i loop and call object field form Zombies with index i
Second you can append fields you need to change insede the nodes like this:
class ZombieNode: SKSpriteNode {
var healthPoints: Int
var attack: Int
...
// create custom init method with this params
...
}
Then append to scene this objects of this type like this:
func addZombie(xPos: CGFloat, yPos: CGFloat){
var zombieNode = ZombieNode(imageNamed: "zombie", healthPoints: 100, attack: 10)
...
...
addChild(zombieNode)
}
And in this case you have access you need inside you find in didBeginContact method objects like this:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! ZombieNode
let secondBody = contact.bodyB.node as! ZombieNode
if(firstBody.name == "Player" && secondBody.name == "Zombie"){
secondBody.helthPoints -= Player.attack
} else if(firstBody.name == "Zombie" && secondBody.name == "Player"){
firstBody.helthPoints -= Zombie.attack
}
}
Edits for new code:
Your zombie objects are not removed from the scene because you have not deleted them. You have a variable zombieNode, whenever you create Zambia in this line of code:
zombieNode = SKSpriteNode(imageNamed: "zombie")
you every time assign it a new value, and is a result zombieNode is a pointer to the last created node. This approach works with the player because it is single object, and did not accept for a set of objects. In fact every time when your zombie died you tryed to remove this last created object. Thats why this was not worked. To fix it you need to do the following:
First of all you need do remove this line of code:
var zombieNode = SKSpriteNode()
In your addZombie method create new nodes like this:
let zombieNode = SKSpriteNode(imageNamed: "zombie")
And in changeHealthPointsForZombieWithNode method you can remove zombie nodes like this:
if(zombie.healthPoints <= 0){
node.removeFromParent()
}
It would be good as well, remove objects from Zombie array when you remove the node.

Related

No collisions being detected in subclassed nodes

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

Day 1 collision detection: swift 3

So I want a game made, this was striped from the Xcode SpriteKit sampler, pretty simple. It will evolve greatly as I get this key issue out of the way. It has a player, Wall's, and a door. Nodes are assigned, player works fine. Wall's attempted for children in self, but crashes with my comments removed. I have a guess as multiple nodes of same name? But the door, when assigned node, for some reason no matter what slowly falls, with no gravity ticked and no gravity coded.
Those are lesser concerns. I come to you today to pick at why my collisions might not be activating my collision argument functions, to enter the house.
Yes I am aware it says contact mapped to the event. It suits my theory I am pretty sure.
//
// GameScene.swift
// Sandbox
//
// Created by M on 7/1/16.
// Copyright © 2016 M. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
var playerNode : SKSpriteNode?
var wallNode : SKSpriteNode?
var doorNode : SKSpriteNode?
private var spinnyNode : SKShapeNode?
var furnishing : SKSpriteNode?
var playerCategory = 0x1 << 0
var wallCategory = 0x1 << 1
var doorCategory = 0x1 << 2
var pathCategory = 0x1 << 3
func nextRoom() {
let sceneNode = SKScene(fileNamed: "MyScene")
sceneNode?.scaleMode = .aspectFill
// Present the scene
if let view = self.view {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func loadRoom() {
let furnishing = SKSpriteNode(color: #colorLiteral(red: 0.2464724183, green: 0.05352632701, blue: 0.03394328058, alpha: 1), size:CGSize(width:25, height:25))
doorNode?.addChild(furnishing)
}
func enterHouse() {
let newWindow = CGSize(width: 500, height: 500)
doorNode?.scale(to: newWindow)
loadRoom()
}
func exitHouse(){
let oldWindow = CGSize(width: 100, height: 100)
doorNode?.scale(to: oldWindow)
}
override func sceneDidLoad() {
self.lastUpdateTime = 0
physicsWorld.contactDelegate = self
// Get nodes from scene and store for use later
self.playerNode = self.childNode(withName: "//player") as? SKSpriteNode
playerNode?.physicsBody = SKPhysicsBody(rectangleOf: (playerNode?.frame.size)!)
playerNode?.physicsBody?.isDynamic = true
playerNode?.physicsBody?.affectedByGravity = false
playerNode?.physicsBody?.categoryBitMask = UInt32(playerCategory)
playerNode?.physicsBody?.collisionBitMask = UInt32(wallCategory)
playerNode?.physicsBody?.contactTestBitMask = UInt32(doorCategory)
for child in self.children {
/*if child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
wallNode?.physicsBody?.isDynamic = false
wallNode?.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode?.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(child)
}
}*/
}
self.doorNode = self.childNode(withName: "door") as? SKSpriteNode
doorNode?.physicsBody?.affectedByGravity = false
doorNode?.physicsBody?.isDynamic = false
doorNode?.physicsBody = SKPhysicsBody(rectangleOf: (doorNode?.frame.size)!)
doorNode?.physicsBody?.categoryBitMask = UInt32(doorCategory)
doorNode?.physicsBody?.contactTestBitMask = UInt32(playerCategory)
}
func touchDown(atPoint pos : CGPoint) {
let fromX = playerNode?.position.x
let fromY = playerNode?.position.y
let toX = pos.x
let toY = pos.y
let resultX = toX - (fromX)!
let resultY = toY - (fromY)!
let newX = (playerNode?.position.x)! + resultX / 10
let newY = (playerNode?.position.y)! + resultY / 10
playerNode?.position.x = newX
playerNode?.position.y = newY
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func didBeginContact(contact: SKPhysicsContact) {
//this gets called automatically when two objects begin contact with each other
// 1. Create local variables for two physics bodies
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
// 2. Assign the two physics bodies so that the one with the lower category is always stored in firstBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if secondBody.categoryBitMask == UInt32(doorCategory){
enterHouse()
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(withDeltaTime: dt)
}
self.lastUpdateTime = currentTime
}
}
I presume this line is causing the crash
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
as you are force unwrapping (!) the wallNode size however looking at your code you never assign it to anything like so
wallNode = self.childNode(withName: "wallNode") as? SKSpriteNode
or in the for loop.
Try this code in your for in loop that should avoid crashes and assigns your wall node.
for child in self.children where child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode = child // Try this
if let wallNode = wallNode { // safely unwrap wall node to avoid crashes
wallNode.physicsBody = SKPhysicsBody(rectangleOf: (wallNode.frame.size))
wallNode.physicsBody?.isDynamic = false
wallNode.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(wallNode) // add wall node here instead if you are using your wallNode property
}
}
}
Hope this helps

How can I require two collisions on one of my nodes before removing it from screen?

I have a collision based game with missiles and bombs. Everything works perfect right now with 1 collision and it removes the node from screen.
Although I want to be able to make bomb1 of my "bombs" harder to destroy so I would like to require it being hit twice. How can I do this?
Here are the bombs
func enemies() {
let bomb1 = SKSpriteNode(imageNamed: "Bomb1")
let bomb2 = SKSpriteNode(imageNamed: "bomb2")
let bomb3 = SKSpriteNode(imageNamed: "Bomb3")
let enemy = [bomb1, bomb2, bomb3]
// Enemy Physics
for bomb in enemy {
bomb1.size = CGSize(width: 55, height: 55)
bomb2.size = CGSize(width: 55, height: 70)
bomb3.size = CGSize(width: 30, height: 30)
bomb.physicsBody = SKPhysicsBody(circleOfRadius: bomb.size.width / 4)
bomb.physicsBody?.categoryBitMask = PhysicsCategory.enemy
bomb.physicsBody?.collisionBitMask = PhysicsCategory.missile | PhysicsCategory.airDefense
bomb.physicsBody?.contactTestBitMask = PhysicsCategory.missile | PhysicsCategory.airDefense
bomb.physicsBody?.affectedByGravity = false
bomb.physicsBody?.dynamic = true
bomb1.name = "enemy1"
bomb2.name = "enemy2"
bomb3.name = "enemy3"
}
Here is the function thats called when the bomb hits the missile.
func collisionWithMissile(enemy : SKSpriteNode, missile : SKSpriteNode) {
enemy.physicsBody?.dynamic = true
enemy.physicsBody?.affectedByGravity = true
missile.physicsBody?.affectedByGravity = true
enemy.physicsBody?.mass = 10.0
missile.physicsBody?.mass = 5.0
enemy.removeAllActions()
missile.removeAllActions()
enemy.removeFromParent()
missile.removeFromParent()
enemy.physicsBody?.contactTestBitMask = 0
enemy.physicsBody?.collisionBitMask = 0
enemy.name = nil
score++
scoreLbl.text = "\(score)"
}
This may help with the error Im getting
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node != nil && contact.bodyB.node != nil {
let firstBody = contact.bodyA.node as! Bomb
let secondBody = contact.bodyB.node as! SKSpriteNode
// Bomb1 **********
if ((firstBody.name == "enemy1") && (secondBody.name == "missile")) {
collisionWithMissile(firstBody, missile: secondBody)
atomicExplosion(contact.bodyA.node!.position)
missileExplosion(contact.bodyB.node!.position)
}
You can subclass SKSpriteNode for your bombs and add a collision counter to this class. Inside your collisionWithMissile func you check the value of this counter and remove the bomb if a given value is reached otherwise you increase the counter.
Your subclass can look like this
class Bomb: SKSpriteNode {
var hitCount: Int
init(imageNamed: String, hitCount: Int) {
self.hitCount = hitCount
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The init method has hitCount as a parameter, so you can create easily Bombs with different strength. Your collisionWithMissile method could be like this. Just fill the if-else parts with your needed code.
func collisionWithMissile(enemy : Bomb, missile : SKSpriteNode) {
if enemy.hitCount <= 0 {
enemy.removeFromParent()
} else {
enemy.hitCount -= 1
}
}

How to create explosion effect using image frames sprite kit swift

So I created this game where you have to shoot at objects. Now, I have an imageset that replicates an object exploding. I would like to call those images to appear in a sequence so it looks like an explosion after the projectile hits the object. Obviously the images have to be called at the exact location of where the projectile hits the object. Does anyone have any idea on how to make this happen? Here is some code.
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
projectile.removeFromParent()
monster.removeFromParent()
playerScore = playerScore + 1
playerScoreUpdate()
if (playerScore > 100) {
let reveal = SKTransition.crossFadeWithDuration(0.3)
let gameOverScene = GameOverScene(size: self.size, won: true)
self.view?.presentScene(gameOverScene, transition: reveal)
}
}
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 & UInt32(laserCategory)) != 0 && (secondBody.categoryBitMask & UInt32(monsterCategory)) != 0 {
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
}
if playerScore > highScore() {
saveHighScore(playerScore)
println("New Highscore = " + highScore().description)
highScoreLabel.text = "Best score: \(highScore().description)"
} else {
println("HighScore = " + highScore().description ) // "HighScore = 100"
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "muteSound" {
if musicIsPlaying == true {
backgroundMusicPlayer.stop()
musicIsPlaying = false
} else if musicIsPlaying == false {
backgroundMusicPlayer.play()
musicIsPlaying = true
}
} else {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let projectile = SKSpriteNode(imageNamed: "boom")
projectile.setScale(0.6)
projectile.position = player.position
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.dynamic = true
projectile.physicsBody?.categoryBitMask = UInt32(laserCategory)
projectile.physicsBody?.contactTestBitMask = UInt32(monsterCategory)
projectile.physicsBody?.collisionBitMask = 0
projectile.physicsBody?.usesPreciseCollisionDetection = true
// 3 - Determine offset of location to projectile
let offset = touchLocation - projectile.position
// 4 - Bail out if you are shooting down or backwards
if (offset.y < 0) { return }
// 5 - OK to add now - you've double checked position
addChild(projectile)
// 6 - Get the direction of where to shoot
let direction = offset.normalized()
// 7 - Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
// 8 - Add the shoot amount to the current position
let realDest = shootAmount + projectile.position
// 9 - Create the actions
let actionMove = SKAction.moveTo(realDest, duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
if !isStarted {
start()
}else{
projectile.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}
}
}
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 10)
self.addChild(monster)
}
For you explosion you could create an SKSpriteNode that play the frames you mentioned:
1. You're going to need the images as an array of SKTextures. You said you've got you images in an image set so the easiest thing to do may be to create an array using a for loop, for example:
// I don't know how many images you've got, so I'll use 10.
var textures: [SKTexture] = []
for i in 0..<10 {
let imageName = "explosion\(i)"
textures.append(SKTexture(imageNamed: imageName))
}
Alternatively, which is what I would recommend, is to create a Texture Atlas of your images. (For more information on texture atlases see here) To create an atlas, make a folder with the extension .atlas and adding all your explosion images to it. (Then add this to your project). Here's an extension I wrote to get your sprites out of a texture atlas, ready for animation:
extension SKTextureAtlas {
func textureArray() -> [SKTexture] {
var textureNames = self.textureNames as! [String]
// They need to be sorted because there's not guarantee the
// textures will be in the correct order.
textureNames.sort { $0 < $1 }
return textureNames.map { SKTexture(imageNamed: $0) }
}
}
And here's how to use it:
let atlas = SKTextureAtlas(named: "MyAtlas")
let textures = atlas.textureArray()
2. Now you've got your textures you need to create an SKSpriteNode and animate it:
let explosion = SKSpriteNode(texture: textures[0])
let timePerFrame = // this is specific to your animation.
let animationAction = SKAction.animateWithTextures(textures, timePerFrame: timePerFrame)
explosion.runAction(animationAction)
3. Add the sprite to your scene and position it correctly. To add it in the correct place you could use the contactPoint variable on SKPhysicsContact, after checking it was the projectile hitting an object.
func didBeginContact(contact: SKPhysicsContact) {
// Other stuff...
explosion.position = contact.contactPoint
self.addChild(explosion)
}
Hope that helps!

iOS Swift didBeginContact not being called

I have been struggling for the past two days to get two SKSpriteNodes to register a collision and evoke didBegin#contact.
I've set their bit masks 'categoryBitMask', 'contactTestBitMask' and 'collisionTestBitMask' for both objects.
I've also set the 'dynamic' property for both to 'true'
initPhysics() seems to set up the physicsWorld okay.
All I'm expecting is that didBegin#Contact is called, but it is not
//Set up Physicsbody bit masks
let playerCarBitMask: UInt32 = 0x1 << 1
let slowCarBitMask: UInt32 = 0x1 << 2
//initPhysics
func initPhysics() {
println("(((((((((((((( Initiating Physicsbody ))))))))))))))")
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector.zeroVector
println("self.physicsWorld.contactDelegate = \(self.physicsWorld.contactDelegate)")
}
//setupPlayer
func setupPlayer() {
car = SKSpriteNode(imageNamed: "redCarUp")
car.setScale(2.0)
car.position = CGPoint(x: 800, y: 400)
car.zPosition = 100
car.name = "car"
gameNode.addChild(car)
let carBody = SKPhysicsBody(
rectangleOfSize: car.frame.size, center: car.position)
carBody.dynamic = true
carBody.categoryBitMask = playerCarBitMask
carBody.contactTestBitMask = slowCarBitMask
carBody.mass = 5
carBody.collisionBitMask = slowCarBitMask
car.physicsBody = carBody
println("carBody = \(carBody)")
println("carBody.dynamic = \(carBody.dynamic)")
println("carBody.mass = \(carBody.mass)")
println("carBody.categoryBitMask = \(carBody.categoryBitMask)")
println("carBody.contactTestBitMask = \(carBody.contactTestBitMask)")
println("carBody.collisionBitMask = \(carBody.contactTestBitMask)")
slowCar = SKSpriteNode(imageNamed: "blueCarUp")
slowCar.setScale(2.0)
let slowCarScenePos = CGPoint(
x: 680,
y: 2048)
slowCar.position = gameNode.convertPoint(slowCarScenePos, fromNode: self)
println("slowCar.position = \(slowCar.position) ****")
slowCar.zPosition = 80
slowCar.name = "slowCar"
let slowCarBody = SKPhysicsBody(
rectangleOfSize: slowCar.frame.size, center: slowCar.position)
println("slowCar = \(slowCar) ****")
slowCarBody.dynamic = true
slowCarBody.categoryBitMask = slowCarBitMask
slowCarBody.contactTestBitMask = playerCarBitMask
slowCarBody.mass = 5
slowCarBody.collisionBitMask = playerCarBitMask
slowCar.physicsBody = slowCarBody
gameNode.addChild(slowCar)
}
func didBeginContact(contact: SKPhysicsContact!) {
println("*******************PhysicsContact********************")
}
'didBeginContact' has been changed to 'didBegin' in swift 3
func didBegin(_ contact: SKPhysicsContact) {
//stuff
}
I had a code from swift 2 and 'didBeginContact' was sitting there but wasn't being called. After quite a white I figured out that the function was changed. So, I thought my answer could help someone.
If you want make a contact between car and slowCar you have to init the categoryBitMask of both physicsBodies (I think you did). See the code below to get contact between two physicsBodies. When there is a contact it returns your display function :
//init your categoryBitMask :
let carCategory:UInt32 = 0x1 << 0
let SlowCarCategory:UInt32 = 0x1 << 1
//init car
car.physicsBody?.categoryBitMask = carCategory
car.physicsBody?.contactTestBitMask = slowCarCategory
//init slowCar
slowCar.physicsBody?.categoryBitMask = slowCarCategory
slowCar.physicsBody?.contactTestBitMask = CarCategory
// set your contact function
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 & carCategory) != 0 && (secondBody.categoryBitMask & slowCarCategory) != 0)
{
displayfunction(firstBody.node as SKSpriteNode, car: secondBody.node as SKSpriteNode)
}
}
func displayFunction (slowCar : SKSpriteNode, car : SKSpriteNode)
It turned out to be a simple problem. In my original code I was setting parameters for the SKPhysicsBody detection frame like so:
let carBody = SKPhysicsBody(
rectangleOfSize: car.frame.size, center: car.position)
Similarly I was doing the same for the second node that I was testing physics collisions for.
Simply removing the 'centre:' parameters like so:
let carBody = SKPhysicsBody(rectangleOfSize: car.frame.size)
for the two sprite nodes solved the problem and the nodes now crash into each other and push themselves aside as expected.
Please Note that contact will not be detected between two static bodies
(node.physicsBody?.isDynamic = false)