Swift Spritekit physicsBodies do collision bit masks in the SKScene editor - swift

I have two balls. I don't want them to collide with each other. I want them to collide with everything else. I made the two balls have different category bit masks of 2 and 3. All the other objects have the same category bit mask of 1. I have tried setting the collision bit masks of both balls to 1. I also read something that said that an operation is done to the collision bit mask and the category bit mask and if it is nonzero then there is a collision, so I made the balls have the collision bit mask as the category bit mask of the other. So they would be 0 and everything else would be nonzero and collide. None of this works, so how do I actually make them so it works the way I want them to.
These are some of the masks I tried

Do it in code, it's so much easier to read.
class GameScene: SKScene, SKPhysicsContactDelegate {
weak var ball1: SKSpriteNode?
weak var ball2: SKSpriteNode?
let noCategory:UInt32 = 0
let ball1Category:UInt32 = 0b1
let ball2Category:UInt32 = 0b1 << 1
let wall1Category:UInt32 = 0b1 << 2
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
ball1 = self.childNode(withName: "ball1") as? SKSpriteNode
ball2 = self.childNode(withName: "ball2") as? SKSpriteNode
wall1 = self.childNode(withName: "wall1") as? SKSpriteNode
ball1?.physicsBody?.categoryBitMask = ball1Category
ball1?.physicsBody?.collisionBitMask = noCategory
ball2?.physicsBody?.categoryBitMask = ball2Category
ball2?.physicsBody?.collisionBitMask = noCategory
wall1?.physicsBody?.categoryBitMask = wall1Category
wall1?.physicsBody?.collisionBitMask = ball1Category | ball2Category
}
}
Now you can easily see that wall1 collides with the ball1 and ball2 but the balls themselves don't collide with anything. With collisions only one of the physicsbodies needs to have the collisionbitmask set. Therefore you could add the ball1 and ball2Category to all physicsbodies collisionbitmasks except the opposite ball and the balls with collide with those physicsbodies but not each other.

Related

Swift - How to add scene boundaries for specific nodes?

I'm trying to develop an IOS game using SpriteKit, and I want to add a Physics body to the scene so that the player won't be able to go through the edges of the screen. At the same time, I want some nodes (for example - bombs that fall from the sky) to be able to go through the edges of the screen.
I know that I can use the following line to add a physics body to the scene:
self.physicsBody = SKPhysicsBody (edgeLoopFrom: self.frame)
My question is how can I allow a "bomb" object to go through such body while having a "player" object obligated to those boundaries.
The answer is relative to categoryBitMask and collisionBitMask of the involved physic bodies.
For example, for the scene:
if let scenePB = scene.physicsBody {
scenePB.categoryBitMask = 1
scenePB.collisionBitMask = 2 // collides with player
}
For the player:
if let playerPB = player.physicsBody {
playerPB.categoryBitMask = 2
playerPB.collisionBitMask = 1+4 // collides with scene and bombs
}
For any bomb:
if let bombPB = bomb.physicsBody {
bombPB.categoryBitMask = 4
bombPB.collisionBitMask = 2 // collides with player
}

SKSpriteNode physics body created from texture results in nil value

I'm currently working on a small iOS game. In its current iteration, 20 targets spawn and move across the screen space-invaders style, and you control a little ship to shoot and destroy them. The code for my targets, the player ship's bullets, and a simple collision detection function I've written in the interim are as follows:
class Red_Target: SKSpriteNode{
var game_scene: GameScene!
private var ship_texture: SKTexture!
convenience init(scale: CGFloat, game_world: GameScene){
self.init(texture: SKTexture(imageNamed: "Proto Target"))
self.ship_texture = SKTexture(imageNamed: "Proto Target")
self.setScale(scale)
game_scene = game_world
game_scene.addChild(self)
self.position = CGPoint(x: game_scene.view!.bounds.width/10, y: 9 * game_scene.view!.bounds.height/10)
//self.physicsBody = SKPhysicsBody(texture: ship_texture, size: self.size)
self.physicsBody = SKPhysicsBody(circleOfRadius: 13)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.categoryBitMask = CollisionType.Enemy.rawValue
self.physicsBody!.contactTestBitMask = CollisionType.Player_Bullet.rawValue
}
func move() {
self.run(space_invaders(scene: game_scene))
}
}
class PC_Bullet: SKSpriteNode{
convenience init(scale: CGFloat){
self.init(imageNamed: "Goodbullet")
self.setScale(scale)
self.physicsBody = SKPhysicsBody(circleOfRadius: 3)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.categoryBitMask = CollisionType.Player_Bullet.rawValue
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.contactTestBitMask = CollisionType.Enemy.rawValue
}
}
func didBegin(_ contact: SKPhysicsContact) {
contact.bodyA.node!.removeFromParent()
contact.bodyB.node!.removeFromParent()
}
}
This code, in its current iteration, works just fine. However, if the line defining the target's physicsbody as its texture is uncommented and the line defining physicsbody as circleOfRadius is removed, the game will consistently crash after the 5th target is destroyed, claiming that didBegin unwraps a nil value. Why does this only happen from physics bodies with textures? Is there any way I could change the code for this to work? I would love to be able to use the physics body from texture function later on, when working with more irregular shapes.
You are pulling a classic nooby mistake. You are removing your bodies too early. If you browse Stackoverflow you will find a plethera of ways to solve it.
The basic idea is do not remove your sprites until the end of the physics phase because your 1 sprite could have multiple contact points to handle. So come up with a way to flag sprites that need to be deleted, and remove them during the didSimulatePhysics function.

How to change node value

