didBeginContact doesn't fire - ios

Any idea why my didBeginContact method is never firing? The sphere and rectangles are actually touching each other...
Here i declare the two categories
static const uint32_t rectangleCategory = 0x1 << 0;
static const uint32_t ballCategory = 0x1 << 1;
This method creates a sphere
- (SKSpriteNode *)createSphereNode
{
SKSpriteNode *sphereNode =
[[SKSpriteNode alloc] initWithImageNamed:#"sphere.png"];
sphereNode.name = #"sphereNode";
sphereNode.physicsBody.categoryBitMask = ballCategory;
sphereNode.physicsBody.contactTestBitMask = rectangleCategory;
return sphereNode;
}
This method creates a rectangle
- (void) createRectangle
{
SKSpriteNode *rectangle = [[SKSpriteNode alloc] initWithImageNamed:#"balk.png"];
rectangle.position = CGPointMake(randomBetween(0, self.size.width),
self.size.height);
rectangle.name = #"rectangleNode";
rectangle.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(rectangle.size.width/2)-7];
rectangle.physicsBody.usesPreciseCollisionDetection = YES;
rectangle.physicsBody.categoryBitMask = rectangleCategory;
rectangle.physicsBody.contactTestBitMask = ballCategory;
[self addChild:rectangle];
}
This is the didBeginContact method that doesn't fire
- (void) didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"Contact begin");
SKSpriteNode *firstNode, *secondNode;
firstNode = (SKSpriteNode *)contact.bodyA.node;
secondNode = (SKSpriteNode *) contact.bodyB.node;
if ((contact.bodyA.categoryBitMask == rectangleCategory)
&& (contact.bodyB.categoryBitMask == ballCategory))
{
CGPoint contactPoint = contact.contactPoint;
float contact_x = contactPoint.x;
float target_x = secondNode.position.x;
float margin = secondNode.frame.size.height/2 - 25;
if ((contact_x > (target_x - margin)) &&
(contact_x < (target_x + margin)))
{
NSLog(#"Hit");
self.score++;
}
}
}
Any ideas? All help is much appreciated!
Thanks in advance
Stijn

I originally suspected it could be the lack of a proper SKPhysicsContactDelegate as it was not mentioned in the original post. Upon inspection of the code however, this was not the case and the problem was simply that the SKPhysicsBody used for rectangle was created with the ill-fitting bodyWithCircleOfRadius: rather than the bodyWithRectangleOfSize:.
rectangle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: rectangle.size];
Original answer, not correct for this particular case, but leaving it here for anyone with a similar problem down the line:
There is no mention of the SKPhysicsContactDelegate anywhere to be seen in the code you supply. The didBeginContact: method is a delegate method for the physicsWorld property of your SKScene.
So if you for instance want to have the SKScene being its own delegate it must support the SKPhysicsContactDelegate protocol. e. g.:
#interface YourSceneSubclass : NSObject <SKPhysicsContactDelegate>
Then you need to set the delegate somewhere in your scene before the game starts proper...
self.physicsWorld.contactDelegate = self;

Related

SK Game - didBeginContact/didEndContact isn't getting called

