Sprite Kit Physics Collision Issue
Asked Answered
K

2

6

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

Karyotin answered 23/1, 2014 at 17:48 Comment(6)
This is a VERY complex interaction of objects. In real life it looks like it's just the collision passing to the last object but it isn't that. The object in the middle is undergoing very odd forces. Is this a cat's cradle or a pool table type situation?Verner
You probably can't do anything here. Like @Verner said this is a very complex interaction between bodies of mass (like a Newton's cradle). The problem is that physics contacts are not progressive, it they don't pass on and distribute incoming forces directly to other contacting bodies or not to the extent to correctly model this kind of interaction.Unkind
You could try using a more advanced solver like the Bullet Physics library raywenderlich.com/53077/bullet-physics-tutorial-getting-started it should be noted this is based on a 3D solver although it will easily do 2D sims if you set things up right. This is far more powerful solver than Box2D (which is what Sprite Kit uses / is based on), so there is a good chance it will transfer the forces your after correctly.Aubert
Yes its like a pool table situation. I have my code mostly done in sprite kit already... Maybe some kind of override in the didBeginContact method or something.... Otherwise starting from scratch... sigh...Karyotin
You might be able to cheat this by doing some manual distance calculations and by checking to see which balls are colliding at the moment of contact.Grati
Maybe just calculate the velocities manually and set them after the collision?Karyotin
T
4

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
Triolet answered 18/5, 2015 at 21:29 Comment(0)
S
0

What you could do, and this is only in theory , as I have not yet tried it myself, is inside the collision callback check if one of the colliders has intersection/collision with a third object and manually calculate its velocity too.

I suspect that you won't get a collision detection for the 2 stacked balls (since they start @ that position) so manually checking this and applying the appropriate velocities to the adjoining bodies might do the trick.

Might have to track which "body pairs" have already calculated their new velocities to avoid calculation cycles (which may be infinite).

Sopher answered 16/12, 2014 at 9:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.