I'm making my first game with Swift and SpriteKit and while I've had collisions working properly for some time now, I recently noticed a huge bug. My collisions can either be the user with and enemy (spaceship against alien) user shooting an enemy (laser against alien). The latter works fine, but recently the collision detection when the ship touches an alien hasn't been working-- normally the alien should be removed from the scene when it touches the so that only a single life (1 of 3 total) is removed. However, the ship looses 3-6 lives upon touch now. Here's the code and where I'd assume the problem is:
This is one of a few contact functions that is then called in the general contact method.
func alien_ship_contact(contact:SKPhysicsContact){
var alien:SKNode? = nil
if contact.bodyA.categoryBitMask == PhysicsCategory.Alien && contact.bodyB.categoryBitMask == PhysicsCategory.Ship{
alien = contact.bodyA.node
}
else if contact.bodyB.categoryBitMask == PhysicsCategory.Alien && contact.bodyA.categoryBitMask == PhysicsCategory.Ship{
alien = contact.bodyB.node
}
else{
return
}
killOffAlien((alien)!)
aliensKilled = aliensKilled + 1
shipLives = shipLives-1
aShip.lives = aShip.lives - 1
print("ship/alien contact")
}
Here is the killOffAlien function:
func killOffAlien(alien:SKNode){
print("Kill off")
//alien.removeFromParent()
func stopMotion(){
alien.physicsBody?.categoryBitMask = 0
alien.physicsBody?.collisionBitMask = 0
alien.physicsBody?.contactTestBitMask = 0
alien.physicsBody?.dynamic = false
alien.physicsBody?.velocity = CGVector(dx:0, dy:0)
alien.removeActionForKey("facialMotion")
}
func removeAlien(){
alien.removeFromParent()
}
let stopMoving = SKAction.runBlock(stopMotion)
let fadeOut = SKAction.fadeOutWithDuration(1)
let removeFromParent = SKAction.runBlock(removeAlien)
let die = SKAction.sequence([stopMoving, fadeOut, removeFromParent])
alien.runAction(die)
}
And here is the general contact method:
func didBeginContact(contact:SKPhysicsContact){
alien_laser_contact(contact)
alien_ship_contact(contact)
....
Any help would be awesome, I would think that upon initial contact the alien has it's bitmask set off from Alien so that in itself would prevent future collisions as every alien should only be able to remove at max one life from the ship.
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
}
}
}
}
}
}
I'm working on a platform game where the character can climb ladders. Since the total length of each ladder varies, I have several ladders stacked on top of each other in SceneEditor. Upon entering a ladder, the contact delegate works fine and my code allows the character to move up the ladder. The problem I'm having is that as soon as the character moves off the first ladder segment, the didEnd method fires even though the character has entered the next ladder segment. I've gotten around it by given the ladder segments different category masks. Is this the only way to do it?
try overlapping the ladders by 1 rung, and then set a count whenever didBegin contact is fired increase the value by 1 whenever didEnd is called decrease the value by 1. at the end of the didEnd func check if onladderCount == 0. if it is, trigger whatever code is supposed to fire when the player is not on a ladder.
this assumes that you have a ladder class, and will have to put a property in the ladder class for onLadder to ensure that the += 1 isn't called multiple times.
var onladderCount: Int = 0
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if !ladder.onLadder {
ladder.onLadder = true
onladderCount += 1
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if ladder.onLadder {
ladder.onLadder = false
onladderCount -= 1
}
}
if onladderCount == 0 {
//trigger whatever code happens when they leave a ladder
}
}
I have a an iOS app coded in Swift 3 where a ball is shot and bounces off of bricks on the screen. If I have the brick being one PhysicsBody (a rectangle), I can't easily determine which side/corner of the brick is being hit. What I decided to do instead of this, is have each side of the brick be its own separate node. The issue I am having now, is that a ball can't be in contact with two nodes (say the left and bottom) at once. I am decreasing the value of the brick after every contact with the ball, which in turn is decreasing the value by 2 for this one hit. How can I make it so that if a ball hits two nodes, only execute code for one contact?
Sometimes the below code gets executed twice, with the ball contacting with two brickNodes both times.
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
let countPoint = true
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & ballCategory) != 0 {
if (firstBody.node != nil && secondBody.node != nil){
if (secondBody.categoryBitMask & brickCategory) != 0 {
ballDidHitBrick(ballNode: firstBody.node as! SKShapeNode, brickNode: secondBody.node as! SKShapeNode, decreasePoint: countPoint)
} else if (secondBody.categoryBitMask & roofCategory) != 0 || (secondBody.categoryBitMask & rightWallCategory) != 0 || (secondBody.categoryBitMask & leftWallCategory) != 0 || (secondBody.categoryBitMask & bottomCategory) != 0 {
ballDidHitWall(ballNode: firstBody.node as! SKShapeNode, wallNode: secondBody.node as! SKShapeNode)
} else {
//Nothing as of yet
}
}
}
}
So going along with what Steve has said above, I implemented the code below and I am no longer having dual contacts per update:
if !bricksHit.contains("\(secondBody.node?.name ?? ""), \(firstBody.node?.name ?? "")") {
//If ball hasnt hit the object more than once
bricksHit.append("\(secondBody.node?.name ?? ""), \(firstBody.node?.name ?? "")")
ballDidHitBrick(ballNode: firstBody.node as! SKShapeNode, brickNode: secondBody.node as! SKShapeNode, decreasePoint: countPoint, contact: contact)
}
I also added in the below to my code, which clears the bircksHit after every update:
override func didFinishUpdate() {
bricksHit.removeAll()
}
I would scrap the multiple nodes with multiple bodies, that would yield terrible performance if you have many blocks.
Instead, you should process your work in steps.
During your didBegin phase, you need to keep track of where the contact point is. This can be done with userData
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
let countPoint = true
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & ballCategory) != 0, (secondBody.categoryBitMask & brickCategory) != 0 {
let userData = firstBody.node!.userData ?? [String,AnyObject]()
let contactPoints = userData["contactPoints"] as? [CGPoint] ?? [CGPoint]()
contactPoints.append(contact.contactPoint) //if need be add other info like the vector or relative to the node
userData["contactPoints"] = contactPoints
}
}
Then in a process later on, like didSimulatePhysics You can evaluate the nodes that were contacted, determine the priority of contact (like the bottom would supersede the sides, or if the velocity x > velocity y, sides, whatever you need to do) and react in this manner.
Note this is only sample code, it will not work verbatim. You will need to structure it into your code to get it to properly work.
Yep - this happens. The consensu of opinion seems to be that if there are multiple simultaneous contact points between 2 physics bodies, SK will call didBegin for every contact point, resulting in multiple calls (in the sam game loop) for the same pair of physics bodies.
The way to handle it (you can't get sprite-kit to NOT call didBegin multiple times in some circumstances) is to make sure that your contact code accommodates this and that handling the contract multiple times does not cause a problem (such as adding to the score multiple times, removing multiple lives, trying to access a node or physicsBody that has been removed etc).
Some things you can do include:
If you remove a node that is contacted, check for it being nil before
you remove it (for the duplicate contacts)
Add the node to a set and then remove all the nodes in the set in didFinishUpdate
Add an 'inactive' flag' to the node's userData
Make the node a subclass of SKSpriteNode and add an 'inactive' property (or similar)
Etc etc.
See this question and answer about SK calling didBegin multiple times for a single contact:
Sprite-Kit registering multiple collisions for single contact
Also SKPhysicsContact contains not only details of the 2 physics bodies that have collided, but also the contact point. From this, and the position properties of the 2 nodes involved, you could indeed calculate which side/corner of the brick is being hit.
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!
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.