func createBall(){
ballS = SKNode()
let ball = SKSpriteNode(imageNamed: "Ball")
saw.zPosition = 2
ballS.addChild(ball)
self.addChild(ballS)
}
This creates a Ball node with z position 2. But while running the game, I want to create another ball that has a z position at 1. How can I do this, or do I have to make a whole new function that makes a ball node with z position 1?
If each ball sprite has to be a child of the ballS node, then you could do the following.
Define ballS as a property (i.e. after the class definition but before any functions)
let balls = SKNode()
in didMove(to:), add the ballS Node:
addChild(ballS)
Create a addBall function: (don't call it createBall unless all it does create the ball)
func addBall(withZPosition zPos: Int) {
let newBall = SKSpriteNode(imageNamed: "Ball")
newBall.zPosition = zPos
ballS.addChild(newBall)
}
Then simply call addBall(withZPosition:) to create and add a new ball:
addBall(withZPosition: 2)
If you want more control over the ball that is created (e.g. you want to change some of it's properties before adding it to the scene), you can make a createBall function that creates a new ball and returns the new node:
func createBall(withZPosition zPos: Int) -> SKSpriteNode {
let newBall = SKSpriteNode(imageNamed: "Ball")
newBall.zPosition = zPos
return newBall
}
and use this as follows:
let newBall = createBall(withZPosition: 2)
newBall.size = CGSize(x: 50, y:50)
ballS,addchild(newBall)
U can have a global variable so that whenever you want to change u set it and create the ball
so create a variable
var myZposition = 2
and create a ball at the start in didmove to view by calling createBall()
func createBall(){
ballS = SKNode()
let ball = SKSpriteNode(imageNamed: "Ball")
saw.zPosition = myZPosition
ballS.addChild(ball)
self.addChild(ballS)
}
then after these if you want to change the zPosition just set it at the end of didmove to view
myZposition = 1
Then whenever u create balls from here your zPosition would be 1. This would be an easy setup if you have lots of balls and implement a change in zPosition for reaching a specific area of the game
Hope this helped
I'd suggest to use an argument, like:
func createBall(z: Int) {
ballS = SKNode()
let ball = SKSpriteNode(imageNamed: "Ball")
saw.zPosition = z
ballS.addChild(ball)
self.addChild(ballS)
}
And use it as:
createBall(z: 2)

Detecting when two of the same colors collide

I have a square consisting of four different colors in the middle of my scene. At the same time, I have smaller squares of the same color randomly generated from each sides of the scene with the intention of colliding with the square in the middle. (Blue to blue, yellow to yellow, etc).
My goal is to have it set up so that when a blue square collides with a blue square or any of the like, it will .removeFromParent(). How should I go about doing this? Will post code if necessary.
Edit:
enum BodyType: UInt32 {
case blueSquare = 1
case redSquare = 2
case yellowSquare = 4
case greenSquare = 8
}
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
didBeginContact() {
switch(contactMask) {
case BodyType.redSquare.rawValue | BodyType.redSquare.rawValue:
let scoreLabel = childNodeWithName("scores") as! Points
scoreLabel.increment()
let firstNode = contact.bodyB.node
firstNode?.removeFromParent()
default:
return
}
}
First thing you should do is set up the contactTestBitMasks & categoryBitMasks on all of your SKSpriteNodes, like this -
struct PhysicsCatagory {
static let FirstPerson : UInt32 = 0x1 << 1
static let SecondPerson : UInt32 = 0x1 << 2
}
override func didMoveToView(view: SKView) {
...
firstPerson.SKPhysicsBody?.catagoryBitMask = PhysicsCatagory.FirstPerson
firstPerson.SKPhysicsBody?.contactTestBitMask = PhysicsCatagory.SecondPerson
...
secondPerson.SKPhysicsBody?.catagoryBitMask = PhysicsCatagory.SecondPerson
secondPerson.SKPhysicsBody?.contactTestBitMask = PhysicsCatagory.FirstPerson
...
}
This is just setting up the catagoryBitMask and the contactTestBitMask. The categoryBitMask will be equal to the object you are currently editing, whereas, the contactTestBitMask will be equal to the object you want the object to collide with.
Also, before we move on, we want to add the Contact Delegate to our scene.
class GameScene: SKScene, SKPhysicsContactDelegate{...
And then add the delegate to our scene -
override func didMoveToView(view: SKView) {
...
self.physicsWorld.contactDelegate = self
...
Next, you add the didBeginContact
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode!
let secondBody = contact.bodyB.node as! SKSpriteNode!
}
Lastly inside of that, test...
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode!
let secondBody = contact.bodyB.node as! SKSpriteNode!
if firstBody.color == secondBody.color{
firstBody.removeFromParent()
secondBody.removeFromParent()
}
}
Hope that helps! :D
Once you detect a collision, compare the colors of the colliding squares and if equal, call .removeFromParent(). If you post code I could try to give the specific methods that would help.
If you want to get fancy you could create a subclass for your squares with a colorTag property (1 = blue, 2 = yellow ect.) and then compare the tags of the colliding squares. Although I doubt the cost of comparing the colors is much.

Change texture of SKNode that is an SKSpriteNode

In a game I'm making I can get the node the user is touching with
var node = self.nodeAtPoint(positionInScene)
However, this gets me an SKNode (even if the node the user touches is an SKSpriteNode) and I cannot use node.texture to change its texture. How can I change that SKNode to an SKSpriteNode or change its texture? (Actions won't work here because I need to pause the scene and they won't work when the scene is paused).
Casting is what you are looking for.
For instance:
If you are sure that the casting will be successful and your SKNode() is actually a SKSpriteNode, you can do the following:
let s = SKNode()
let ss: SKSpriteNode = (s as? SKSpriteNode)!
However, it is always better to use the optional specially in casting:
let ss2: SKSpriteNode? = s as? SKSpriteNode