Physics bodies other than my player won't call didBeginContact when colliding - objective-c

In my game I have 4 bitmasks and everything is setup, yet didBeginContact only gets called when the first bitmask (playerCategory) collides with something. if 3 collides with 4, nothing happens, even though I have the contactTestBitMask set for them to collide.
myscene.h
self.physicsWorld.gravity = CGVectorMake(0.0, -2.45);
self.physicsWorld.contactDelegate = self;
self.player = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size: CGSizeMake(10.0, 20.0)];
self.player.position = CGPointMake(20.0, self.frame.size.height);
self.player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.player.size];
self.player.physicsBody.affectedByGravity = YES;
self.player.physicsBody.dynamic = YES;
self.player.physicsBody.categoryBitMask = playerCategory;
self.player.physicsBody.contactTestBitMask = enemyCategory;
self.player.physicsBody.collisionBitMask = tileCategory;
[self.gameNode addChild:self.player];
[...]
- (void) swipeRightHandler:(UISwipeGestureRecognizer *) recognizer {
NSLog(#"swipe right");
SKSpriteNode *attackRect = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(40, 5)];
attackRect.position = CGPointMake(self.player.position.x + 10, self.player.position.y);
attackRect.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:attackRect.size];
attackRect.physicsBody.categoryBitMask = attackCategory;
attackRect.physicsBody.contactTestBitMask = 255;
attackRect.physicsBody.collisionBitMask = enemyCategory;
attackRect.physicsBody.affectedByGravity = NO;
attackRect.physicsBody.dynamic = NO;
[self.gameNode addChild:attackRect];
[attackRect runAction:[SKAction moveBy:CGVectorMake(250, 0) duration:1.0] completion:^{
[attackRect removeFromParent];
}];
}
RandomLevelGenerator.m:
SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(20, 20)];
enemy.position = CGPointMake(visual_x, FLOOR_X + arc4random_uniform(MAX_Y - FLOOR_X));
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.physicsBody.categoryBitMask = enemyCategory;
enemy.physicsBody.contactTestBitMask = playerCategory | attackCategory;
enemy.physicsBody.collisionBitMask = attackCategory;
enemy.physicsBody.affectedByGravity = NO;
enemy.physicsBody.dynamic = NO;
[self.scene.gameNode addChild:enemy];
LevelGenerator.h:
static const uint32_t playerCategory = 0x1 << 0;
static const uint32_t tileCategory = 0x1 << 1;
static const uint32_t enemyCategory = 0x1 << 2;
static const uint32_t attackCategory = 0x1 << 3;
MyScene.m again:
SKPhysicsBody *firstBody, *secondBody;
if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
NSLog(#"%d - %d", firstBody.categoryBitMask, secondBody.categoryBitMask);
That last NSLog, only ever prints 1 - x, never 3 - x. I've also tried allowing other collisions and I can't get any of them to collide.

If the categoryBitMask for two bodies ANDed together is zero, they will not generate contact events. For instance this happens with playerCategory (1) and enemyCategory (4): 1 & 4 = 0 <== no contact events are generated, ever, between players and enemies. In that case it won't even get to check the contactBitMask flags.
If you want two bodies to generate contact events, they both have to have the same category bitmask flag set.
The default for categoryBitMask is 0xFFFFFFFF so that all bodies can contact with each other. Same for collisionBitMask because generally you want all bodies to contact and collide by default.
The only bit mask you really need to modify in most cases is the contactBitMask, whose default is 0, meaning no contact events are generated by default (to improve performance).
Only change the other bitmasks where changing contactBitMask alone does not suffice.
For example when you want contact events, but no collision feedback (= change in velocity/position of colliding bodies when they come in contact), then make sure that those body's collisionBitMask ANDed together is 0. This allows bodies to generate contact events, but pass through each other. Useful to create triggers, ie when the player enters an area you can let him pass but also trigger a game event.
Or when you want to use the same contact bitmasks for two different categories of bodies who should only collide/contact with other bodies sharing the same category - ie regular and "ghost" characters who should have the same contact behavior with everything else in the game, except they shouldn't contact/collide with their corporeal (non-ghost) counterparts. In that case, use the same contactBitMask and collisionBitMask for both bodies. Then set all flags in categoryBitMask for all other categories, but don't set the flags for the two categories representing the corporeal and ghost entities. Ie if corporeal category is 2 and ghost category is 8, the categoryBitMask should be: 0xFFFFFFFF - 2 - 8 = 0xFFFFFFF5 (lower 8 bits would be: 11110101)
Long story short: categoryBitMask flags should be cleared from 0xFFFFFFFF (rather than set to a specific bit) so that individual bodies in specific categories don't contact with each other but still contact with all other bodies.

Related

Sprite Kit Objective C

I am creating a game using sprite kit but I seem to have trouble with the bodyWithTexture when using it with collisions. bodyWithRectangle and circleOfRadius work fine, but when i use bodyWithTexture it looks like the didBeginContact method is being called more than once.
here is an example of the code i'm using
-(SKNode *) createPlayer
{
level3Player = [SKNode node];
player3Sprite = [SKSpriteNode spriteNodeWithImageNamed:#"character.png"];
level3Player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player3Sprite.size.width/2];
level3Player.physicsBody.categoryBitMask = playerCategory3;
level3Player.physicsBody.contactTestBitMask = platformCategory3 | rockCategory;
[level3Player setPosition:CGPointMake(self.size.height/2, screenHeightL3 *11)];
level3Player.physicsBody.affectedByGravity = NO;
player3Sprite.physicsBody.dynamic = YES;
level3Player.zPosition = 2;
[level3Player setScale:0.6];
[level3Player addChild:player3Sprite];
return level3Player;
}
-(void) addRocksL3
{
int randomNumber = arc4random_uniform(300);
rock1 = [SKSpriteNode spriteNodeWithImageNamed:#"AsteroidFire.png"];
rock1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rock1.size.width/2];
rock1.position = CGPointMake(self.size.width * 3, randomNumber);
rock1.physicsBody.categoryBitMask = rockCategory;
rock1.physicsBody.contactTestBitMask = playerCategory3;
rock1.physicsBody.dynamic = NO;
rock1.physicsBody.affectedByGravity = NO;
rock1.zPosition = 2;
[rock1 setScale:0.3];
[foregroundLayerL3 addChild:rock1];
[self addChild:rock1];
}
-(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((secondBody.categoryBitMask == platformCategory3) | redPlatformCategory)
{
level3Player.physicsBody.velocity = CGVectorMake(0, 100);
level3Player.physicsBody.affectedByGravity = YES;
player3Sprite.texture = [SKTexture textureWithImageNamed:#"goo5.png"];
SKAction *sound1 = [SKAction playSoundFileNamed:#"squish.wav" waitForCompletion:NO];
[self runAction:sound1];
gestureRec3.enabled = YES;
}
if(secondBody.categoryBitMask == rockCategory)
{
gestureRec3.enabled = YES;
playerL3.physicsBody.velocity = CGVectorMake(0, 200);
SKAction *playSound = [SKAction playSoundFileNamed:#"Hurt.wav" waitForCompletion:NO];
[self runAction:playSound];
hitCountL3++;
}
switch (hitCountL3)
{
case 1:
[health1Level3 removeFromParent];
[self healthNodelevel31];
break;
case 2:
[hit1L3 removeFromParent];
[self healthNodeLevel32];
break;
case 3:
[hit2L3 removeFromParent];
player3Sprite.texture = [SKTexture textureWithImageNamed:#"splat.png"];
[self gameOverSplatLevel3];
didDie3 = true;
SKAction *playSplat = [SKAction playSoundFileNamed:#"splat.wav" waitForCompletion:NO];
[self runAction:playSplat];
break;
}
when i use this code my character will sometimes take 1 hit and sometimes take all 3 hits when i collide with the rock. I could use circleOfRadius which works fine, but it's not what I am really looking for. Is there anyway i could use bodyWithTexture so my character only takes 1 hit each time?
If you are experiencing multiple collisions eg. didBeginContact is called multiple times, you have few options...
Without looking at your code, lets say you have a player and a rock. Each time when player collides with rock you want to remove the rock. So, you remove the rock from its parent. But before that, you make this change in your code (pseudo code):
if([rockNode parent]){
[rockNode removeFromParent];
}
The other way would be to subclass SKSpriteNode and make a Rock class and to make a custom boolean property which will change its value once when first collision happens. But this is just unnecessary complication:
if(rockNode.canCollide){
[rockNode removeFromParent];
rockNode.canCollide = NO;
}
I had many problems with correct amount of collisions. Sometimes it would get one, sometimes none. So I tried this and it works. The only thing to change is in didBeginContact method.
I will presume that you declared categories like this:
//define collision categories
static const uint32_t category1 = 0x1 << 0;
static const uint32_t category2 = 0x1 << 1;
static const uint32_t category3 = 0x1 << 2;
Try to replace your code in didBeginContact with this one. I remember that correct collisions finally got to work after I did this.
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKNode *newFirstBody = contact.bodyA.node;
SKNode *newSecondBody = contact.bodyB.node;
uint32_t collision = newFirstBody.physicsBody.categoryBitMask | newSecondBody.physicsBody.categoryBitMask;
if (collision == (category1 | category2))
{
NSLog(#"hit");
}
}
Hope it helps

sprite kit - objective c: slow fps when I create a lot nodes

I wanted to create a space background so I make a for loop to create the stars. Here is the code:
for (int i = 0; i<100; i++) {
SKShapeNode *star= [SKShapeNode shapeNodeWithPath:Path.CGPath];
star.fillColor = [UIColor whiteColor];
star.physicsBody = nil;
int xposition = arc4random()%960;
int yposition = arc4random()%640;
star.position = CGPointMake(xposition, yposition);
float size = (arc4random()%3 + 1)/10.0;
star.xScale = size;
star.yScale = size;
star.alpha = (arc4random()%10 + 1 )/ 10.0;
star.zPosition = -2;
[self addChild:star];
}
But it takes a lot from my cpu. when the code is activated the cpu at top 78%.(I check the code in the iPhone simulator);
Somebody know how to fix it? thanks.
Your physics bodies continue to calculate even when off of the screen. You will need to remove them once they go out of the frame, otherwise everything will slow to a crawl. (And to echo what others have stated you will eventually need a real device).
From this document: Jumping Into Sprite Kit
You can implement the "Did Simulate Physics" method to get rid of the stars that fell from the bottom of the screen like so:
-(void)didSimulatePhysics
{
[self enumerateChildNodesWithName:#"star" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0)
[node removeFromParent];
}];
}
Note that you will first need to set the name of your star shapes by using the name property like so:
star.name = "star"

SpriteKit collision prevent from pushing out

I have this problem:
I have node A and node B.
What i want is when collision between them happens (node A collides with the top of B), node B won't be pushing node A a bit up. Because as it is now, when A collides with B, its being pushed back a bit, and collision instantly ends(didEndContact is called). So when im like colliding with that B, collision instantly ends, then starts again, then ends, then starts again.... What i want is that if A collided with B, A will lay on this B. I noticed i could achieve that by setting A.dynamic = NO, but i need to apply velocity to A, and if its not dynamic, velocity wont work.
This is the code:
typedef NS_OPTIONS(uint32_t, CollisionCategory)
{
CollisionCategoryPlayer = 1 << 0,
CollisionCategoryDiggable = 1 << 1,
};
-(void)didMoveToView:(SKView *)view
{
self.playerNode = [SKSpriteNode spriteNodeWithImageNamed:#"player"];
self.playerNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerNode.size];
self.playerNode.physicsBody.categoryBitMask = CollisionCategoryPlayer;
self.playerNode.physicsBody.collisionBitMask = CollisionCategoryDiggable;
self.playerNode.physicsBody.contactTestBitMask = CollisionCategoryDiggable;
self.playerNode.position = CGPointMake(160, 520);
self.playerNode.name = #"player";
self.playerNode.physicsBody.allowsRotation = NO;
self.playerNode.physicsBody.friction = 0;
[self addChild:self.playerNode];
SKSpriteNode* spriteNode = [SKSpriteNode spriteNodeWithTexture:textToUse];
spriteNode.name = keyFromCoordinate(coord);
spriteNode.position = CGPointMake(160, 400);
spriteNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.size];
spriteNode.physicsBody.dynamic = NO;
spriteNode.physicsBody.categoryBitMask = CollisionCategoryDiggable;
spriteNode.physicsBody.collisionBitMask = CollisionCategoryPlayer;
spriteNode.physicsBody.contactTestBitMask = CollisionCategoryPlayer;
spriteNode.physicsBody.resting = YES;
[self addChild:spriteNode];
}
-(void)update:(NSTimeInterval)currentTime
{
self.playerNode.physicsBody.velocity = CGVectorMake(0, -200);
}
And again the problem is that didBeginContact is called, then didEndContact, then didBegin and so on, what i want is - did begin is only called once.
Setting the restitution = 0 for both physicsBodies helped

How to animate SKNodes or SKSpriteNode to collide and bounce?

I have a few questions regarding animating SKNodes and or SKSpriteNodes. First one is how do i get them to move once the view loads? How do i make them collide and bounce off each other and go into the other direction. I am using an SKScene for this. Also is there a way to group SKSriteNodes with SKLabels?
I have:
#import <SpriteKit/SpriteKit.h>
//constants for the collision bitmap
static const uint32_t classicCategory = 1 << 0;
static const uint32_t arcadeCategory = 1 << 1;
static const uint32_t frenzieCategory = 1 << 2;
#interface HomeScene : SKScene <SKPhysicsContactDelegate> {
BOOL isSoundActionCompleted;
}
#end
Here is where I declare the SKSpriteNodes
SKSpriteNode *classicMode = [SKSpriteNode spriteNodeWithImageNamed:#"bubble.png"];
classicMode.size = CGSizeMake(130, 130);
classicMode.position = CGPointMake(self.size.width/2, self.size.height/2+50);
classicMode.name = #"classicMode";
[self addChild:classicMode];
SKLabelNode *classicTitle = [SKLabelNode labelNodeWithFontNamed:#"Noteworthy"];
classicTitle.text = #"Classic";
classicTitle.fontSize = 25;
classicTitle.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
classicTitle.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
classicTitle.fontColor = [SKColor darkGrayColor];
classicTitle.position = CGPointMake(self.size.width/2, self.size.height/2+50);
classicTitle.name = #"classicTitle";
[self addChild:classicTitle];
The other one here
SKSpriteNode *arcadeMode = [SKSpriteNode spriteNodeWithImageNamed:#"bubble.png"];
arcadeMode.size = CGSizeMake(130, 130);
arcadeMode.position = CGPointMake(self.size.width/2+68, self.size.height/2-65);
arcadeMode.name = #"arcadeMode";
[self addChild:arcadeMode];
SKLabelNode *arcadeTitle = [SKLabelNode labelNodeWithFontNamed:#"Noteworthy"];
arcadeTitle.text = #"Arcade";
arcadeTitle.fontSize = 25;
arcadeTitle.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
arcadeTitle.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
arcadeTitle.fontColor = [SKColor darkGrayColor];
arcadeTitle.position = CGPointMake(self.size.width/2+68, self.size.height/2-65);
arcadeTitle.name = #"arcadeTitle";
[self addChild:arcadeTitle];
Collision:
//collision and contact detection
self.physicsBody.categoryBitMask = arcadeCategory;
//physics and collision detection
classicMode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:classicMode.size];
classicMode.physicsBody.dynamic = NO; //no gravity
classicMode.physicsBody.categoryBitMask = arcadeCategory;
arcadeMode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:arcadeMode.size];
arcadeMode.physicsBody.dynamic = NO; //no gravity
arcadeMode.physicsBody.categoryBitMask = classicCategory;
And collision detection:
//collision detection
classicMode.physicsBody.categoryBitMask = classicCategory;
classicMode.physicsBody.collisionBitMask = arcadeCategory;
classicMode.physicsBody.contactTestBitMask = arcadeCategory;
arcadeMode.physicsBody.categoryBitMask = arcadeCategory;
arcadeMode.physicsBody.collisionBitMask = classicCategory;
arcadeMode.physicsBody.contactTestBitMask = classicCategory;
1) first problem in your code is that two static bodies never collide make them dynamic. so make atleast one or both bodies dynamic.
2)secondly for linear movement use body.physicsBody.velocity property with dynamic body bodies.
3)change your bitmask categories
classicMode.physicsBody.categoryBitMask = classicCategory;
classicMode.physicsBody.collisionBitMask = arcadeCategory;
classicMode.physicsBody.contactTestBitMask = arcadeCategory;
arcadeMode.physicsBody.categoryBitMask = arcadeCategory;
remember that contactTestBitMask uses in spritekit when two bodies contact or collide with each other and collisionBitMask is used to prevnet them to overlap or pass through each other
i cut arcadeMode.physicsBody.collisionBitMask = classicCategory;
arcadeMode.physicsBody.contactTestBitMask = classicCategory;
from your code because you alreay define it in
classicMode.physicsBody.categoryBitMask = classicCategory;
classicMode.physicsBody.collisionBitMask = arcadeCategory;
classicMode.physicsBody.contactTestBitMask = arcadeCategory; so no need to redfine it
use contactTestBitMask and collisionBitMask only once when you want two category interact with each other
4) handle all your collision and collision logic inside didbegincontact and didEndContact

SpriteKit only detecting partial collision on physics body

I have a simple jumping game setup where an hero jumps over a ball rolling towards him.
there is no gravity setup on the scene
the hero jump straight up and then straight down. if the hero stays still "didBeginContact" is fired and I can detect the collision. If the front of the hero hits the rolling ball when jumping up or coming down "didBeginContact" is fired. However if the back half of the hero land on the ball as it is going under it, it does not detect the collision.
Does anyone have any idea why it only catches the collision on the front? This seems more like edge detection vs. object detection! is that possible?
static const uint32_t groundCategory = 0x1 << 1;
static const uint32_t obstacleCategory = 0x1 << 2;
static const uint32_t heroCategory = 0x1 << 3;
SKSpriteNode *hero = [[SKSpriteNode alloc] initWithColor:[SKColor redColor] size:CGSizeMake(100, 115)];
[hero setPosition:CGPointMake(HERO_UP_POSITION_X, HEIGHT_GAME / 2 + hero.size.height / 2 + 10)];
hero.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:hero.size];
[hero.physicsBody setAffectedByGravity:NO];
hero.physicsBody.usesPreciseCollisionDetection = YES;
hero.physicsBody.allowsRotation = NO;
hero.physicsBody.categoryBitMask = heroCategory;
hero.physicsBody.contactTestBitMask = obstacleCategory | groundCategory;
hero.physicsBody.collisionBitMask = 0;
hero.zPosition = 0;
hero.name = kHeroUpName;
[self addChild:hero];
obstacle = [[SKSpriteNode alloc] initWithImageNamed:kImgObstacleDown];
obstacle.name = #"obstacle";
[obstacle setPosition:CGPointMake(WIDTH_GAME + x, HEIGHT_GAME / 2 + y)];
obstacle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:obstacle.size.width / 2];
obstacle.physicsBody.usesPreciseCollisionDetection = YES;
[obstacle.physicsBody setAffectedByGravity:NO];
obstacle.physicsBody.categoryBitMask = obstacleCategory;
obstacle.physicsBody.contactTestBitMask = heroCategory;
obstacle.physicsBody.collisionBitMask = 0;
obstacle.zPosition = 1;
I figured out the problem.
I was checking if the object was past the hero in order to track score, If it was past the center of the hero I renamed it, so that it wouldn't get checked and scored again.
Thanks #0x141E your snippet made me realize that I was checking for names inDidBeginContact (Which fails) vs. checking for categoryBitMask
There are two test cases that needs to be in your didBeginContact: 1) if contact.bodyA is the hero and contact.bodyB is the obstacle and 2) if contact.bodyA is the obstacle and contact.bodyB is the hero. You are likely handling only one case. Here's an example of how to do that:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if ((contact.bodyA.categoryBitMask == heroCategory && contact.bodyB.categoryBitMask obstacleCategory)
|| (contact.bodyA.categoryBitMask == obstacleCategory && contact.bodyB.categoryBitMask == heroCategory)) {
// Do something here
}
}