Accessing individual objects that all have the same categoryBitMask - ios

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"])

Related

Sprite doesn't move upon collision

When I detect collision with a sprite, I want it (fuelSprite) to move to a new randomly generated position.
I generate the sprite like this
//setup fuel
SKSpriteNode *fuel = [SKSpriteNode spriteNodeWithImageNamed:#"fuel.png"];
fuel.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
[fuel setScale:0.6];
fuel.zPosition = 1;
fuel.shadowCastBitMask = 1;
fuel.name = #"fuelNode";
fuel.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fuel.frame.size];
fuel.physicsBody.dynamic = FALSE;
fuel.physicsBody.affectedByGravity = false;
fuel.physicsBody.usesPreciseCollisionDetection = YES;
fuel.physicsBody.categoryBitMask = fuelCategory;
fuel.physicsBody.collisionBitMask = fuelCategory | fireCategory;
fuel.physicsBody.contactTestBitMask = fireCategory;
[self addChild:fuel];
[_items addObject:#"fuelNode"];
Then when collision is detected I want to grab the sprite (only thing in the array) and move it
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"contact detected");
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
NSLog(#"Hit");
score ++;
SKSpriteNode *object = [_items firstObject];
object.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
}
This is in my header file for the fuelSprite and items array
#property (nonatomic, strong) SKSpriteNode *fuelSprite;
#property (nonatomic) NSMutableArray *items;
For some reason I can't get the sprite to move to a new random location :(
The hit detection is working flawless and the NSlog shows that
You are adding a string to the array not a sprite. Try replacing
[_items addObject:#"fuelNode"];
with
[_items addObject:fuel];
Also, since this statement
SKSpriteNode *object = [_items firstObject];
returns an NSString not an SKSpriteNode, using object as a sprite should have caused an exception. I suspect that object is nil because your array wasn't properly allocated.
You can access the sprite in the scene with
SKSpriteNode *object = [self childNodeWithName:#"fuelNode"];
instead of adding it to an array.
Haven't done SpriteKit for a while but instead of setting the position.
Try replacing:
object.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
with:
CGPoint randomPoint = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
CGFloat duration = 2;
SKAction *move = [SKAction moveTo:randomPoint duration:duration];
[ojbect runAction:run];
Hopefully this works for you. :)
The reason is apple prevents you from messing with a node attached to a physicsBody.
You have two options:
A
SKPhysicsBody* tmp = object.physicsBody;
object.physicsBody = nil;
object.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
object.physicsBody = tmp;
Why is this bad? Because this way you detach your physics object from your actual node and they may be in two different places at the same time - you don't want that but in some cases it doesnt matter - then you can use this.
B
Create a new physics body at the position you need it to be and attach it back to your node

Cannot add SKSpriteNode dynamically?

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];
});

SKPhysicsJointPin stretching

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];

SkSpriteNode collision not being detected

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.

Sprite Kit, Remove Sprite for collision

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.

Resources