I'm making a Space Shooter game on Scene Kit and I want the asteroids to explode when the ship gets contact with them, I don't want them to collide, I just want to know when they contact each other.
The problem is that I don't know how to implement a collision detection when the ship intersects the asteroid. Like for example, I want Xcode to log "COLLIDED" when they actually get contact with each other.
I already added the categoryBitMask and the collisionBitMask for both objects. So, how could I achieve this? By the way, I'm doing everything on Swift.
SceneKit doesn't offer an option for separating contact detection from collision resolution for dynamic bodies. (If one of your bodies is a kinematic body, it won't have collisions resolved against it, but it won't be movable through physics either.) File a feature request?
However, it sounds like your use case is compatible with collision resolution — because the asteroids are exploding, you don't need to care about the effects of the collision on an asteroid (just remove it from the scene and replace it with explosion VFX, smaller asteroids, space slug, whatever). If the ship is meant to survive the collision and you don't want it to be affected by the collision, just set the relative masses of the ship and asteroid so that the asteroid won't impart significant momentum.
To do things when a collision occurs, you need to set a contact delegate on your scene's physics world. In whatever class is serving as your contact delegate, implement the didBeginContact method to be notified when a collision occurs.
In that method, you'll need to look at the contact's nodeA and nodeB to find out what categories of bodies collided (and which is which). Once you know that, e.g., nodeA is a ship and nodeB is an asteroid (or vice versa), you can kill the asteroid, update your score, etc.
I got this to work with a much simpler answer.
On the node you want things to fall through, set the physics mass to a very small number:
ship.physicsBody.mass = 0.00000000000001;
At the end I figured out how to solve this.
First I added the categoryBitMask and the collisionBitMask for both objects, the spaceship and the asteroids.
ship.physicsBody?.categoryBitMask = 2
asteroid.physicsBody?.categoryBitMask = 4
ship.physicsBody?.collisionBitMask = 4
ball.physicsBody?.collisionBitMask = 2
Then I gave a dynamicBody() to both of them.
ship.physicsBody = SCNPhysicsBody.dynamicBody()
asteroid.physicsBody = SCNPhysicsBody.dynamicBody()
Then I added a NSTimer to help me with the contact detection to update each 0.1 seconds to see if there is a contact.
override func viewDidLoad()
{
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "checkContact", userInfo: nil, repeats: true)
}
func checkContact()
{
if (self.scene.physicsWorld.contactTestBetweenBody(ship.physicsBody, andBody: asteroid.physicsBody, options: nil) != nil)
{
println("CONTACT!")
}
}
I didn't needed to add the SCNPhysicsContactDelegate to my view, but is better to add it to be sure there are no syntax problems or warnings.
And that's it!
NOTE: It's important to add a dynamicBody() or staticBody() to our 3D objects, otherwise it won't have a contact. And it's important to add the dynamicBody() or staticBody() to our 3D objects BEFORE we add them as a child to our scene.
Hope this helps someone!
Related
In SpriteKit SKPhysicsBody is it possible to have an object you can pass through but not go back.
The idea is their is no collision in one direction so you go through and not go back, like a trap door.
I'm not quite sure one way physics are possible, but you should be able to mess with a physics body's collision bit mask while the game is running to achieve a similar affect.
So you have have your door in an open state, and when it detects the player is touching it*, it changes the bit mask so the player will collide with it. That should allow the player to go through one way, but not come back.
*In reality, have the door detect when the player is no longer touching the door via the player's physics body, and test the x or y location depending on if this is a trap door, or regular door. If the location is far enough away from the door, change the collision bit mask of the door so that the player can't go through.
The solution is just to change the collisionBitMask
func platformSolid() {
self.physicsBody?.collisionBitMask = kBIT_MASK_PLAYER | kBIT_MASK_WALL | kBIT_MASK_PLATFORM
}
func platformThrough() {
self.physicsBody?.collisionBitMask = kBIT_MASK_PLAYER | kBIT_MASK_WALL
}
self in this example is player Collision Bit Mask
In the delegate:
func didEndContact(contact: SKPhysicsContact) {
if contact.bodyB.categoryBitMask == kBIT_MASK_PLATFORM {
player.platformSolid()
}
}
So once the player has passed ( didEndContact ) you make the platform ( door ) solid.
The most straightforward way to do that would be to add two child sprites to the node representing top and bottom of the trap door. That way you can test which direction a colliding sprite is coming from, and enable/disable dynamics on the other as needed.
In your contact test of door and object, you need to check the direction the object is traveling (you can use velocity to get this). If the object is traveling in the direction that the door would block them, then you place a value on the doors categoryBitMask (set it in a way that it will AND with the nodes collisionBitMask to create a value > 0), otherwise you need to remove the doors categoryBitMask (or set it in a way it will not AND the nodes collisionBitMask so that it creates a value of 0)
My code currently spawns a new enemy every second so there are multiple spawned enemies on the screen at the same time. I am able to detect a collision but I cannot figure out how to detect which enemy was hit so that I can remove it from the screen. Currently when a collision occurs and I remove the node from the screen, the last spawn enemy is removed and not the specific enemy that was hit. Any ideas on how to detect which spawned enemy was hit?
I assume you're using SpriteKit?
When you create a new enemy, you should give it a name. To do this, do enemyChildNode.name = "enemy". Then, when a collision is detected, use enumerateChildNodesWithName to check which node has collided.
This is how you declare this method:
func enumerateChildNodesWithName(_ name: String,
usingBlock block: ((SKNode!,
UnsafeMutablePointer<ObjCBool>) -> Void)!)
For more information on this go here.
So, you should use it like this:
enumerateChildNodesWithName("enemy") { node, stop in
let enemy = node as! SKSpriteNode
//check if enemy is being hit
//if true: enemy.removeFromParent()
}
I hope this helps you out!
Loads of ways to achieve this, one of the easiest is to have your enemy-sprites, but in my mind the easiest is to use the contactDelegate for your scene's physicsWorld.
In didBeginContact(contact: SKPhysicsContact) you can get the two colliding nodes by accessing contact.bodyA.node & contact.bodyB.node. Then your enemyNodes only need to have a wasHit() function which runs a removeFromParent action. (Implemented either trough subclassing or an extension).
Now, you probably need some logic to sort out the different types involved in the collission, but you have the options of checking on both class-type and categoryBitMasks.
Recently I have found that some sprite nodes can't test the collision with others. After some days trying and googling finally I made them works as usual. I have realized that there seems to be some relations between the dynamic and category bit mask. My guess is that:
player.dynamic = false
enemy.dynamic = true
player.category = player
enemy.collision = player
In the conditions above, enemy can't test the collision with player, but I wanna to know more details, any help will be appreciated.
update:
In my game, I want some of the sprites fixed in the scene, whose dynamic should be set to false, but can be collided by the players or enemies. For example the ground, a tree, or some buildings. What should I do to deal with these properties correctly ?
Like
tree.dynamic = false
enemy.collision = tree //can't works
tree.collision = enemy //should I do this? Is there another way to do this?
Non-dynamic bodies don't collide. From Apple's docs:
The dynamic property controls whether a volume-based body is affected
by gravity, friction, collisions with other objects, and forces or
impulses you directly apply to the object.
In an iOS game that uses Sprite Kit along with the contact detection in Sprite Kit's build-in physics engine, I decrease the Hero's number lives by one each time he gets in contact with an enemy. This is done from the didBeginContact method.
However, it seems like that method is not just called once, when the contact begins, but called continuously as long as the Hero and the enemy overlaps: when I set a breakpoint in that method, I can see, that it is the exact same physics body instances that exist as contact.bodyA and contact.bodyB. The result is, that the Hero will lose multiple lives, even though he only passes one single enemy.
If the Hero meets the same enemy again later, he should get one more live subtracted, and therefore I cannot just maintain a seenEnemies hash set to deal with the problem above.
The question is now: how would you make sure that only one live is subtracted for each Hero/enemy contact?
The reason why the didBeginContact is being fired multiple times is because you have multiple contact points happening on concave shapes.
If you look at the picture below, you will see I have 2 sprites, a black star and a red rectangle. When the black star hits the red rectangle, it hits it on multiple points, circled in blue. Sprite Kit will then do a call for each line intersection, so that the developer can use the contactPoint variable for each of these contacts.
I had the same problem (score increasing multiple times for a single enemy destroyed and multiple life points being lost for a single instance of damage.) A user on the Apple forums thinks that it's a bug in [SKPhysicsBody bodyWithTexture:size:] but I don't believe that's the case, because it was happening with other constructors too.
First off, the categoryBitMask and contactTestBitMask are very important, obviously. Take a look at Apple's SpriteKit Physics Collisions sample code:
// Contacts are often a double dispatch problem; the effect you want is based on the type of both bodies in the contact. This sample this in a brute force way, by checking the types of each. A more complicated example might use methods on objects to perform the type checking.
// The contacts can appear in either order, and so normally you'd need to check
each against the other. In this example, the category types are well ordered, so
the code swaps the two bodies if they are out of order. This allows the code
to only test collisions once.
What I did to solve it was setting a flag after handling each condition. In my case, I was testing whether bodyA.node.parent was nil in didBeginContact, because I called removeFromParent() on the missile/enemy nodes to destroy them.
I think you should expect the event to fire multiple times and your code in there has to make sure it's processed only once.
I figured out easy solution:
Just change either body's categoryBitMask value to 0 or non-used value right after it detected contact.
For example:
if (firstBody.categoryBitMask == padCategory && secondBody.categoryBitMask == colorBallCategory) {
secondBody.categoryBitMask = 0;
// DO OTHER THING HERE
}
I came across the same issue. In my case the didBeginContact() was called many times (I counted up to 5 times) for one contact of a bullet with the enemy. As the bullet is a simple circle format, I agree with #SFX that it cannot be a bug just in Texture-Bodies. The tests have shown that there was no call to update() between the didBeginContact() calls. So the solution is simple (Swift):
var updatesCalled = 0
...
internal update() {
updatesCalled ++
}
...
internal func didBeginContact(contact: SKPhysicsContact) {
NSLog("didBeginContact: (\(contact.contactPoint.x), \(contact.contactPoint.y)), \(updatesCalled)")
if(updatesCalled == 0) {return} // No real change since last call
updatesCalled = 0
... your code here ...
}
I tried didEndContact() but that was not called at all. I didn't investigate further into this.
BTW: I just switched from Android, and I'm impressed by the easiness and stability of this System :-)
Here is an option that makes the player invulnerable after being hit for a set time:
A. Create a variable that makes the player invulnerable to losing a life after being hit for a few seconds.
Create a global Boolean variable called isInvuln (set to FALSE) and an NSTimeInterval called invulnTime.
In the method that handles the player and enemy making contact, check to see if isInvuln is False before taking a life. (if isInvuln is true ... do nothing)
If isInvuln is false, take a life then set isInvuln to true.
if(self.isInvuln == FALSE){
self.player.lives-=1;
self.isInvuln = True;}
Add to your updateWithCurrentTime:
if(self.isInvuln==True){
self.invulnTime += timeSinceLast;}
if (self.invulnTime > 3) {
self.isInvuln = FALSE:}
self.invulnTime= 0;
This will make it so that when an enemy and player collide, the player loses a life and becomes invulnerable 3 seconds. After that 3 seconds, the player can take damage again. If the enemy contacts the player within the 3 invulnerable seconds, the contact method does nothing. Hope this helps spark ideas to tackle your problem.
In my experience, didEndContact & didBeginContact are both called multiple times while the objects overlap. This is also happening in SceneKit using iOS 9, so I have to assume it's an intended behavior.
I'm working on a piece of code, and I want there to be gravity in the view but I want specific projectiles to defy gravity and fly across the screen. I know to get rid of the gravity on the whole view its just:
self.physicsWorld.gravity = CGVectorMake(0, 0);
But as stated I want gravity on the scene.
So I'm wondering if there is a way to take the gravity off one specific item? (i.e. the SKSpriteNode _debris item in my case)
By setting the physicsBody to not be affected by gravity.
E.g.
myNoGravityObject.physicsBody.affectedByGravity = NO;
See the documentation of SKPhysicsBody.
If you don't want a node to interact with physics calculations at all, but still want it to have a physicsBody (i.e. to make it begin to interact with things later on, or to check for collisions with other nodes), you can set
node.physicsBody.dynamic = NO;
This will cause the node to ignore gravity, as well as collisions, impulses, and the like. If you are setting up the contact delegate, note that at least one node in any given contact must be dynamic for the contact delegate to be notified of the event.