I have an app with three sprite nodes, ball1, ball2, ball3. they move around the scene by tipping the device up, down, right, left. Everything is working fine.
I would like to add an effect in which when any two balls touch (collide) a sound plays. I am using SpriteKit and have each ball with a physicalBody and they have a ball shape property, I have precisionCollision Yes, categoryBitMask set to ballCategory1, 2, and 3 for each ball. (or can I just use one category)
I have tried various tutorials on how to do this but nothing seems to be working.
I have tried using Ray Wenderlich's example with a tweek for mine. but,
//here are my three categories
static const uint32_t ballCategory = 0x1 << 1;
static const uint32_t ballCategory2 = 0x1 << 2;
static const uint32_t ballCategory3 = 0x1 << 3;
//these are my three nodes
ball = [SKSpriteNode spriteNodeWithImageNamed:#"mankala piece"];
ball.scale = 1;
ball.position = CGPointMake(600, 500);
ball.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(ball.size.width/2)];
ball.physicsBody.usesPreciseCollisionDetection = YES;
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.collisionBitMask = ballCategory2|ballCategory|ballCategory3;
ball.physicsBody.affectedByGravity = NO;
[self addChild:ball];
ball2 = [SKSpriteNode spriteNodeWithImageNamed:#"mankala piece"];
ball2.scale = 1;
ball2.position = CGPointMake(350, 200);
ball2.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(ball2.size.width/2)];
ball2.physicsBody.usesPreciseCollisionDetection = YES;
ball2.physicsBody.categoryBitMask = ballCategory2;
ball2.physicsBody.collisionBitMask = ballCategory2|ballCategory|ballCategory3;
ball2.physicsBody.affectedByGravity = NO;
[self addChild:ball2];
ball3 = [SKSpriteNode spriteNodeWithImageNamed:#"mankala piece"];
ball3.scale = 1;
ball3.position = CGPointMake(500, 400);
ball3.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(ball3.size.width/2)];
ball3.physicsBody.usesPreciseCollisionDetection = YES;
ball3.physicsBody.categoryBitMask = ballCategory3;
ball3.physicsBody.collisionBitMask = ballCategory2|ballCategory|ballCategory3;
ball3.physicsBody.affectedByGravity = NO;
[self addChild:ball3];
// and here is my revised contact code
- (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 & ballCategory) != 0)
{
NSLog(#"click");
}
if ((firstBody.categoryBitMask & ballCategory2) != 0)
{
NSLog(#"click2");
}
if ((firstBody.categoryBitMask & ballCategory3) != 0)
{
NSLog(#"click3");
}
}
but still nothing happens.
has anyone worked with collisions in spriteKit. Just want to have a sound play when two different sprite nodes come in contact.
First, you do not have any contactTestBitMasks set up. Currently you're telling the physics delegate which nodes are set up to collide with one another, but nothing is set up to contact. Add this to each ball physicsBody:
ball2.physicsBody.contactTestBitMask = ballCategory | ballCategory2 | ballCategory3
This way, the physics delegate will understand that each ball can make contact with the others.
Your contact if statement is still incorrectly being used. ballCategory and ballCateogry2 are variables which you've equated to integers; this means that your categoryBitMask are now set equal to those integers.
What your if statement does is check to see if the physicsBody's category equals a specific integer, or just equals an integer greater than zero (it's up to you).
In this case, you will need to adjust your if statement to test the value of those categoryBitMasks you've set up:
if ((firstBody.physicsBody.categoryBitMask == ballCategory) &&
(secondBody.physicsBody.categoryBitMask == ballCategory2))
{
NSLog(#"click");
}
What you have is incorrect, as you're not testing the value of anything. You need to use your if statement to check if the firstBody and secondBody have a categoryBitMask set equal to specific ballCategory variable values.
Related
This function should increase my snake size by increment width when I call in my didbegincontact function. However when contact is made it calls the function once and then never calls it again. I am not sure why when contact is made it does not keep increasing my snake size by increment
func increaseSnakeSize(increment: CGFloat){
snake.snake1.size = CGSizeMake(snake.size.width + increment, snake.size.height + increment)
snake.snake2.size = CGSizeMake(snake.size.width + increment, snake.size.height + increment)
snake.snake3.size = CGSizeMake(snake.size.width + increment, snake.size.height + increment)
print(snake.snake1.size)
}
// didBeginContact
if (firstBody.categoryBitMask & physicsCategory.snakeCategory == 0b1) && (secondBody.categoryBitMask & physicsCategory.foodCategory == 0b10 ){
score++
scoreLabel.text = "\(score)"
food.removeFromParent()
addFood()
increaseSnakeSize(2)
}
When the initial contact is made, the snake grows to 22x22, but its physics body's size stays at 20x20. A solution is to create a new physics body that's 22x22 for the initial contact, 24x24 for the next, 26x26 for the contact after that, ... For example,
func increaseSnakeSize (increment: CGFloat) {
snake.snake1.size = CGSizeMake(snake.size.width + increment, snake.size.height + increment)
// Create a new, larger physics body that matches the size of the snake
snake.snake1.physicsBody = SKPhysicsBody(circleOfRadius:snake.size.width/2.0)
// Configure the physics body's property accordingly
snake.snake1.physicsBody?.affectedByGravity = false
...
}
Another potential issue is the order of contact.bodyA and contact.bodyB can switch between contacts. You will need to check if the snake is the first body and the food is the second and the food is the first body and the snake is the second body. Here's an example
func didBeginContact (contact: SKPhysicsContact) {
var firstBody, secondBody: SKPhysicsBody!
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask == physicsCategory.snakeCategory) && (secondBody.categoryBitMask == physicsCategory.foodCategory ) {
// Increase size of the snake
}
or
let bitMask = contact.BodyA.categoryBitMask | contact.BodyB.categoryBitMask
if bitMask == physicsCategory.snakeCategory | physicsCategory.foodCategory {
// Increase size of the snake
}
Looks like you do not increase the PhysicsBody of your snake. Do you have physics hit boxes showing in your app? If not, I would recommend turning them on and seeing how it behaves. In your View code, do:self.showsPhysics = true
I'm making a type of a platformer ( think super mario but in a top-down view style ) game and i want my main character to move with the platform it has contact with.
So i used this:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if (firstBody.categoryBitMask == PlatformCategory || secondBody.categoryBitMask == PlayerCategory) {
_contact = YES;
}
-(void)didSimulatePhysics
{
if (_contact) {
_player.position = CGPointMake(_platform2.position.x, _platform2.position.y + 10);
}
}
}
It kinda worked, and now my player is moving with that SPECIFIC platform when it touches it, but that's not a very practical way for doing it because i will be randomly generating platforms. So how t achieve that ?
SKPhysicsBody has a property called node, which is the parent sprite it's attached to. In your collision method, test if the physics body is of collision category platform, then:
SKSpriteNode *thisCollidedPlatform = (SKSpriteNode*) nameOfPhysicsBody.node
And then use the position of thisCollidedPlatform to set the player position.
I'm working on a spaceship game for OS X using SpriteKit. This is how I setup physicsBody for my spaceship node.
// SKSpriteNode
ship.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:triangle];
ship.physicsBody.categoryBitMask = playerCategory;
ship.physicsBody.contactTestBitMask = enemyCategory | edgeCategory;
ship.physicsBody.collisionBitMask = enemyCategory | edgeCategory;
ship.physicsBody.allowsRotation = NO;
ship.physicsBody.dynamic = YES;
ship.physicsBody.linearDamping = 0;
ship.physicsBody.angularDamping = 0.1;
ship.physicsBody.mass = 0.1;
ship.physicsBody.restitution = 0;
This is how I setup physicsWorld and physicsBody in my scene file.
// SKScene
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = edgeCategory;
self.physicsBody.contactTestBitMask = missileCategory | enemyCategory | playerCategory;
self.physicsBody.collisionBitMask = playerCategory;
self.physicsBody.restitution = 0;
In my scene's update method, I call [ship accelerate:#"up"];, which ends up calling this.
[self.physicsBody applyForce:CGVectorMake(self.engineThrust * cosf(self.shipOrientation),
self.engineThrust * sinf(self.shipOrientation))];
Is there a way to disable the ship's inertia? I don't want my ship gliding along my scene. I want the ship to stop moving as soon as I release the key. I also want the ship to immediately change direction when toggling between the up arrow and the down arrow.
Is there something in physicsBody that I can change or will I have to implement this?
You can stop any inertia by using ship.physicsBody.velocity = CGVectorMake(0,0);
At certain points in my game I want a ton of balls to fall onto the screen. Once they hit the ground and bounce a bit, I want them to just sit there, and no longer need them to move.
Once I get up to 200 physics bodies, the game gets very slow, so I'd like to destroy the bodies. Here is what I was trying in my code:
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.contactPoint.y < 150) {
if (contact.bodyA.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body a is ball");
contact.bodyA = nil;
}
if (contact.bodyB.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body b is a weapon");
}
}
This doesn't work, because contact.bodyA and contact.bodyB are both readonly, so I have to fix that, but apart from that, will just setting the actual physics body to nil destroy it and make the physics simulator run faster? Or is there a better way to fix the performance hit? I want to be able to add more than 200 balls, maybe 500 or 600.
Use contact.bodyA.node.physicsBody:
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.contactPoint.y < 150) {
if (contact.bodyA.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body a is ball");
// contact.bodyA = nil;
contact.bodyA.node.physicsBody = nil;
}
if (contact.bodyB.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body b is a weapon");
}
}
I haven't tested it though
Is there any way to remove from Parent a SKSpriteNode that has left area bounds?
for instance:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
firstNode = (SKSpriteNode *)contact.bodyA.node;
if (firstNode.position.y<0) {
[firstNode removeFromParent];
}
}
Just point me in the right direction. Is it the update method enumerate through checking their rects or is their an action you can apply. I have gone through the documentation can't seem to find it but I would have thought it would be an easy implement since it saves memory
Update method is where you can do it alright:
- (void)update:(NSTimeInterval)currentTime {
//remove any nodes named "yourNode" that make it off screen
[self enumerateChildNodesWithName:#"yourNode" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0){
[node removeFromParent];
}
}];
}
Though note that removing nodes doesn't guarantee freeing up memory!!
Here's how to test if the node has left any edge of the screen. In your update loop, iterate through all children object of your layer. Test if the object is of a specific type. Then test if the node's frame is outside each edge of the screen. If so call removeFromParent. Note that since the node's position is from its center you want to take that into account.
-(void)update:(CFTimeInterval)currentTime
{
[_gameLayer.children enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj isKindOfClass:[MyNode class]])
{
MyNode *myNode = (MyNode *)obj;
if (myNode.position.x + myNode.size.width/2 < 0 ||
myNode.position.x - myNode.size.width/2 > self.size.width ||
myNode.position.y + myNode.size.height/2 < 0 ||
myNode.position.y - myNode.size.height/2 > self.size.height)
{
[myNode removeFromParent];
}
}
}];
}
You can also detect contact with the background SKSpriteNode and set a category for it.
And then, implement didEndContact: method to remove the object
- (void)didEndContact:(SKPhysicsContact *)contact
{
//Find out your object (Remember it could be bodyA or bodyB) and remove it here
}
You can use this function.
This function will be called every frame.(ps:My English is poor)
-(void)didSimulatePhysics
I prefer contact handling. Contact testing is done in each frame just like the processing of update:. But the contact detection by comparing bits (SKPhysicsBody.categoryBitMask) is very fast and leightweight.
I need to detect and remove balls that leave the screen.
So I setup an invisible border as the scenes physicsBody that is exactly large enough to detect balls the have left the screen completely.
If physicsBody.contactTestBitMask of node#1 == physicsBody.categoryBitMask of node #2 then didBeginContact: is being called when both of them get in touch.
-(void)didMoveToView:(SKView *)view {
// bitmasks:
static uint32_t const categoryBitMaskBall = 0x1<<1;
static uint32_t const categoryBitMaskBorder = 0x1<<6;
CGFloat ballDiameter = [BallSprite radius] *2;
// floorShape is my scenes background
CGRect largeFloorFrame = floorShape.frame;
largeFloorFrame.origin.x -= ballDiameter;
largeFloorFrame.origin.y -= ballDiameter;
largeFloorFrame.size.width += ballDiameter *2;
largeFloorFrame.size.height += ballDiameter *2;
CGPathRef pathMainView = CGPathCreateWithRect(largeFloorFrame, nil);
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromPath:pathMainView];
self.physicsBody.categoryBitMask = categoryBitMaskBorder;
}
- (BallSprite*)addBall {
// initialize ball with additional configuration...
BallSprite *ball = [BallSprite ballAtPoint:(CGPoint)p];
ball.categoryBitMask = categoryBitMaskBall;
ball.contactTestBitMask = categoryBitMaskBorder;
[self addChild:ball];
}
- (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;
}
/*!
Check on outside border contact
*/
if ((firstBody.categoryBitMask & categoryBitMaskBall) != 0 &&
(secondBody.categoryBitMask & categoryBitMaskBorder) != 0) {
[firstBody.node removeFromParent];
}
if ((firstBody.categoryBitMask & categoryBitMaskBorder) != 0 &&
(secondBody.categoryBitMask & categoryBitMaskBall) != 0) {
[secondBody.node removeFromParent];
}
}
Swift version
self.enumerateChildNodesWithName("enemy") {
node, stop in
if (node.position.x < 0) {
node.removeFromParent()
}
}