Im using Sprite Kit to detect collision between two objects. Here is how I define their bitmasks.
static const uint32_t puffinCategory = 0x1 << 0;
static const uint32_t planeCategory = 0x1 << 1;
Here is my code as to how setup the puffins and planes physics body.
For puffin
SKSpriteNode *PuffinNode = [[SKSpriteNode alloc]initWithImageNamed:#"puffin"];
PuffinNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:PuffinNode.size];
PuffinNode.physicsBody.usesPreciseCollisionDetection = YES;
PuffinNode.physicsBody.categoryBitMask = puffinCategory;
PuffinNode.physicsBody.dynamic = NO;
PuffinNode.physicsBody.collisionBitMask = puffinCategory;
PuffinNode.physicsBody.contactTestBitMask = planeCategory;
[PuffinNode setZPosition:1.5];
For Plane
SKSpriteNode *planeSpriteNode = [[SKSpriteNode alloc]initWithImageNamed:planeStringFileName];
planeSpriteNode.position = CGPointMake(0, self.view.frame.size.height*-1);
planeSpriteNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:planeSpriteNode.size];
planeSpriteNode.physicsBody.usesPreciseCollisionDetection = YES;
planeSpriteNode.physicsBody.categoryBitMask = planeCategory;
planeSpriteNode.physicsBody.dynamic = NO;
planeSpriteNode.physicsBody.collisionBitMask = puffinCategory;
planeSpriteNode.physicsBody.contactTestBitMask = puffinCategory;
Here is my implementation of the delegate method didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact{
NSLog(#"collission method run");
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 & puffinCategory) != 0 && (secondBody.categoryBitMask & planeCategory) != 0 ){
NSLog(#"collission occured");
}
}
Im not seeing the method logging if it is called, nor do I see a log when the two sprites collide.
You set
PuffinNode.physicsBody.dynamic = NO;
and
planeSpriteNode.physicsBody.dynamic = NO;
two static bodies cannot collide, at least one should be dynamic
Some problems :
1) Did you set the SKScene's physicsWorld.contactDelegate ?
2) Both of your nodes have no dynamic. If you want them to interact in the physics world you should make them under physics laws. One of them at least must be dynamic.
3) Your collisionBitMask are not well set.
Related
I have several game world objects the player needs to interact with individually upon his physicsBody.categoryBitMask contacting them. Instead of using separate categoryBitMasks for each individual object (object count surpasses categoryBitMask's limit, which is 32) I just use 1 categoryBitMask and gave all the objects individual names. Here's how it looks in code:
-(void)createCollisionAreas
{
if (_tileMap)
{
TMXObjectGroup *group = [_tileMap groupNamed:#"ContactZone"]; //Layer's name.
//Province gateway.
NSDictionary *singularObject = [group objectNamed:#"msgDifferentProvince"];
if (singularObject)
{
CGFloat x = [singularObject[#"x"] floatValue];
CGFloat y = [singularObject[#"y"] floatValue];
CGFloat w = [singularObject[#"width"] floatValue];
CGFloat h = [singularObject[#"height"] floatValue];
SKSpriteNode *object = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(w, h)];
object.name = #"provinceGateway";
object.position = CGPointMake(x + w/2, y + h/2);
object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(w, h)];
object.physicsBody.categoryBitMask = terrainCategory;
object.physicsBody.contactTestBitMask = playerCategory;
object.physicsBody.collisionBitMask = 0;
object.physicsBody.dynamic = NO;
object.physicsBody.friction = 0;
object.hidden = YES;
[_backgroundLayer addChild:object];
}
/*More code written below. Too lazy to copy & paste it all*/
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody; //Create 2 placeholder reference's for the contacting objects.
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) //If bodyA has smallest of 2 bits...
{
firstBody = contact.bodyA; //...it is then the firstBody reference [Smallest of two (category) bits.].
secondBody = contact.bodyB; //...and bodyB is then secondBody reference [Largest of two bits.].
}
else //This is the reverse of the above code (just in case so we always know what's what).
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
/**BOUNDARY contacts*/
if ((firstBody.categoryBitMask == noGoCategory) && (secondBody.categoryBitMask == playerCategory))
{
//Boundary contacted by player.
if ([_backgroundLayer childNodeWithName:#"bounds"])
{
NSLog(#"Player contacted map bounds.");
}
if ([_backgroundLayer childNodeWithName:#"nogo"])
{
NSLog(#"Player can't go further.");
}
if ([_backgroundLayer childNodeWithName:#"provinceGateway"])
{
NSLog(#"Another province is ahead. Can't go any further.");
}
if ([_backgroundLayer childNodeWithName:#"slope"])
{
NSLog(#"Player contacted a slope.");
}
}
The problem is, in didBeginContact method, when the player contacts any object all the code gets executed. Even the code from objects the player hasn't contacted yet. This means the IF statements, such as if ([_backgroundLayer childNodeWithName:#"slope"]), are not complete. Can someone tell me how to properly write out the IF statements for individual object contact? The following IF statement, inside didBeginContact, doesn't work either:
if ([_player intersectsNode:[_backgroundLayer childNodeWithName:#"slope"]])
Your if statements are all just checking if the child exists, which it seems they all do. What I think you want to check is if the collision node has the name you are looking for, so change:
if ([_backgroundLayer childNodeWithName:#"bounds"])
to:
if ([firstBody.node.name isEqualToString:#"bounds"])
I'm adding SKSpriteNode dynamically on didBeginContact delegate method :
Here is my code
-(void)adBall
{
SKSpriteNode *obj = [[SKSpriteNode alloc]initWithImageNamed:#"ball.png"];
obj.physicsBody = [SKPhysicsBody bodyWithTexture:obj.texture size:obj.texture.size];
obj.position = CGPointMake(100, 100);
obj.name = OBSTACLE_KEY;
obj.physicsBody.categoryBitMask = BallCategory1;
obj.physicsBody.contactTestBitMask = GreenLineCategory | RedLineCategory ;;
obj.physicsBody.collisionBitMask = GreenLineCategory | RedLineCategory ;;
obj.physicsBody.affectedByGravity = YES;
obj.physicsBody.dynamic = YES;
obj.physicsBody.friction = 0.0f;
obj.physicsBody.linearDamping = 0.0f;
obj.physicsBody.restitution = 1.0f;
obj.physicsBody.allowsRotation = NO;
[obj.physicsBody applyForce:CGVectorMake(200, 300)];
}
-(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.node.name isEqual: OBSTACLE_KEY] && ( [secondBody.node.name isEqual: BLACKLINE_KEY]))
{
[self adBall];
}
}
But cant appear in screen it just show ball at bottom and disappear.
and if add [self adBall]; in didMoveToView method than working fine.
So Plz tell me where I'm wrong here ?
You never run
[self addChild:obj];
inside your adBall method. Therefore the newly created sprite won't be added to the scene graph and naturally won't be drawn (it gets discarded at the end of the method).
Ok Done it.
as LearnCocos2D said that i didn't add [self addChild:obj]; i add that line but than it not working in didMoveToView than what i did is called method after some time.
Like :
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self adBall];
});
I tried the solution posted here but it did not fix the issue. No one is responding to the my requests there since the question has already been marked as answered.
I have the player jumping across the screen to grab a rope. The grabbing is achieved by establishing an SKPhysicsJointPin between a rope segment and the player. The rope itself is made up of many segments connected to each other with SKPhysicsJointPins. Those behave as expected. However, the player seems to be joined for about a second but then as the player and rope swing together the joint between them stretches out and the player falls completely off screen.
Here is where the player (a monkey) gets added to the scene:
- (void)addMonkeyToWorld
{
SKSpriteNode *monkeySpriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"Monkey"];
// Basic properties
monkeySpriteNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monkeySpriteNode.size];
monkeySpriteNode.physicsBody.density = physicsParameters.monkeyDensity;
monkeySpriteNode.physicsBody.restitution = physicsParameters.monkeyRestitution;
monkeySpriteNode.physicsBody.linearDamping = physicsParameters.monkeyLinearDamping;
monkeySpriteNode.physicsBody.angularDamping = physicsParameters.monkeyAngularDamping;
monkeySpriteNode.physicsBody.velocity = physicsParameters.monkeyInitialVelocity;
// Collision properties
monkeySpriteNode.physicsBody.categoryBitMask = monkeyCategory;
monkeySpriteNode.physicsBody.contactTestBitMask = ropeCategory;
monkeySpriteNode.physicsBody.collisionBitMask = 0x0;
monkeySpriteNode.physicsBody.usesPreciseCollisionDetection = YES;
}
Here is where a contact event is sorted out:
- (void)didBeginContact:(SKPhysicsContact *)contact
{
// Sort which bodies are which
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
// Verify that the two bodies were the monkey and rope, then handle collision
if ((firstBody.categoryBitMask & ropeCategory) != 0 && (secondBody.categoryBitMask & monkeyCategory) != 0)
{
[self monkey:secondBody didCollideWithRope:firstBody atPoint:contact.contactPoint];
}
}
And here is where the joint is added between the player and the rope:
- (void)monkey:(SKPhysicsBody *)monkeyPhysicsBody didCollideWithRope:(SKPhysicsBody *)ropePhysicsBody atPoint:(CGPoint)contactPoint
{
if (monkeyPhysicsBody.joints.count == 0) {
// Create a new joint between the monkey and the rope segment
CGPoint convertedMonkeyPosition = CGPointMake(monkeyPhysicsBody.node.position.x + sceneWidth/2., monkeyPhysicsBody.node.position.y + sceneHeight/2.);
CGPoint convertedRopePosition = CGPointMake(ropePhysicsBody.node.position.x + sceneWidth/2., ropePhysicsBody.node.position.y + sceneHeight/2.);
CGFloat leftMostX = convertedMonkeyPosition.x < convertedRopePosition.x ? convertedMonkeyPosition.x : convertedRopePosition.x;
CGFloat bottomMostY = convertedMonkeyPosition.y < convertedRopePosition.y ? convertedMonkeyPosition.y : convertedRopePosition.y;
CGPoint midPointMonkeyAndRope = CGPointMake(leftMostX + fabsf(ropePhysicsBody.node.position.x - monkeyPhysicsBody.node.position.x) / 2.,
bottomMostY + fabsf(ropePhysicsBody.node.position.y - monkeyPhysicsBody.node.position.y) / 2.);
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:midPointMonkeyAndRope]; // FIXME: Monkey-rope joint going to weird position
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];
}
}
Any ideas what would cause an SKPhysicsJointPin to stretch out?
This issue was related to another one I posted here. I'll repeat the answer here for anyone coming along.
I added the following convenience method to my GameScene.m.
-(CGPoint)convertSceneToFrameCoordinates:(CGPoint)scenePoint
{
CGFloat xDiff = myWorld.position.x - self.position.x;
CGFloat yDiff = myWorld.position.y - self.position.y;
return CGPointMake(scenePoint.x + self.frame.size.width/2 + xDiff, scenePoint.y + self.frame.size.height/2 + yDiff);
}
I use this method to add joints. It handles all of the coordinate system transformations that need to be dealt with that lead to the issue raised in this question. For example, the way I add joints
CGPoint convertedRopePosition = [self convertSceneToFrameCoordinates:ropePhysicsBody.node.position];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];
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
Im making a game in sprite kit and I'm fairly new to iOS programming and i have been working on getting it so when 2 images collide that one is deleted or made invisible. I have been very unsuccessful with this and was wondering if anyone knew how to do it?
Below is the ship (which always stays) and one of the objects to be deleted.
-(void)addShip
{
//initalizing spaceship node
ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
[ship setScale:0.5];
ship.zRotation = - M_PI / 2;
//Adding SpriteKit physicsBody for collision detection
ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.size];
ship.physicsBody.categoryBitMask = shipCategory;
ship.physicsBody.dynamic = YES;
ship.physicsBody.contactTestBitMask = DonutCategory | PizzaCategory | ChocolateCategory | SoftCategory | AppleCategory | GrapeCategory | OrangeCategory | BananaCategory;
ship.physicsBody.collisionBitMask = 0;
ship.physicsBody.usesPreciseCollisionDetection = YES;
ship.name = #"ship";
ship.position = CGPointMake(260,30);
actionMoveRight = [SKAction moveByX:-30 y:0 duration:.2];
actionMoveLeft = [SKAction moveByX:30 y:0 duration:.2];
[self addChild:ship];
}
- (void)shoot1 //donut
{
// Sprite Kit knows that we are working with images so we don't need to pass the image’s extension
Donut = [SKSpriteNode spriteNodeWithImageNamed:#"1"];
[Donut setScale:0.15];
// Position the Donut outside the top
int r = arc4random() % 300;
Donut.position = CGPointMake(20 + r, self.size.height + Donut.size.height/2);
Donut.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:Donut.size];
Donut.physicsBody.categoryBitMask = DonutCategory;
Donut.physicsBody.dynamic = YES;
Donut.physicsBody.contactTestBitMask = shipCategory;
Donut.physicsBody.collisionBitMask = 0;
Donut.physicsBody.usesPreciseCollisionDetection = YES;
// Add the Dount to the scene
[self addChild:Donut];
// Here is the Magic
// Run a sequence
[Donut runAction:[SKAction sequence:#[
// Move the Dount and Specify the animation time
[SKAction moveByX:0 y:-(self.size.height + Donut.size.height) duration:5],
// When the Dount is outside the bottom
// The Dount will disappear
[SKAction removeFromParent]]]];
}
You have to set the delegate of your physics world:
self.physicsWorld.contactDelegate = self;
Then, you have a delegate that is called when two objects contact :
- (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 & projectileCategory) != 0 &&
(secondBody.categoryBitMask & monsterCategory) != 0)
{
//remove the donut and the target
SKSpriteNode *firstNode = (SKSpriteNode *) firstBody.node;
SKSpriteNode *secondNode = (SKSpriteNode *) secondBody.node;
[firstNode removeFromParent];
[secondNode removeFromParent];
}
}
For more information you can jump to the collision part in this tutorial.