I am making a game where I have a ball created as an SKSpriteNode that is colliding with some PhysicsBodies I do not want to. Here is my ball:
orangeBall = OrangeBall(path: ballTrajectory, color: UIColor.orange, borderColor: UIColor.black)
orangeBall.physicsBody = SKPhysicsBody(circleOfRadius: Physics.PhysicsNumbers.ballRadius)
orangeBall.position = Physics.PhysicsNumbers.ballRestPos
orangeBall.physicsBody?.categoryBitMask = CollisionsChecker.Ball
orangeBall.physicsBody?.collisionBitMask = CollisionsChecker.Frame | CollisionsChecker.Box | CollisionsChecker.Score
orangeBall.physicsBody?.contactTestBitMask = CollisionsChecker.Frame | CollisionsChecker.Box | CollisionsChecker.Score
orangeBall.physicsBody?.isDynamic = false
orangeBall.physicsBody?.affectedByGravity = false
addChild(orangeBall)
and I want it to detect that it has been in contact with this score physics body but not actually collide:
score = NetBoxes(path: scoreStationary, color: UIColor.red, borderColor: UIColor.red)
score.position = Physics.PhysicsNumbers.scoreRest
score.physicsBody = SKPhysicsBody(circleOfRadius: 50)
score.physicsBody?.categoryBitMask = CollisionsChecker.Score
score.physicsBody?.contactTestBitMask = 0
score.physicsBody?.collisionBitMask = CollisionsChecker.Ball
score.physicsBody?.affectedByGravity = false
score.physicsBody?.isDynamic = false
addChild(score)
(circleOfRadius is only for testing purposes) Every time I try to check my collisions it seems to not output what I want it to do:
func didBegin(_ contact: SKPhysicsContact){
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == CollisionsChecker.Ball && secondBody.categoryBitMask == CollisionsChecker.Score || firstBody.categoryBitMask == CollisionsChecker.Score && secondBody.categoryBitMask == CollisionsChecker.Ball{
print("Ball in hoop")
}
Essentially, my orangeBall which is a SKSphapeNode always collide with my Score PhysicsBody. It might also be that both physics bodies around my ball and score get into contact. I am at a loss, any help would be appreciated!
I have figured it out. I was recreating a physicsBody for my orangeBall later in the code. Forming another physicsBody around it making it collide with my other nodes!
Related
It's my first project with Swift and I'm trying to learn, but I'm stuck and can't find a solution online. Basically, it's a clone of pong, and everything is going good, until now.
My problem it's that I can't understand how to make a sound effect play, once my ball hit the paddles.
Everything else with the collisions is ok, and other sound like the goal one is playing fine. Just can't figure out how i can make a sound play when the two sprites collide.
Here's what I've written:
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.node?.name == "main" && contact.bodyB.node?.name == "ball" || contact.bodyB.node?.name == "main" && contact.bodyA.node?.name == "ball" || contact.bodyA.node?.name == "enemy" && contact.bodyB.node?.name == "ball" || contact.bodyB.node?.name == "enemy" && contact.bodyA.node?.name == "ball" {
ball.run(blipPaddleSound)
}
}
It's all wrong?
I’ve already tried to break the if statement, and already tried with make one of my node run the sound effect (ball.run(‘nameofthesound’). I’ve thought maybe it could be a problem with the file, but other effect don’t play as well in that part of the code. I don’t think there is a problem with the collision, because the ball bounce on the paddle and on the wall without problem
This is the part of the code where I have put all the collision and contact mask:
ball.physicsBody?.contactTestBitMask = 1
ball.physicsBody?.collisionBitMask = 3
ball.physicsBody?.categoryBitMask = 1
main.physicsBody?.contactTestBitMask = 3
main.physicsBody?.collisionBitMask = 1
main.physicsBody?.categoryBitMask = 2
enemy.physicsBody?.contactTestBitMask = 3
enemy.physicsBody?.collisionBitMask = 1
enemy.physicsBody?.categoryBitMask = 2
Most likely you are not setting the SKPhysicsContactDelegate to the Scene
You'll also find that a single collision can fire multiple triggers within the game cycle so you may want to put a variable controlling the sound play so that it doesn't play multiple times per collision
class GameScene: SKScene, SKPhysicsContactDelegate {
private var didCollide = false
func didBegin(_ contact: SKPhysicsContact) {
let object1 = contact.bodyA.node?.name
let object2 = contact.bodyB.node?.name
if object1 == "ball" || object2 == "ball" {
if object1 == "main" || object2 == "main" || object1 == "enemy"|| object2 == "enemy" {
//check if we have just collided within the last 0.3 seconds
if !didCollide {
//set the marker to true so that the sound doesn't repeatedly fire
didCollide = true
//play the sound
ball.run(blipPaddleSound)
//wait 0.3 seconds (give the ball a chance to move away from the object)
self.run(.wait(forDuration: 0.3) {
self.didCollide = false
}
}
}
}
}
}
In my game, I use SKSprite. Some collisions are not detected. I did 10 tries, collisions are working well but about 25% of collisions that are supposed to be detected are not detected. I have NO idea why, I tried many things. Collisions are only with nodes of the same category.
I have no idea why randomly some collisions are not made when I can obviously see them, do you have any idea? Thanks for your help.
Here is the code of didBeginContact:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody = contact.bodyA
var secondBody: SKPhysicsBody = contact.bodyB
if firstBody.categoryBitMask == secondBody.categoryBitMask {
listContacts.append([firstBody.node!,secondBody.node!])
}
}
}
Here is the code of didEndContact:
func didEndContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody = contact.bodyA
var secondBody: SKPhysicsBody = contact.bodyB
if contact.bodyA.categoryBitMask == contact.bodyB.categoryBitMask {
for i in listContacts{
if (i.contains(firstBody.node!) && i.contains(secondBody.node!)){
let findIndex = listContacts.indexOf { $0 == i }
listContacts.removeFirst(findIndex!)
}
}
}
Finally when I declare a new SKSpriteNode I set this:
rectangle.physicsBody = SKPhysicsBody(rectangleOfSize: rectangle.size)
rectangle.physicsBody?.dynamic = true
rectangle.physicsBody?.collisionBitMask = PhysicsCategory.None
usesPreciseCollisionDetection = true doesn't change anything so I don't use usePrecisionCollisionDetection
Every SKSpriteNode has his categoryBitmask and contactTestBitmask equal because only same SKSpriteNodes are supposed to collide.
Also:
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
Finally here is a short video of my game if you want to understand easily what happens (problem of collisions are between rectangles) https://www.youtube.com/watch?v=-pbmKwQiE9U
You seem to be checking if the categoryBitMask of body A equals body B.
if firstBody.categoryBitMask == secondBody.categoryBitMask {
listContacts.append([firstBody.node!,secondBody.node!])
}
}
This will only run an action if a node hits a node with the same categoryBitMask
You should be checking the collisionBitMasks to see if you have a collision, or you can check the name.
For an example using categoryBitMask you can do something like this:
func didBeginContact(contact: SKPhysicsContact) {
var sprite: SKSpriteNode!
if contact.bodyA.categoryBitMask == <your category bitmask> {
sprite = contact.bodyA.node! as! SKSpriteNode
}
else if contact.bodyB.categoryBitMask == <your category bitmask> {
sprite = contact.bodyB.node! as! SKSpriteNode
}
// Do something with sprite....
// You can also create another sprite, and assign it to the other body, and perform functions on it, or both A and B.
// It is good to have different functions you send can send the nodes to once you find out which ones they are.
}
You can do these checks to see which sprites are hitting each other.
I just fixed it! The reason was that in the function "touchesEnded", I had a recursive function that was deleting bad connections in the listContacts!
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. The Problem is that some zombies are killable and lose health and other (normally 1-3) are not able to lose health. These zombies who aren't able to lose health are the only one able to kill the player. Zombies that lose health can't deal damage(why?)? So there only able to do one thing(attack or lose health) although they should be able to do two things(attack and lose health).
Keep in mind that the two physics bodies described in the contact parameter are not passed in a guaranteed order. Inside the delegate function there is not such a thing as "A is the one colliding with B, not the other way around". There is only "A and B are colliding". That said, you have two options at least, depending on your game mechanics:
Make player AND zombie deal damage to each other every time they collide.
Make for player and zombie a subclass of SKSpriteNode with a property isAttacking, a boolean to determine when the entity should deal damage when detecting a contact with it. For the player this boolean might get "activated" every button/tap press. For the zombie, every once in a while, if close enough to the player.
Additionally, the way you implemented you will be getting doubled contact notifications. SpriteKit will detect that the player is in contact with the zombie, by the player contact mask and zombie category mask, and that the zombie is in contact with the player, by the zombie contact mask and the player category mask. You usually don't want this to happen. You should make only one of them detect it. I would suggest the player. This way there is no need to set the zombie contact mask. As long as you set zombie in the player's contact mask and the zombie's category mask, you will already have the detection (and once).
Alright, so I have contact detection set up between 2 nodes - savior and chicken1. This is set up here:
//This is within GameScene class
var screenTouches = Bool()
enum ColliderType:UInt32 {
case Savior = 1
case Chicken1 = 2
}
savior.physicsBody?.categoryBitMask = ColliderType.Savior.toRaw()
savior.physicsBody?.contactTestBitMask = ColliderType.Chicken1.toRaw()
savior.physicsBody?.collisionBitMask = ColliderType.Chicken1.toRaw()
chicken1.physicsBody?.categoryBitMask = ColliderType.Chicken1.toRaw()
chicken1.physicsBody?.contactTestBitMask = ColliderType.Savior.toRaw()
chicken1.physicsBody?.collisionBitMask = ColliderType.Savior.toRaw()
//This is outside of Gamescene class
//Collision detection
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == ColliderType.Savior.toRaw() && contact.bodyB.categoryBitMask == ColliderType.Chicken1.toRaw() ) {
chicken1.hidden = true
let chickenGrabbedLeft = SKAction.moveTo(CGPointMake(self.size.width * 0.1,self.size.height * 1.2), duration:0)
chicken1.runAction(chickenGrabbedLeft)
println("contact made")
} else if (contact.bodyA.categoryBitMask == ColliderType.Chicken1.toRaw() && contact.bodyB.categoryBitMask == ColliderType.Savior.toRaw()) {
chicken1.hidden = true
let chickenGrabbedLeft = SKAction.moveTo(CGPointMake(self.size.width * 0.1,self.size.height * 1.2), duration:0)
chicken1.runAction(chickenGrabbedLeft)
println("contact made")
}
}
When savior comes in contact with chicken1, I need it to look like chicken1 has disappeared. As it is, I have it so that chicken1 becomes hidden when it touches savior, but this isn't enough because savior still collides with it and the user can tell that the object is still there even if it isn't visible.
I don't want to delete chicken1 because I still need it to be present within the game. So I am now trying to get chicken1 to move back to its starting position (which is offscreen) when it touches savior. I did this by placing the SKAction in the above function.
It is not working. When savior touches chicken1, chicken1 still just gets hidden. It doesn't move. What should I do?
From your question, I assume that your function still runs even when chicken1 becomes hidden.
What you can do is to use a BOOL that changes to true when savior comes into contact with chicken1 and use that as a condition before running your action and change it back to false when you want it to no longer interact with those objects.
Alright, so I've been following various other SO links and trying to figure this out- I need to have contact detection between 2 nodes. Not collision detection, which I learned results in the nodes bouncing each other around. I don't want them to knock each other around, I just want to know when they touch.
Right now I have physics bodies for the 2 nodes, savior and chicken1 as well as the ground (ground) upon which savior sits. These are all set up here:
savior.physicsBody?.dynamic = true
savior.physicsBody?.allowsRotation = false
savior.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(savior.size.width, savior.size.height))
chicken1.physicsBody?.dynamic = true
chicken1.physicsBody?.allowsRotation = false
chicken1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(chicken1.size.width, chicken1.size.height))
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, groundTexture.size().height*2))
ground.physicsBody?.dynamic = false
I need to set up contact detection between savior and chicken1. There seem to be various ways to do this, but this is what I put together:
//Contact detection
self.physicsWorld.contactDelegate = self
savior.physicsBody?.categoryBitMask = saviorCategory
savior.physicsBody?.contactTestBitMask = animalCategory
savior.physicsBody?.collisionBitMask = 0
chicken1.physicsBody?.categoryBitMask = animalCategory
chicken1.physicsBody?.contactTestBitMask = saviorCategory
chicken1.physicsBody?.collisionBitMask = 0
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 == 0 && secondBody.categoryBitMask == 1 {
println("they made contact")
}
}
This code results in savior falling right through ground and going right through chicken1, with no contact detection because even when savior and chicken1 touch, nothing happens.
I need savior and ground to continue to collide, but I don't want savior and chicken1 to collide, just touch.
The program needs to execute something when they touch.
It's a mess but how can I fix this?
EDIT:
Here is what I have, animalCategory has been changed to chickenCategory for clarity and no contact is detected. Also savior still falls through ground.
self.physicsWorld.contactDelegate = self
var screenTouches = Bool()
let saviorCategory: UInt32 = 0x1 << 0
let chickenCategory: UInt32 = 0x1 << 1
savior.physicsBody?.categoryBitMask = saviorCategory
savior.physicsBody?.contactTestBitMask = chickenCategory
savior.physicsBody?.collisionBitMask = chickenCategory
chicken1.physicsBody?.categoryBitMask = chickenCategory
chicken1.physicsBody?.contactTestBitMask = saviorCategory
chicken1.physicsBody?.collisionBitMask = saviorCategory
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody
var secondBody : SKPhysicsBody
if contact.bodyA.categoryBitMask == chickenCategory && contact.bodyB.categoryBitMask == saviorCategory {
println("contact made")
savior.hidden = true
}
else if contact.bodyA.categoryBitMask == saviorCategory && contact.bodyB.categoryBitMask == chickenCategory {
println("contact made")
savior.hidden = true
}
}
Ok so it seems like all you want is for just detection that they touched, but you don't want them to push each other. I would in the didBeginContact where ever you put your collision detection between them , make their physicsbody = nil, so that when they collide it knows that they collide and when you put this it makes them go through each other. And if you want to put code to do something else just put that code before you make the physicsbody nil. Also if you want to put back their physicsbody, just put it back.
For anyone who is still stuck on this, if you want to detect that two sprites are touching each other without them actually having a physical effect on each other (i.e. causing movement), then the key is to set the collisionBitMask property of the physicsBody to 0:
node.physicsBody!.collisionBitMask = 0
This means that you will receive events in didBeginContact but the interacting objects will not cause an actual physical effect on each other.
This is how to do it:
Set up your category bit masks:
let saviorCategory: UInt32 = 0x1 << 0
let chickenCategory: UInt32 = 0x1 << 1
let groundCategory: UInt32 = 0x1 << 2
Set up the physics bodies:
func didMove(to: View) {
savior.physicsBody?.categoryBitMask = saviorCategory
savior.physicsBody?.contactTestBitMask = chickenCategory
savior.physicsBody?.collisionBitMask = groundCategory
chicken1.physicsBody?.categoryBitMask = animalCategory
chicken1.physicsBody?.contactTestBitMask = saviorCategory
chicken1.physicsBody?.collisionBitMask = groundCategory
ground.physicsBody?.categoryBitMask = groundCategory
ground.physicsBody?.contactTestBitMask = 0 // No contact detection for ground
ground.physicsBody?.collisionBitMask = UInt32.Max // Everything collides with the ground
physicsWorld.contactDelegate = self
// Rest of didMoveToView
}
Note: It isn't actually necessary to define chicken as contacting saviour and saviour as contacting chicken; you only need to define that one contacts the other, but it can make the code more readable to say that each contacts the other.
Implement didBegin:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case chickenCategory | saviourCategory:
print("Collision between chicken and saviour")
let saviour = contact.bodyA.categoryBitMask == saviourCategory ? contact.bodyA.node! : contact.bodyB.node!
saviour.hidden = true
default :
//Some other contact has occurred
print("Some other contact")
}
}
Don't forget to set your class as an SKPhysicsContactDelegate
Check out the examples here:
Attack button in SpriteKit