sprite kit collision detection with child sprite - ios

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

Related

Not detecting collision between boundary frame and SKNode in SpriteKit

Here is my code in my GameScene. I have a simple sprite which falls down under gravity and bounces of the edges of the screen and a particle emitter. All I need is that I should be able to detect collisions between the SKNode and the physicsbody i.e. the frame.
Code Used
#import "GameScene.h"
#interface GameScene () <SKPhysicsContactDelegate>
#property (strong) UITouch *rightTouch;
#property (strong) UITouch *leftTouch;
#property (strong) SKNode *spinnyNode;
#property (assign, nonatomic) int count;
#end
#implementation GameScene {
}
static const uint32_t birdCategory = 1 << 0;
static const uint32_t worldCategory = 1 << 1;
- (void)didMoveToView:(SKView *)view {
self.backgroundColor = [UIColor colorWithRed:134/255.0 green:50/255.0 blue:148/255.0 alpha:1.0];
self.scaleMode = SKSceneScaleModeAspectFit;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = worldCategory;
NSString *burstPath = [[NSBundle mainBundle] pathForResource:#"fire" ofType:#"sks"];
SKEmitterNode *burstNode = [NSKeyedUnarchiver unarchiveObjectWithFile:burstPath];
burstNode.position = CGPointMake(0, 0);
[self addChild:burstNode];
self.spinnyNode = [self childNodeWithName:#"ball"];
self.spinnyNode.physicsBody.angularVelocity = 10.0;
self.spinnyNode.hidden = YES;
self.spinnyNode.physicsBody.categoryBitMask = birdCategory;
self.spinnyNode.physicsBody.contactTestBitMask = 0x0;
// self.spinnyNode = ball;
SKNode *leftPaddle = [self childNodeWithName:#"bottom"];
leftPaddle.physicsBody.angularVelocity = 10.0;
}
- (void)touchDownAtPoint:(CGPoint)pos {
self.count = self.count+1;
CGPoint p = pos;
NSLog(#"/n %f %f %f %f", p.x, p.y, self.frame.size.height, self.frame.size.width);
if (self.count == 1)
{
self.spinnyNode.position = p;
self.spinnyNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:40];
self.spinnyNode.physicsBody.velocity=CGVectorMake(203, 10);
self.spinnyNode.hidden = NO;
self.spinnyNode.physicsBody.restitution = 0.8;
self.spinnyNode.physicsBody.linearDamping = 0.0;
self.spinnyNode.physicsBody.angularDamping = 0.3;
self.spinnyNode.physicsBody.friction = 0.1;
self.spinnyNode.physicsBody.angularVelocity = -100.0;
}
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"contact detected");
NSString *nameA = contact.bodyA.node.name;
NSString *nameB = contact.bodyB.node.name;
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
//Your first body is the block, secondbody is the player.
//Implement relevant code here.
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {[self touchDownAtPoint:[t locationInNode:self]];
}
}
-(void)update:(CFTimeInterval)currentTime {
// Called before each frame is rendered
}
#end
This is my first time working with SpriteKit. So please be patient and help me out.
If you want to get the callback to didBeginContact when they touch you will need to set the contact bit mask like this:
self.spinnyNode.physicsBody.contactTestBitMask = worldCategory;
currently you are setting it to 0x0 which means not to report any contacts to the delegate. You also need to set the delegate on the scene:
self.physicsWorld.contactDelegate = self;
UPDATE
You also need to put the lines:
self.spinnyNode.physicsBody.contactTestBitMask = worldCategory;
self.spinnyNode.physicsBody.categoryBitMask = birdCategory;
into the touchDownAtPoint method after you create the physicsBody with:
self.spinnyNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:40];
otherwise they will be reset when you create the physicsBody.

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.

didBeginContact doesn't fire

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;

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.

How can I stop walking of a man if there is a wall in Sprite Kit?

I am practicing with Sprite Kit in iOS 7.
Now I am getting a problem while a man walking from one point to another point by SKAction moveTo.
If there is any wall in the screen, I want to stop walking before the wall.
I used the code in skscen.h file like below:
typedef NS_ENUM(uint32_t, CollisionType)
{
CollisionTypePlayer = 0x1 << 0,
CollisionTypeWall = 0x1 << 1,
CollisionTypeRiver = 0x1 << 3,
CollisionTypeBush = 0x1 << 4
};
#import <SpriteKit/SpriteKit.h>
#interface THMyScene : SKScene <SKPhysicsContactDelegate>
#property (nonatomic) SKSpriteNode *manContainer;
//Wall sprite
#property (nonatomic) SKSpriteNode *wallNode1;
#end
and in the scene.m file given below:
#import "THMyScene.h"
#interface THMyScene()
#property (nonatomic) SKTextureAtlas *manAtlas;
#property (nonatomic) NSMutableArray *manImagesArray;
#end
#implementation THMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
// Configure physics for the world.
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f); // no gravity
self.physicsWorld.contactDelegate = self;
[self createWall];
[self createMan];
}
return self;
}
-(void) createMan
{
_manAtlas = [SKTextureAtlas atlasNamed:#"secondLevelMan"];
_manImagesArray = [[NSMutableArray alloc]init];
for(int i = 1; i <= _manAtlas.textureNames.count ; i++)
{
NSString *imageName = [NSString stringWithFormat:#"man%d.png",i];
SKTexture *tmpTexture = [SKTexture textureWithImageNamed:imageName];
[_manImagesArray addObject:tmpTexture];
}
_manContainer = [SKSpriteNode spriteNodeWithTexture:[_manImagesArray objectAtIndex:0]];
_manContainer.position = CGPointMake(50, 250);
[self addChild:_manContainer];
_manContainer.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_manContainer.size];
_manContainer.physicsBody.allowsRotation = YES;
_manContainer.physicsBody.dynamic = YES;
_manContainer.physicsBody.usesPreciseCollisionDetection = YES;
_manContainer.physicsBody.categoryBitMask = CollisionTypePlayer;
_manContainer.physicsBody.contactTestBitMask = CollisionTypeWall;
_manContainer.physicsBody.collisionBitMask = CollisionTypeRiver;
}
-(void) createWall
{
//Create wall 1
_wallNode1 = [SKSpriteNode spriteNodeWithImageNamed:#"rock-1"];
_wallNode1.name = #"stonewall";
_wallNode1.position = CGPointMake(400 , 700);
[self addChild:_wallNode1];
_wallNode1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_wallNode1.size];
_wallNode1.physicsBody.dynamic = NO;
_wallNode1.physicsBody.categoryBitMask = CollisionTypeWall;
_wallNode1.physicsBody.contactTestBitMask = 0;
_wallNode1.physicsBody.collisionBitMask = CollisionTypePlayer;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint locationTouch = [touch locationInNode:self];
CGPoint manLocation = _manContainer.position;
CGFloat rotatingAngle = [self pointPairToBearingDegrees:manLocation secondPoint:locationTouch];
CGFloat distance = [self distanceBetweenTwoPoints:manLocation andSecondpoint:locationTouch];
SKAction *animationAction = [SKAction repeatActionForever:[SKAction animateWithTextures:_manImagesArray timePerFrame: 0.0833]];
float duration = distance / 300;
SKAction *movePoint = [SKAction moveTo:locationTouch duration:duration];
SKAction *rotate = [SKAction rotateToAngle:rotatingAngle duration:0.0f];
SKAction *sequenceAction = [SKAction sequence:#[rotate, movePoint]];
[_manContainer runAction:[SKAction group:#[sequenceAction, animationAction]] withKey:#"runAnimation"];
}
- (CGFloat) pointPairToBearingDegrees:(CGPoint)startingPoint secondPoint:(CGPoint) endingPoint
{
CGPoint originPoint = CGPointMake(endingPoint.x - startingPoint.x, endingPoint.y - startingPoint.y); // get origin point to origin by subtracting end from start
float bearingRadians = atan2f(originPoint.y, originPoint.x); // get bearing in radians
return bearingRadians;
}
-(CGFloat)distanceBetweenTwoPoints:(CGPoint)firstPoint andSecondpoint:(CGPoint)secondPoint
{
CGFloat dx = secondPoint.x - firstPoint.x;
CGFloat dy = secondPoint.y - firstPoint.y;
return sqrt(dx*dx + dy*dy );
}
- (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;
}
}
#end
try something like this:
-(void)update:(CFTimeInterval)currentTime {
int distance = 10; // distance to wall when to stop
if ((man.position.x > wall.position.x + wall.size.width/2 + distance) &&
(man.position.x < wall.position.x - wall.size.width/2 - distance)
if ((man.position.y > wall.position.y + wall.size.height/2 + distance) &&
(man.position.y < wall.position.y - wall.size.height/2 - distance)
//do what you want like remove some or all actions of man
} }
//change your code this would work
- (void) didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
[firstBody.node removeAllActions];
firstBody.node.speed=0;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
[secondBody.node removeAllActions];
secondBody.node.speed=0;
}
}
//
remember every node in spritekit have speed property which control sprite animation speed

Resources