Okay I've searched around and I cannot find an answer to this problem.
I have 3 different node types that I want to handle collisions with. A meteor, a shot, and a station. I have the code for the collision handling between the shots and the meteors working perfectly, but I cannot for the life of me figure out how to respond to collisions (yes the collision is being detected but the response code isn't executing) between the station and a meteor. Here's my didBegin func:
func didBegin(_ contact: SKPhysicsContact){
print("contact")
let shot = (contact.bodyA.categoryBitMask == shotCategory) ? contact.bodyA : contact.bodyB
let stationBody = (contact.bodyA.categoryBitMask == stationCategory) ? contact.bodyA : contact.bodyB
let otherObject = (shot == contact.bodyA) ? contact.bodyB : contact.bodyA
if ((contact.bodyA == stationBody) && (contact.bodyB == otherObject)) {
print("collision!")
}
}
"contact" is being printed when a meteor collides with the station, but "collision!" is not being printed. I know its got something to do with the way the code is worded but i can't seem to get it to work no matter how I write/rewrite it
Try considering the order...
if (contact.bodyA == stationBody && contact.bodyB == otherObject) || (contact.bodyB == stationBody && contact.bodyA == otherObject)
Also you are setting the other object according to shot, so if shot is not one of the objects in the collision it might be a problem
if bodyA is the other object and bodyB the station for example, shot would be set to bodyB (because the categoryBitMask != shotCategory) and then the otherObject would be set to bodyA. Therefore contact.bodyB won't equal otherObject.
Related
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!
So I'm trying to work with two SKPhysicsBody objects. I was looking at some tutorials to help work with the collision mechanics and dealing with that.
I pulled this code out of one of the tutorials:
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 & ColliderType.Bird != 0) &&
(secondBody.categoryBitMask & ColliderType.Edge != 0)) {
birdCollideWithEdge(firstBody.node as SKSpriteNode)
}
}
My ColliderType is set up like this:
enum ColliderType:UInt32 {
case Bird = 1
case Edge = 3
}
The error I'm getting comes from this line:
if ((firstBody.categoryBitMask & ColliderType.Bird != 0) &&
(secondBody.categoryBitMask & ColliderType.Edge != 0))
The error is
Could not find an overload for '&&' that accepts the supplied
arguments
I've tried casting the 0 as UInt32 and also casting the firstBody.categoryBitMask & ColliderType.Bird as Int but that didn't help.
I also tried bringing the firstBody.categoryBitMask & ColliderType.Bird out of that and setting it as it's own variable thinking that maybe there was just too much going on in that if statement but that didn't fix anything either.
Can anyone help push me toward the right documentation that might help or let me know if this won't work under its current setup.
Thanks in advance
EDIT: Adding rawValue to the end of ColliderType.Bird fixed my errors.
You can use rawValue of your enum this way:
if ((firstBody.categoryBitMask & ColliderType.Bird.rawValue != 0) && (secondBody.categoryBitMask & ColliderType.Edge.rawValue != 0)) {
}
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let planet : UInt32 = 0b1
static let rocket : UInt32 = 0b10 }
planet.physicsBody = SKPhysicsBody(circleOfRadius: planet.size.width / 2)
planet.physicsBody?.dynamic = true planet.physicsBody?.categoryBitMask = PhysicsCategory.planet
planet.physicsBody?.contactTestBitMask = PhysicsCategory.rocket
planet.physicsBody?.collisionBitMask = PhysicsCategory.None
func planetCollidedWithRocket(rocket: SKSpriteNode, planet: SKSpriteNode) {
println("collision planet") }
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.planet != 0) &&
(secondBody.categoryBitMask & PhysicsCategory.rocket != 0)) {
planetCollidedWithRocket(firstBody.node as! SKSpriteNode, planet: secondBody.node as! SKSpriteNode)}
I'm currently using this to add collision detection to my game (I've taken only taken out the bits of code I think are relevant). The first time I ran the game on the simulator it worked fine, and I added a game over scene to the planetCollidedWithRocket method. But I tested it again today and for some reason the game is detecting a collision between the rocket and the planet when they aren't colliding! Can anyone see what Ive done wrong??
Your contact isn't set up correctly. This is what you have, and what it translates to:
if((firstBody.categoryBitMask & PhysicsCategory.planet !=0)
// This is checking if the firstBody *has* a categoryBitMask, but it is not evaluating what it is.
// In addition to that, it is requiring that the PhysicsCategory.planet does not equal zero, which it doesn't.
// This would evaluate to true every time, no matter what.
// It is also requiring that...
&& (secondBody.categoryBitMask & PhysicsCategory.rocket != 0))
// This is checking if the secondBody *has* a categoryBitMask, but it is not evaluating what it is.
// In addition to that, it is requiring that the PhysicsCategory.planet does not equal zero, which it doesn't.
// This would evaluate to true every time, no matter what.
What you need to do is check that you categoryBitMask equals one of your PhysicsCategory variables, such that when a planet and a rocket contact, something evaluates to true. Try this:
if ((firstBody.categoryBitMask == PhysicsCategory.planet) &&
(secondBody.categoryBitMask == PhysicsCategory.rocket)){
// Code Here
}
Alternatively, since you know the hard-coded numbers in your PhysicsCategory struct, you can evaluate to hard numbers:
if ((firstBody.categoryBitMask == 1) &&
(secondBody.categoryBitMask == 2)){
// Code Here
}
What this will do is check to see if the physicsBody's categoryBitMask equals certain numbers when they contact. If they do, something happens, if not, nothing happens.
I want a sprite to delete itself when touching another sprite. Right now when they touch, they just push each other.
I have this:
let alphaCategory: UInt32 = 0x1 << 0
let betaCategory: UInt32 = 0x1 << 1
I made the sprites dynamic and not affected by gravity
self.physicsworld.contactDelegate = self
alpha.physicsBody?.categoryBitMask = alphaCategory
alpha.physicsBody?.contactTestBitmask = betaCategory
and
beta.physicsBody?.categoryBitMask = betaCategory
beta.physicsBody?.contactTestBitmask = alphaCategory
I couldn't find anything in swift that made sense to me, but I think the problem is here
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.BodyB
beta.removeFromParent()
}
}
First you should set your firstBody & secondBody to the order of their collisionBitMask:
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 {
secondBody.removeFromParent()
}
This will prevent your sprites from colliding with anything (including each other). Set this where you set the other BitMask properties:
alpha.physicsBody.collisionBitMask = 0
beta.physicsBody.collisionBitMask = 0
This will actually cause problems as I found out last summer when working on a SpritKit game. If you modify the scene's node structure during any of the SKPhysicsContact callbacks the results are undefined according to Apple. What I was told after filing a bug on this was that you should never modify the scene Node structure from inside any of those callbacks. Instead, Apple says to simply set a flag and then deal with the scene modification in your update loop where it's safe.
Try:
secondBody = contact.bodyB