I have been going through a SpriteKit tutorial that makes a Flappy Bird Style Game. One of the issues I am having, is that it is firing off the code for collision detection incorrectly.
Sometimes, this goes perfect...it hits the ground, it fires the method for when it collides with the ground. However, at seemingly random times, it will hit the ground, and fire off the method for ground collisions anywhere from 2-6 times. It doesn't matter if any other nodes are present on the screen or not. I can sit and let it drop immediately, and sometimes I get the collision code correctly ran once, other times it runs several times. Is there something wrong in this code causing it to do that?
UPDATE: It seems to be where the two objects meet on multiple intersecting points. If object A intersects with object B at 3 points, it will fire 3 times. How do you keep it from doing this?
- (void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ((firstBody.categoryBitMask & pillerCategory) != 0 &&
(secondBody.categoryBitMask & flappyBirdCategory) != 0)
{
[self pillar:(SKSpriteNode *) firstBody.node didCollideWithBird:(SKSpriteNode *) secondBody.node];
}
else if ((firstBody.categoryBitMask & flappyBirdCategory) != 0 &&
(secondBody.categoryBitMask & bottomBackgroundCategory) != 0)
{
[self flappyBird:(SKSpriteNode *)firstBody.node didCollideWithBottomScoller:(SKSpriteNode *)secondBody.node];
}
}
- (void)pillar:(SKSpriteNode *)pillar didCollideWithBird:(SKSpriteNode *)bird
{
NSLog(#"Did collide with bird");
[self showGameOverLayer];
}
- (void)flappyBird:(SKSpriteNode *)bird didCollideWithBottomScoller:(SKSpriteNode *)bottomBackground
{
NSLog(#"Did collide with scroller");
[self showGameOverLayer];
}
The easiest way i would solve this problem is by using this.
1st
Create a BOOL called running.
BOOL running;
2nd
Set running to YES when the game started
running = YES;
3rd
Place an if statement around your collision code like so,
if(running == YES)
{
//do collision detection
}
else
{
//do nothing
}
You can also use this running bool to control various other useful parts such as your update method.
Related
I have a Cat node, and a Bird node. The bird nodes are nested together in a container node called a birdBlock. Everything is contained in a WorldNode. If I add a bird to the WorldNode, the Cat can interact with it appropriately, but when the birds are in the birdBlock, the Cat just shoves them out of the way and they go flying.
I am using the following to find my birds:
[worldNode enumerateChildNodesWithName:kBirdName usingBlock:^(SKNode *node, BOOL *stop)
{
SKSpriteNode *newBird = (SKSpriteNode *)node;
if (CGRectIntersectsRect(newBird.frame,cat.frame))
{
//Do Something
//This is never reached when the birds are in the SKSpriteNode birdBlock.
//They just get shoved all over the screen.
}
}];
The birds in the block have the correct name.
They are now being enumerated, but still do not interact with the cat other than flying around the screen.
Now I am doing this:
[[worldNode children] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
SKSpriteNode *blockNode = (SKSpriteNode *)obj;
if ([blockNode.name isEqualToString:kBirdBlockName])
{
[blockNode enumerateChildNodesWithName:kBirdName usingBlock:^(SKNode *node, BOOL *stop)
{
SKSpriteNode *nbird = (SKSpriteNode *)node;
NSLog(#"FOUND BIRDS HERE");
//THIS IS FOUND! But below still does not work
if (CGRectIntersectsRect(nbird.frame, cat.frame))
{
NSLog(#"Hit BIRD");
[nbird removeFromParent];
}
}
}
}];
So this does not work either. How do you change the coordinate system of a sprite?
You can search the node tree in many ways. The SKNode documentation explains it clearly under the section Searching the Node Tree.
If you have a node named #"node" then you can search through all descendants by putting a // before the name. So you would search for #"//node".
Try changing your constant kBirdName to equal #"//my_bird_name"
An example using a string literal would be -
[worldNode enumerateChildNodesWithName:#"//nodeName" usingBlock:^(SKNode *node, BOOL *stop)
{ // Do stuff here... }
I could not get this to work, so I just gave all the birds a physicsBody, and moved the detection to didBeginContact
Now it does not matter what their parent is and I don't need to worry about changing coordinates.
- (void)didBeginContact:(SKPhysicsContact *)contact
{
SKSpriteNode *firstNode = (SKSpriteNode *)contact.bodyA.node;
SKSpriteNode *secondNode = (SKSpriteNode *) contact.bodyB.node;
if ((firstNode.physicsBody.categoryBitMask == catCategory && secondNode.physicsBody.categoryBitMask == birdCategory) || (firstNode.physicsBody.categoryBitMask == birdCategory && secondNode.physicsBody.categoryBitMask == catCategory))
{
NSLog(#"DID HIT BIRD");
if (firstNode.physicsBody.categoryBitMask == catCategory) {
//do something to secondNode.
}
if (firstNode.physicsBody.categoryBitMask == birdCategory) {
//do something to firstNode.
}
}
}
I can see here how to use the update() function to monitor properties like "position" on an SKNode but I don't see how I would know how methods like [node.physicsBody applyImpulse:vector] has finished.
-(void)someMethod {
_monitorOn = YES;
[_node.physicsBody applyImpulse:CGVectorMake(10,10)];
}
-(void)update:(CFTimeInterval)currentTime {
if( _monitorOn == YES ) {
NSLog(#"node position: %f,%f", _node.position.x, _node.position.y);
}
// When will this be turned off?
}
Here are two ways to check if the effect of applyImpulse is completed:
if (_node.physicsBody.resting) {
// Node is at rest, do something
}
You'll often find that the resting property is never set because your sprite is moving very slowly (particularly with circle nodes). Therefore, it's better to check if the speed is nearly zero.
static inline CGFloat speed(const CGVector v)
{
return sqrtf(v.dx*v.dx+v.dy*v.dy);
}
if (speed(_node.physicsBody.velocity) < kSmallValue) {
// Node is moving very slowly, do something
}
applyImpulse simply adds some velocity to your _node; it only does so when you call it and is "finished" after one frame. I think what you're really looking for is when _node stops moving (which will happen the physics engine determines that _node's velocity is zero). To check for this, you can look at SKPhysicsBody's resting property. Simply check for it in your update: loop; when it's true your _node has stopped.
-(void)update:(CFTimeInterval)currentTime {
if( _monitorOn == YES ) {
NSLog(#"node position: %f,%f", _node.position.x, _node.position.y);
}
if( _node.physicsBody.resting ) {
NSLog(#"node is stopped");
}
}
Note: You'll probably want to set an addition flag somewhere that you can use to see if you should be checking if _node is resting, otherwise you're going to get a ton of "node is stopped" messages.
-(void)someMethod {
_monitorOn = YES;
_appliedImpulse = YES;
[_node.physicsBody applyImpulse:CGVectorMake(10,10)];
}
-(void)update:(CFTimeInterval)currentTime {
if( _monitorOn == YES ) {
NSLog(#"node position: %f,%f", _node.position.x, _node.position.y);
}
if( _appliedImpulse && _node.physicsBody.resting ) {
_appliedImpulse = NO;
NSLog(#"node is stopped");
}
}
I have two collisions in my game that happen when the main characters hits an obstacle, game over appears and one where it touches the ground and nothing happens. This are the codes
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString:#"ground"] || [contact.bodyB.node.name isEqualToString:#"ground"]) {
[hero land];
} else {
[self gameover];
}
How can I add a different main character/hero collision where it doesn't lead me to game over but a whole different outcome (Like a different reaction for the main character)
Try detecting contact for the other special cases:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
BOOL isContactWithGround = ([contact.bodyA.node.name isEqualToString:#"ground"] || [contact.bodyB.node.name isEqualToString:#"ground"]);
BOOL isContactWithHero = ([contact.bodyA.node.name isEqualToString:#"hero"] || [contact.bodyB.node.name isEqualToString:#"hero"]);
BOOL isContactWithOtherThing = ([contact.bodyA.node.name isEqualToString:#"otherThing"] || [contact.bodyB.node.name isEqualToString:#"otherThing"]);
if (isContactWithHero && isContactWithGround) {
[hero land];
} else if (isContactWithHero && isContactWithOtherThing) {
[self doSomethingElse];
else {
[self gameover];
}
}
I am making a game in which when 2 objects (object A and B) collide, the game gets over. Object A's position changes each time update is called. Object B's position is the same. The code is:
[self enumerateChildNodesWithName:#"objA" usingBlock:^(SKNode *node, BOOL *stop) {
if ([objB intersectsNode:node])
{
[node removeFromParent];
[self GameOver];
}
The problem is: I want objA NOT to disappear after the collision. So, for that I removed [node removeFromParent]; , but since update is called again and again. My number of nodes increases and the sound that i have added never seems to end. So, what i tried was adding:
[self performSelector:#selector(pauseGame) withObject:Nil afterDelay:0.1];
-(void) pauseGame
{
self.scene.view.paused = YES;
[self performSelector:#selector(gameOver) withObject:Nil afterDelay:0.1];
}
I had to use performSelector with delay, because putting self.scene.view.paused = YES; within the update wouldn't allow me to go to gameOver. However, I do not want any delays! is there a way to do this??
Thanks
Just use a state variable in your scene which will indicate the current state.
Use this state to run updates on your scene objects.
For example (pseudocode) :
update()
if (state == GAME_PLAY) {
// Update relevant game nodes
} else if (state == GAME_OVER) {
// Update only what needs to be updated when the game is over
}
This way you do not need to stop your entire scene and add only what is relevant to the current game state
I instance a sprite, and then when it collides with a second sprite, the child of that sprite is removed:
if (CGRectIntersectsRect(spriteOne.boundingBox, self.swat.boundingBox))
{
if (spriteOne.tag == 0){
[self removeChild:spriteOne cleanup:YES];
}
if (spriteOne.tag == 1){
[self removeChild:spriteOne cleanup:YES];
}
}
This works how I want it to, and the sprite disappears off the screen. However, it seems that the boundingBox is still there, even if the image is not, and this causes trouble with scoring, etc... So, what I would like to know is how to 'dis-activate' the sprite's boundingBox so that when the first time that the two sprites colide, the collision is detected, but any time after that, it is not.
Thanks in advance.
As far as I understand, you should have some issue with removing the sprite after a collision.
Would you try this?
if (CGRectIntersectsRect(spriteOne.boundingBox, self.swat.boundingBox))
{
if (spriteOne.tag == 0){
[spriteOne removeFromParentAndleanup:YES];
}
if (spriteOne.tag == 1){
[spriteOne removeFromParentAndleanup:YES];
}
}
Have you tried adding some NSLog traces to see whether the sprite is really removed?
You must be retaining spriteOne. If there is a good reason to keep it around, do this:
if ( spriteOne.visible && CGRectIntersectsRect(spriteOne.boundingBox, self.swat.boundingBox))
{
if (spriteOne.tag == 0){
spriteOne.visible=NO;
}
if (spriteOne.visible && spriteOne.tag == 1){
spriteOne.visible=NO;
}
}
Later, when you need spriteOne in play again, just set its visibility to YES;
If not, you have a leak, and do this:
if ( spriteOne && CGRectIntersectsRect(spriteOne.boundingBox, self.swat.boundingBox))
{
if (spriteOne.tag == 0){
[self removeChild:spriteOne cleanup:YES];
self.spriteOne=nil; // assumes you have a property for spriteOne
}
if (spriteOne && spriteOne.tag == 1){
[self removeChild:spriteOne cleanup:YES];
[spriteOne release]; // assumes no property for spriteOne
spriteOne=nil; // dont forget this ! beware of zombies
}
}