Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sprite Kit Physics Collision Issue

I'm having an issue with some collisions. I have two objects equal size and mass. When one collides into another that is at rest I get the correct behavior (grey area in image). When I have two objects that are next to each other the behavior isn't quite right. Spritekit result on the left. Expected/needed result on right.

I think I know what is going on, but not sure what to do about it. If the object was one object with twice the mass then the spritekit behavior would be correct, but they are separate objects and the uppermost one should take the velocity of the incoming particle. It seems that it treats them as one object.

I've tried cheating and shrinking the radius after the two are touching to put a small gap, but then things get all messed up. Does someone know what I can do here? Thanks.

enter image description here

like image 704
Cherr Skees Avatar asked Jan 23 '14 17:01

Cherr Skees


1 Answers

Brief introduction

This can definitely be done, and there are no complex calculations necessary. SpriteKit is perfectly capable of handling this collision. I think you're misunderstanding the physics in play here. There is one simple rule to keep in mind in physics that you are breaking. Speaking in real-life terms here:

No two objects can occupy the same point in space. This goes for parts of objects as well. No parts of any two objects can occupy the same point in space. With this in mind, we can say that no matter how close two objects are, there is always some space between them.

In other words, the amount of space between two objects next to each other must be greater than zero. No matter how infinitely small that space is, it still has to be there.

The Problem

Your underlying problem is that you've left zero space between the blue circles. Let's assume the circles have a radius of 30 and the top circle has a y-position of 400.

Your math for positioning the middle circle is likely topCircle.position.y - (topCircle.height + middleCircle.height) / 2. Since your circles are all the same radius (30 in our example), you probably simplified that to topCircle.y - CIRCLE_DIAMETER. If you positioned the circles absolutely, this is probably the calculation you performed in your head.

With either equation, when you plug in the numbers, the y-position of the second ball comes out to 340.

First equation: 400 - (60 + 60) / 2 = 340
Second equation: 400 - 60 = 340
(Note: first equation is more flexible and will allow you to work with any size circles instead of uniform-size)

Herein lies the problem. It should be clear by now that the bottom of the top circle is occupying the same point in space as the top of the top of the middle circle.

Top circle: 400 - 30 = 370
Middle circle: 340 + 30 = 370
Space between: |370 - 370| = 0

The two physics bodies are attempting to occupy the same point in space, and thus have merged into one body, giving you the behavior you're seeing.

The solution

With all that being said, if you simply add a point (or less) of space between the circles, you will get the behavior you desire.
(Note: also make sure to set the restitution of each circle's physics body to 1)

This works with any number of circles. Here's a simple example scene with 1 circle contacting 4 others that exhibits this behavior. I ran this on an iPhone 6:

#import "GameScene.h"

@interface GameScene()

@property (strong, nonatomic) NSArray *nodes;

@end


@implementation GameScene

#pragma mark - View Lifecycle
-(void)didMoveToView:(SKView *)view
{
    self.backgroundColor = [SKColor blackColor];

    [self createNodes];
    [self positionNodes];
    [self addNodes];
}

#pragma mark - Setup
- (void)createNodes
{
    // You could use SKShapeNode as well
    self.nodes = @[[SKSpriteNode spriteNodeWithImageNamed:@"circle"],
                   [SKSpriteNode spriteNodeWithImageNamed:@"circle"],
                   [SKSpriteNode spriteNodeWithImageNamed:@"circle"],
                   [SKSpriteNode spriteNodeWithImageNamed:@"circle"],
                   [SKSpriteNode spriteNodeWithImageNamed:@"circle"]];

    // Use this if you don't have a 30-radius circle image at hand
//    self.nodes = @[[SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(60, 60)],
//                   [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(60, 60)],
//                   [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(60, 60)],
//                   [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(60, 60)],
//                   [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(60, 60)]];

    for(SKSpriteNode *node in self.nodes)
    {
        node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:30];
        node.physicsBody.affectedByGravity = NO;
        node.physicsBody.restitution = 1;
    }
}

- (void)positionNodes
{
    SKSpriteNode *node = self.nodes.firstObject;
    node.position = CGPointMake(300, 400);

    for(NSInteger i = 1; i < self.nodes.count - 1; i++)
    {
        SKSpriteNode *prevNode = node;
        node = self.nodes[i];
        node.position = CGPointMake(300, prevNode.position.y - (prevNode.frame.size.height + node.frame.size.height) / 2 - 1);
    }

    node = self.nodes.lastObject;
    node.position = CGPointMake(300, 100);

    // Above created nodes at these positions:
    // (300, 400);
    // (300, 339);
    // (300, 278);
    // (300, 217);
    // (300, 100);
}

- (void)addNodes
{
    for(SKSpriteNode *node in self.nodes)
    {
        [self addChild:node];
    }
}

#pragma mark - Touch Events
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    SKSpriteNode *node = self.nodes.lastObject;
    [node.physicsBody applyImpulse:CGVectorMake(0, 20)];
}

@end
like image 155
Ben Kane Avatar answered Oct 05 '22 12:10

Ben Kane