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.
Related
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.
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!
I am making a game using cocos2d and SpriteBuilder. There is a hero sprite and coins, that should be collected by him. That's a horizontal scrolling game, so every time hero touches a coin, this coin changes it's x-coordinate 2000px to the right and new y-coordinate is generated at random. Using update method I move it to the visible area as a "new" coin. But when hero flyies by and doesn't collect it ,coin must change coordinates only when it's already off the screen, so I tried this solution:
-(void)update:(CCTime)delta{
_coin.position=ccp(_coin.position.x - delta * scrollSpeed, _coinY);
if (CGRectIntersectsRect(_hero.boundingBox,_coin.boundingBox)) {
_coinY=arc4random() % 801 + 100;
_coin.position=ccp(_coin.position.x + 2000.f,_coinY);
}
else if(_hero.position.x >= _coin.position.x + 150){
_coinY=arc4random() % 801 + 100;
_coin.position=ccp(_coin.position.x + 2000.f,_coinY);
}
It works,but after that I found a small bug(I am not sure, whether it's related to this code) : sometimes, when hero touches coin, hero is like pushed away to the left. I have no idea why.
How to fix it?
Is that way, that I am using for coin ,right?
I see 2 issues with your approach:
1. Hero bounces of the coin.
To fix this you need to make your coin a sensor. This way you will still be notified about collisions, but the hero will simply pass through the coin without actually hitting it.
I'm not sure if you can set this in SpriteBuilder, but probably you can enumerate all coins after loading the scene and set sensor property to YES:
coin.physicsBody.sensor = YES;
Things like this is one of the reasons I believe you first need to learn pure Cocos2D and only then use tools making your life easier such as SpriteBuilder. Which is a great tool, but in some cases you still need know what happens behind the scenes.
2. You're mixing physics with things like CGRectIntersectsRect
If you're using physics engine you need to detect collisions via collision delegate and not by checking CGRectIntersectsRect in update:.
It is hard to explain how to do this in a few sentences, so here is a nice tutorial that shows how to detect collisions in Cocos2D v3 and of course there is a chapter about this in my book.
By the way you shouldn't use update:at all when manipulating physics nodes, use fixedUpdate: instead.
I hope this will help you to solve your issue.
I have a little problem and can't know by myself the solution. I'm handling collisions with Sprite Kit and I have a problem when my hero collides with two objects at same time (example with the ground and a cube in the air).
I got booleans that tell when the hero is jumping and when he is running at a great speed and when he is running slow (example when he collides with a wall made of cubes).
In this last example my booleans got crazy and sometimes my hero just pass over the cubes cause the speed don't slow down. Sometimes the boolean "is jumping" activates too, so in resume it goes crazy and I think it's because the handling collisions method (didBeginContact) only allows two contact bodies, contact.bodyA and contact.bodyB.
I would like to know if I can edit a file to add a contact.bodyC and what file do I need to edit? and if yes I will handle with this, I think with three contact bodies I will be able to program all the posible cases. If not then I suppose I will have to remove those cube walls or change their category bit mask...
Maybe queuing the contacts and handle then in -update is what you need. For example:
Declare an instance variable called NSMutableArray *_contactQueue;
Add the contacts to the array:
-(void) didBeginContact:(SKPhysicsContact *)contact
{
[_contactQueue addObject:contact];
}
Create a method to handle each contact in sync with your game ticks:
-(void)processContactsForUpdate:(NSTimeInterval)currentTime
{
for (SKPhysicsContact * contact in [_contactQueue copy]) {
[self handleContact:contact];
[_contactQueue removeObject:contact];
}
}
Call this method from update:
[self processContactsForUpdate:currentTime];
Then implement your handle method that will handle the contact.
-(void) handleContact:(SKPhysicsContact *)contact
{
// What you are doing in your current didBeginContact method
}
You can only handle the contact of two bodies, but in this way it's synchronized with every frame. I learned about this following a SpriteKit tutorial at this tutorial
I discovered a lot of new info since I posted this question so I have completely rewritten it:
I have run into some problems while implementing gravity and jumping in my tile-based game.
Sometimes when my character lands between tiles after a jump, the characters falls through them.
The problem certainly is not that my speed is too high since my max speed is the same as my tile-size. Also, the problem does not occur after every time the character jumps. I can often make a jump, get to max velocity, and still land well. But sometimes the character just falls through.
My level exists of layers of tiles. Each layer has it's own collision-type. So every tile in the same layer, follows the same rules for collision.
In my current set-up all layers use per-pixel collision.
This is how I update the player coordinates
http://pastebin.com/qVc6gv6T
This is the class where I calculate my collissions.
http://pastebin.com/7GqrFih6
In the update I just do:
controls.Update(player);
physicsEngine.HandleCollissions(levelManager, player);
I imagine that the problem could be because the player gets moved to another tile after collission. Collides with the other tile. But that doesn't count since the other tile already has been checked for collision?
Or could it be because I use foreach instead of for-loops?
You say the error occurs after jumping. So, your character falls toward a tile (has some positive velocity.Y) and hits it. The collision code executes the // the player comes FROM ABOVE and collides with the tile block. You run a loop that sets player velocity to zero and moves the player out of collision. This loops calls a function using a term tile as an argument. I assume you loop through a set of tiles in order to collide with more than a single square. Am I right?
If I am, then the problem occurs after that first collision and correction. Now your player has a velocity.Y of 0, but they are colliding with a new tile. So in spite of your comment about the meaning of your else block, you may have more position changes happening:
if (player.Velocity.Y > 0)
else // if (moveVector.Y < 0) // what you commented
else // if (moveVector.Y <= 0) // what your else really means
Now sometimes (only when you have slightly mis-alligned per-pixel collisions) you have course corrections happening the the wrong direction. That would produce a fall-through effect. You could try setting a break point in the // the player comes FROM UNDER and collides with the tile block, and then running the program in a scenario that shouldn't cause player.WorldPositionY += 1; to happen. If it happens when it wasn't supposed to, then you have your culprit.
I am doing a lot of speculation here. I think you should post more code so that we can know for sure. But perhaps a paste bin would be appropriate place for it.