Sprite Kit物理碰撞问题

6

我遇到了一些碰撞问题。我有两个大小和质量相等的物体。当一个物体撞到另一个静止的物体时,我得到了正确的行为(图中灰色区域)。但是,当我有两个相邻的物体时,行为不太对。Spritekit的结果在左边。期望/需要的结果在右边。

我认为我知道发生了什么,但不确定该怎么做。如果物体是具有两倍质量的单个物体,则Spritekit的行为将是正确的,但它们是分开的对象,最上面的对象应该采用传入粒子的速度。它似乎把它们视为一个对象。

我尝试了欺骗,并在两者接触后缩小半径以形成小间隙,但是然后会出现问题。有人知道我该怎么办吗?谢谢。

enter image description here


1
这是非常复杂的对象交互。在现实生活中,它看起来只是碰撞传递到最后一个对象,但事实并非如此。中间的对象正在经历非常奇怪的力量。这是猫咪游戏还是台球桌类型的情况? - Fogmeister
你可能在这里无能为力。就像@Fogmeister所说,这是质量体之间非常复杂的相互作用(例如牛顿摆)。问题在于物理接触不是渐进式的,它们不能直接将传入的力量传递和分配给其他接触的物体,或者不能以正确的方式对这种相互作用进行建模。 - CodeSmile
你可以尝试使用更高级的求解器,比如Bullet Physics库http://www.raywenderlich.com/53077/bullet-physics-tutorial-getting-started。需要注意的是,这是基于3D求解器的,但如果你正确设置了一些东西,它也可以轻松地进行2D模拟。这个求解器比Box2D(Sprite Kit使用/基于的求解器)要强大得多,所以很有可能会正确地传递你想要的力量。 - fuzzygoat
是的,这就像是一个台球桌的情况。我的代码在Sprite Kit中已经完成了大部分...也许可以在didBeginContact方法中进行某种重写或其他操作...否则只能从头开始了...叹气... - Cherr Skees
你可以通过手动计算距离并检查哪些球在接触时发生碰撞来作弊。 - joshd
也许可以手动计算速度,并在碰撞后设置它们? - Cherr Skees
2个回答

3

简介

这绝对是可以做到的,而且没有复杂的计算需要。SpriteKit完全能够处理此碰撞。我认为你误解了这里正在发生的物理现象。在物理学中有一个简单的规则需要记住:

任何两个对象不能占据同一点。这也适用于对象的部分。任何两个对象的任何部分都不能占据同一点。考虑到这一点,我们可以得出结论,无论两个对象多么接近,它们之间总是存在一些空间。

换句话说,相邻两个对象之间的距离必须大于零。

问题

你的根本问题在于,你在蓝色圆之间没有留下任何空间。让我们假设圆的半径为30,顶部圆的y位置为400。

您用于定位中间圆的数学公式可能是 topCircle.position.y - (topCircle.height + middleCircle.height) / 2 。由于您的所有圆都具有相同的半径(在我们的示例中为30),因此您可能将其简化为 topCircle.y - CIRCLE_DIAMETER。如果您将圆圈绝对定位,那么这可能是您在脑海中执行的计算。

无论采用哪个公式,当您输入数字时,第二个圆的y位置都将为340。

第一个公式:400 - (60 + 60) / 2 = 340
第二个公式:400 - 60 = 340
注意:第一个公式更加灵活,可以让您使用任何大小的圆,而不是固定大小的圆

问题就在这里。现在应该清楚了,顶部圆的底部占据了与中间圆的顶部相同的空间。

顶部圆:400 - 30 = 370
中间圆:340 + 30 = 370
之间的空间:|370 - 370| = 0

两个物理体试图占据同一点,因此合并成一个物体,导致您看到的行为。

解决方案

说了这么多,如果简单地在圆之间添加一个或更少的空间,您将得到所需的行为。
注意:还要确保每个圆的物理体的恢复设置为1

这适用于任意数量的圆。以下是一个简单的示例场景,其中有1个圆与4个其他圆接触,并展现了此行为。我在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

0

你可以尝试的方法,仅限于理论上的,因为我自己还没有尝试过,就是在碰撞回调函数中检查其中一个碰撞体是否与第三个物体相交/碰撞,并手动计算其速度。

我怀疑你不会得到2个堆叠球的碰撞检测(因为它们从那个位置开始),因此手动检查并将适当的速度应用于相邻的物体可能会奏效。

可能需要跟踪已经计算了新速度的“物体对”,以避免计算循环(可能是无限的)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接