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)
Related
I wanted to have a collision detection between the avatar and the obstacle, so whenever something collides, it should print "collision", but even that doesn't work, so the problem is, that it doesn't detect any collision. And if it would collide, it should differentiate between the player and the obstacle.
class GameScene: SKScene, SKPhysicsContactDelegate {
let avatar = SKShapeNode(circleOfRadius: 20)
let avatarCategory: UInt32 = 0*1 << 0
let obstacleCategory: UInt32 = 0*1 << 1
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
createAvatar()
spawnObstacles()
}
func createAvatar() {
avatar.name = "avatarNode"
avatar.physicsBody = SKPhysicsBody()
avatar.physicsBody?.categoryBitMask = avatarCategory
avatar.physicsBody?.contactTestBitMask = avatarCategory
avatar.physicsBody?.collisionBitMask = 0
avatar.physicsBody?.usesPreciseCollisionDetection = true
avatar.physicsBody?.affectedByGravity = false
avatar.zPosition = 2
addChild(avatar)
}
func createRandomObstacle() {
let obstacle = SKShapeNode()
obstacle.name = "obstacleNode"
obstacle.physicsBody = SKPhysicsBody()
obstacle.physicsBody?.categoryBitMask = obstacleCategory
obstacle.physicsBody?.contactTestBitMask = obstacleCategory
obstacle.physicsBody?.collisionBitMask = 0
obstacle.physicsBody?.usesPreciseCollisionDetection = true
obstacle.physicsBody?.affectedByGravity = false
obstacle.zPosition = 2
addChild(obstacle)
}
func didBegin(_ contact: SKPhysicsContact) {
print("collision")
}
To start with, both avatarCategory and obstacleCategory are 0, because : UInt32 = 0*1 << 1 = 0, so let’s fix that:
let avatarCategory: UInt32 = 1 << 0
let obstacleCategory: UInt32 = 1 << 1
Now contactTestBitMask represents the object(s) you want to be notified about contacts with, so you need to change that:
avatar.physicsBody?.contactTestBitMask = obstacleCategory
and
obstacle.physicsBody?.contactTestBitMask = avatarCategor
Try that for now 😀
Edit: my step-by-step guide for collisions and contacts:
https://stackoverflow.com/a/51041474/1430420
And a guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420
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
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
I have created two sprites with physics bodies and handled their contact. How can I change the physics body and sprite image for one of sprites when the contact occurs? Please see relevant code below:
// circle Sprite
circleSprite.name = circleSpriteCategoryName
circleSprite.position = CGPointMake(2*self.frame.size.width/3, 2*self.frame.size.height/3)
circleSprite.zPosition = 10
self.addChild(circleSprite)
circleSprite.physicsBody = SKPhysicsBody(circleOfRadius: circleSprite.frame.size.width/2)
circleSprite.physicsBody?.friction = 0
circleSprite.physicsBody?.restitution = 1
circleSprite.physicsBody?.linearDamping = 0
circleSprite.physicsBody?.angularDamping = 0
circleSprite.physicsBody?.allowsRotation = false
circleSprite.physicsBody?.applyImpulse(CGVectorMake(4, -4))
// square sprite
let squareSprite = SKSpriteNode(imageNamed: "square.png")
squareSprite.position = CGPointMake(CGRectGetWidth(frame)*0.4, CGRectGetHeight(frame)*0.8)
squareSprite.physicsBody = SKPhysicsBody(rectangleOfSize: squareSprite.frame.size)
squareSprite.physicsBody?.friction = 0.0
squareSprite.name = squareSpriteCategoryName
squareSprite.physicsBody?.categoryBitMask = squareSpriteCategory
squareSprite.zPosition = 10
addChild(squareSprite)
// contact in didBeginContact
if firstBody.categoryBitMask == circleSpriteCategory && secondBody.categoryBitMask == squareSpriteCategory {
println("contact works")
}
EDIT 1
I would like to change update the physics body and image of circleSprite to have the following properties:
circleSprite = SKSpriteNode(imageNamed: "recto.png")
circleSprite.physicsBody = SKPhysicsBody(rectangleOfSize: circleSprite.frame.size)
circleSprite.physicsBody?.friction = 0.0
circleSprite.name = newSpriteCategoryName
circleSprite.physicsBody?.categoryBitMask = newSpriteCategory
EDIT 2
I managed to get change the sprite image thanks to help from Max_Power89. But the image has been reduced to the dimensions of the physics body. Please see the relevant code below:
if firstBody.categoryBitMask == circleSpriteCategory && secondBody.categoryBitMask == squareSpriteCategory {
if let newSprite = firstBody.node {
let newImage = SKTexture(imageNamed: "newSprite.png")
let action = SKAction.setTexture(newImage)
newSprite.runAction(action)
}
}
You can implement SKPhysicsContactDelegate
then in your didBeginContact delegate you can do something like this
func didBeginContact(contact: SKPhysicsContact) {
// Return whatever it's the other body
let other = contact.bodyA.categoryBitMask == PhisicsCategory.Player ? contact.bodyB : contact.bodyA
if other.categoryBitMask == PhisicsCategory.Playe1 {
//Change sprite
let deadTexture = SKTexture(imageNamed: "Your image name")
let action = SKAction.setTexture(deadTexture)
player1.runAction(action)
}
}
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!