I'm making a drag and drop game, and attempting to use physicsBodies to detect contacts between 1 static object and the object being dragged by the user.
I've searched on here for hours, and watched tutorials online, but all I keep seeing is contact detection working with dynamic nodes (such as projectiles). I don't need collisions because nothing happens to any of the nodes when they intersect, I just need contact detection to implement game logic.
I originally had a working solution using CGRectContainsPoint and CGRectContainsRect, but it's super buggy and after doing some research I realized that it's not the correct way to implement contact/collisions in SK.
I've got my bitMask setup like so:
//contact bit masks
typedef NS_OPTIONS(uint32_t, ContactCategory) {
ContactCategoryTile = 1 << 0, //0000
ContactCategoryKey = 1 << 1, //0010
ContactCategoryRotor = 1 << 2, //0100
};
My selected node setup like so:
#import "TileNode.h"
#import "Utilities.h"
#interface TileNode ()
#end
#implementation TileNode
+(instancetype)tileNodeAtPosition:(CGPoint)position
tileComboScore:(NSInteger)comboScore
tileArray:(NSArray*)array
randomNumber:(int)randomNumber
initialCombo:(NSInteger)initCombo
{
//custom node properties
TileNode *tileNode = [array objectAtIndex:randomNumber];
tileNode.position = position;
tileNode.size = CGSizeMake(23, 63);
//using label property from .h file and parenting to generated tileNode
tileNode.comboLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
tileNode.comboLabel.fontColor = [UIColor whiteColor];
tileNode.comboLabel.text = [NSString stringWithFormat: #"%d",initCombo];
tileNode.comboLabel.fontSize = 12;
tileNode.comboLabel.position = CGPointMake(13, 45);
[tileNode setupPhysicsBody];
[tileNode addChild:tileNode.comboLabel];
return tileNode;
}
-(void)setupPhysicsBody
{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
NSLog(#"tileframe %#", NSStringFromCGRect(self.frame));
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = ContactCategoryTile;
self.physicsBody.contactTestBitMask = ContactCategoryTile | ContactCategoryRotor | ContactCategoryKey;
self.physicsBody.collisionBitMask = 0;
}
My destination node type setup up like so:
#import "KeyNode.h"
#import "Utilities.h"
#implementation KeyNode
+(instancetype)keyNodeAtPosition:(CGPoint)position
{
//custom class properties for generic yellow nodes in UI
KeyNode *keyTile = [KeyNode spriteNodeWithImageNamed:#"key"];
keyTile.position = position;
keyTile.size = CGSizeMake(25, 65);
keyTile.anchorPoint = CGPointMake(0.5, 0.5);
keyTile.name = #"keyNode";
[keyTile setupPhysicsBody];
return keyTile;
}
-(void)setupPhysicsBody
{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
NSLog(#"keyframe %#", NSStringFromCGRect(self.frame));
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = ContactCategoryKey;
self.physicsBody.contactTestBitMask = ContactCategoryTile;
self.physicsBody.collisionBitMask = 0;
}
And my contact detection delegate method like so:
-(void)didEndContact:(SKPhysicsContact *)contact
{
NSLog(#"Working!");
//Condition for tile and key contact
if (contact.bodyA.categoryBitMask == ContactCategoryTile && contact.bodyB.categoryBitMask == ContactCategoryKey)
{
NSLog(#"Tile contact with key");
}
//condition for tile and tile contact
else if (contact.bodyA.categoryBitMask == ContactCategoryTile && contact.bodyB.categoryBitMask == ContactCategoryTile)
{
NSLog(#"Tile contact with tile");
}
//condition for tile and rotor contact
else if(contact.bodyA.categoryBitMask == ContactCategoryTile && contact.bodyB.categoryBitMask == ContactCategoryRotor)
{
NSLog(#"Tile contact with rotor");
}
}
And both nodes are being instantiated in initWithSize like so:
CGPoint position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 180);
TileNode *tileNode1 = [TileNode tileNodeAtPosition:position tileComboScore:self.comboScore tileArray:tileImagesArray randomNumber:randomTileGenerator initialCombo:self.initialCombo];
[self addChild:tileNode1];
KeyNode *keyNode1 = [KeyNode keyNodeAtPosition:CGPointMake(CGRectGetMidX(self.frame) - 100, CGRectGetMidY(self.frame) - 15)];
[self addChild:keyNode1];
Any help would be greatly appreciated.
I found the answer in a thread from last year that worked. You can work around wanting static bodies to have collisions.
If you set the gravity to be non-existent and make your static nodes dynamic it works:
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsBody.dynamic = YES;

How to make balls ignore collisions with each other

With sprite kit, I'm trying to make series of balls drop from the air through physics simulation, and I want them to drop without colliding with each other and bounce off. I just want them to go straight down and pass through each other. How should I make this happen with collisionBitMask and categoryBitMask ?
// Common.h
#ifndef Rainy_Poops_common_h
#define Rainy_Poops_common_h
static int poopSize_x = 20;
static int poopSize_y = 20;
static const uint32_t poopCategory = 0x1 << 1;
#endif
// MyScene.h
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
_timer++;
if (_timer % 3 == 0) {
SKSpriteNode * p = [[Poop alloc] init];
NSInteger random_x = arc4random_uniform([[UIScreen mainScreen]applicationFrame].size.width);
p.position = CGPointMake(random_x, [[UIScreen mainScreen]applicationFrame].size.height - 5);
[self addChild: p];
[self enumerateChildNodesWithName:#"poop" usingBlock:
^(SKNode *node, BOOL *stop) {
Poop *poop = (Poop *) node;
if (!poop.isDropping) {
poop.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(poopSize_x, poopSize_y)];
poop.isDropping = YES;
}
if (poop.position.y < 0) {
[poop removeFromParent];
}
}];
}
}
#import "Poop.h"
#implementation Poop
- (id)init {
self = [super initWithImageNamed:#"poop2.png"];
self.isDropping = NO;
self.name = #"poop";
self.size = CGSizeMake(poopSize_x, poopSize_y);
SKPhysicsBody *physicsPoop = self.physicsBody;
physicsPoop.collisionBitMask = 0;
physicsPoop.categoryBitMask = poopCategory;
physicsPoop.affectedByGravity = YES;
physicsPoop.mass = 100;
physicsPoop.allowsRotation = NO;
physicsPoop.dynamic = YES;
return self;
}
#end
I think your problem is related to this line -
poop.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(poopSize_x,poopSize_y)];
You initialise your categoryBitMask to 2 and collisionBitMask to 0 in the init of your node, but this line will reset the physicsBody for your poop and assign the default collisionBitMask and categoryBitMask values of 0xFFFFFFFF
set collisionButMask equal to 0 for no collision or equal to the categoryBitMask of the sprite you would like the balls to collide with.

Detecting contact no response

I'm trying to detect contact between my mover sprite and the bottom. I've created an category for the bottom sprite called worldCategory and after that i'm detecting collision using didBeginContact method. At the moment i do not get any kind of notification on the contact.
First i define the category using bitmasks:
Then i create the bottom SKSpriteNode and creates the physicsbody andcategory and contaact bitmasks:
static const uint32_t worldCategory = 1 << 1;
bottom = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(self.frame.size.width*2, 120)];
bottom.position = CGPointMake(0, 0);
[self addChild:bottom];
bottom.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:bottom.frame];
bottom.physicsBody.categoryBitMask = worldCategory;
mover.physicsBody.contactTestBitMask = worldCategory;
didBeginContact method:
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKSpriteNode *firstNode, *secondNode;
firstNode = (SKSpriteNode *)contact.bodyA.node;
secondNode = (SKSpriteNode *) contact.bodyB.node;
int bodyAA = contact.bodyA.categoryBitMask;
if (bodyAA == worldCategory) {
NSLog(#"Contact");
SKTransition *reveal = [SKTransition doorsCloseVerticalWithDuration:1.0 ];
EndScene *newScene = [[EndScene alloc] initWithSize: CGSizeMake(self.size.width,self.size.height)];
// Optionally, insert code to configure the new scene.
[self.scene.view presentScene: newScene transition: reveal];
}
}
Edge-based bodies do not generate contact events. Since you create rectangles you should use a polygon body instead.

sprite kit collision detection with child sprite

I'm trying to detect collisions between two sprites but I'm unable to do this with a child sprite.
self.player = [[Player alloc] initWithImageNamed:#"player"];
self.player.position = CGPointMake(150, 75);
[self addChild:self.player];
_object = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
_object.position = CGPointMake(-40, 27);
[self.player addChild:_object];
then I have collision detetion like this
- (void)checkCollisions {
[self.map enumerateChildNodesWithName:#"enemy"
usingBlock:^(SKNode *node, BOOL *stop){SKSpriteNode *enemy = (SKSpriteNode *)node;
if (CGRectIntersectsRect(enemy.frame, _object.frame)) {
[enemy removeFromParent];
}
}]; }
*This does not work!!! but if I use :
CGRectIntersectsRect(enemy.frame, self.player.frame)
I can detect a collision with the main body. how do I do collision detection for a child of another sprite?
The child node's position and frame property are relative to it's parent, which you need to account for in your code.
SKNode has two methods that can help you with converting the position of a given SKNode to/from the coordinate spaces of another node:
convertPoint:fromNode:
convertPoint:toNode:
You can use those to convert a SKNode's position property to another coordinate space. What you want to do is convert the _object's position to the coordinate space of the enemy or visa versa and then use a temporary CGRect with that new position for your CGRectIntersectsRect check.
Here's an example :
CGPoint globalPosition = [self.player convertPoint:_object.position toNode:self.player.parent];
CGRect tempRect = CGRectMake(globalPosition.x, globalPosition.y, _object.frame.size.width, _object.frame.size.height);
if (CGRectIntersectsRect(enemy.frame, _object.frame))
{
// collision occurred
}
This code is assuming that your enemy and your player are in the same coordinate space. (have the same parent)
This happens because _object is a child node for self.player, and enemy node is a child node for self.map node. Both nodes should have same parent if you want to compare their frames.
I suggest you to use Sprite Kit's built-in collision handling mechanism:
// Object:
_object = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
_object.position = CGPointMake(-40, 27);
_object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_object.frame.size;
_object.physicsBody.categoryBitMask = OBJECT_CATEGORY;
_object.physicsBody.contactTestBitMask = ENEMY_CATEGORY;
_object.physicsBody.collisionBitMask = 0;
[self.player addChild:_object];
// Enemy:
// init enemy: SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.frame.size;
enemy.physicsBody.categoryBitMask = ENEMY_CATEGORY;
enemy.physicsBody.contactTestBitMask = OBJECT_CATEGORY;
enemy.physicsBody.collisionBitMask = 0;
// add enemy to the scene: [self addChild:enemy];
// Define that your SKScene conforms to SKPhysicsContactDelegate protocol:
.....
#interface MyScene : SKScene <SKPhysicsContactDelegate>
....
// SKScene.m:
static uint32_t const OBJECT_CATEGORY = 0x1 << 0;
static uint32_t const ENEMY_CATEGORY = 0x1 << 1;
#implementation MyScene
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
.......
self.physicsWorld.contactDelegate = self;
........
}
return self;
}
.......
- (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 & OBJECT_CATEGORY) != 0) &&
(secondBody.categoryBitMask & ENEMY_CATEGORY) != 0) {
NSLog(#"Collision detected!");
}
#end

- (void)didBeginContact:(SKPhysicsContact *) refuses to fire

1) I set the delegate protocol on the SKScene header:
#interface WorldScene : SKScene <SKPhysicsContactDelegate>
2) I set delegate to the physics world:
- (id) init
{
self = [super init];
if (self)
{
self.physicsWorld.gravity = CGVectorMake(0,0);
self.physicsWorld.contactDelegate = self;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}
return self;
}
3) I set my bitmasks:
static const uint32_t playerCategory = 0x1 << 0;
static const uint32_t wallsCategory = 0x1 << 1;
static const uint32_t endCategory = 0x1 << 2;
4) I make my player:
SKSpriteNode *player = [[SKSpriteNode alloc] initWithColor:[SKColor blueColor] size:CGSizeMake(PLAYERSIZE, PLAYERSIZE)];
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.size];
player.name = #"player";
player.physicsBody.dynamic = YES;
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = wallsCategory;
player.physicsBody.contactTestBitMask = endCategory;
5) I make my end tile:
SKSpriteNode *tile = [[SKSpriteNode alloc] initWithColor: [SKColor whiteColor] size:CGSizeMake(TILESIZE,TILESIZE)];
if ([cell isEnd]){
tile.color = [SKColor greenColor];
tile.name = #"endTile";
tile.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: tile.size];
NSLog(#"END TILE OF SIZE %f x %f", tile.size.width, tile.size.height);
tile.physicsBody.dynamic = YES;
tile.physicsBody.affectedByGravity = NO;
tile.physicsBody.categoryBitMask = endCategory;
tile.physicsBody.collisionBitMask = 0;
tile.physicsBody.contactTestBitMask = playerCategory;
}
6) But this never gets called! :(
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"Touched!");
[self removeAllChildren];
}
I'm making a maze game. The player currently collides with all the walls fine, but I want the game to end when it reaches the last tile. Currently, the contact event isn't being called. I don't really know what's wrong, I have my debugger output set to displaying all output (and in any case all the children should be getting removed if it reaches the event anyway).
Whyyyyyy
SOLVED -- turns out init was never being called, so the delegate was not actually being set. Moved all the init code to - (void) didMoveToView:(SKView *)view and it began working just fine.

Resources