applyForce(0, 400) - SpriteKit 不一致

16

我有一个具有物理Body并受重力影响的对象。它也是动态的。

当前,当用户触摸屏幕时,我运行以下代码:

applyForce(0, 400)

该对象向上移动约200个单位,然后由于重力而下落。这只发生在某些情况下。其他时候,它只会以大约50个单位的Y方向移动。

我找不到规律...我将我的项目放在Dropbox上,以便任何人都可以打开并查看。

https://www.dropbox.com/sh/z0nt79pd0l5psfg/bJTbaS2JpY

编辑:似乎这种情况发生在玩家撞击地面后短暂弹跳的时刻。有没有办法让玩家完全不弹跳?

编辑2:我尝试使用摩擦参数来解决这个问题,并且仅在摩擦力= 0时允许玩家“跳跃”(您会认为这将是玩家处于空中的所有情况),但是摩擦力似乎始终大于0。除了使用y位置之外,我还可以如何检测玩家是否正在接触物体?

谢谢


物理体的restitution属性控制节点的“弹性”。 - Matt
2个回答

51

建议的解决方案

如果你想要实现跳跃功能,我建议你使用applyImpulse而不是applyForce。以下是两者的区别,取自Sprite Kit编程指南

你可以选择应用力或者冲量:

力是根据你施加力和下一帧模拟处理之间经过的模拟时间长短来应用的。因此,为了对一个物体施加持续的力,你需要在每次新的帧被处理时调用适当的方法。力通常用于连续效果。

冲量会立即改变物体的速度,与经过的模拟时间无关。冲量通常用于立即改变物体的速度。

跳跃实际上是物体速度的瞬时改变,这意味着你应该使用冲量而不是力。要使用applyImpulse:方法,需要计算出所需的速度瞬时变化量,乘以物体的质量,并将其作为impulse参数传入函数。我相信你会看到更好的结果。

意外行为的解释

如果你在update:函数之外调用applyForce:,那么发生的事情是你的力会乘以施加力和下一帧模拟处理之间经过的时间长短。这个乘数不是一个常数,所以每次以这种方式调用applyForce:时,你会看到不同的速度变化。


非常好的答案。提供了比你需要的更多的信息,但这些信息非常有帮助。非常感谢。 - Max Hudson
谢谢,这也解决了我的一个头疼问题。非常好的和正确的答案。 - aramusss

19

@godel9提供了一个好的解决方案,但在我的测试中,对于预期之外的行为所给出的解释是不正确的。

根据SKPhysicsBody类的参考文献

力量仅在单个模拟步骤(一帧)中应用。

回到SKScene Class Reference关于-update方法的部分:

只有当场景被呈现在视图中而且没有暂停时,它才会被调用一次。

因此,我们可以假设在SKScene的-update方法中调用-applyForce:不应该会导致问题。但是,观察到即使施加比重力大得多的向上力(400牛顿与9.81相比),力也没有超过重力。

我创建了一个测试项目,其中包含两个节点,一个自然下落,将affectedByGravity设置为TRUE,另一个使用相同预期的重力矢量(x方向为0牛顿,y方向为-9.81)调用-applyForce。然后,我计算了每个节点在一个时间步长内的速度差以及时间步长的长度。然后,我记录了加速度(速度变化/时间变化)。

这是我SKScene子类的一部分代码:

- (id)initWithSize:(CGSize)size

{

if (self = [super initWithSize:size])

{

    self.backgroundColor = [UIColor purpleColor];



    SKShapeNode *node = [[SKShapeNode alloc] init];

    node.path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 10, 10), nil);

    node.name = @"n";

    node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:5];

    node.position = CGPointMake(0, 450);

    node.physicsBody.linearDamping = 0;

    node.physicsBody.affectedByGravity = NO;

    [self addChild:node];



    node = [[SKShapeNode alloc] init];

    node.path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 10, 10), nil);

    node.name = @"n2";

    node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:5];

    node.position = CGPointMake(20, 450);

    node.physicsBody.linearDamping = 0;

    [self addChild:node];


}

return self;

}



- (void)update:(NSTimeInterval)currentTime

{

SKNode *node = [self childNodeWithName:@"n"];

SKNode *node2 = [self childNodeWithName:@"n2"];

CGFloat acc1 = (node.physicsBody.velocity.dy - self.previousVelocity) / (currentTime - self.previousTime);

CGFloat acc2 = (node2.physicsBody.velocity.dy - self.previousVelocity2) / (currentTime - self.previousTime);

[node2.physicsBody applyForce:CGVectorMake(0, node.physicsBody.mass * -150 * self.physicsWorld.gravity.dy)];

NSLog(@"x:%f, y:%f, acc1:%f, acc2:%f", node.position.x, node.position.y, acc1, acc2);

self.previousVelocity = node.physicsBody.velocity.dy;

self.previousTime = currentTime;

self.previousVelocity2 = node2.physicsBody.velocity.dy;

}

结果很不寻常。在模拟中受到重力影响的节点加速度与手动施加力的节点相比,其加速度始终乘以150倍。我已尝试使用不同大小和密度的节点进行实验,但是相同的标量乘数存在。

由此我必须推断出SpriteKit内部具有默认的“像素对米”比率。也就是说,每个“米”等于正好150个像素。这有时很有用,否则场景通常太大,意味着力反应缓慢(想象一下从地面观看飞机,它行驶得非常快,但似乎移动得很慢)。 Sprite Kit文档经常建议不要进行精确的物理计算(在“Fudging the Numbers”一节中特别提到),但这种不一致性让我花了很长时间才找到。希望这可以帮助你!


这很有趣,可能与我在使用applyForce(http://stackoverflow.com/questions/28146350/applyforce-is-not-smooth)时遇到的不一致动画问题有关。您是如何利用这种不一致性来获益和/或解决您遇到的任何问题的? - Gadget Blaster
150像素对应一米的比率的独立确认:https://dev59.com/r4nda4cB1Zd3GeqPBp46#29627795 - Michael Ames
@mitchellallison 是150像素还是点?我需要一个视网膜和非视网膜的常量,谢谢。 - Juan Boero
多年后,我也独立确认了150的比率。除了SKPhysicsWorldgravity属性外,径向重力场节点也会发生这种情况。我还没有测试超出径向重力场节点,但我认为所有场节点都会发生这种情况。我无法弄清楚的是,在调用applyForce时为什么要使用这个比率,而在场节点和世界重力中不使用。 - mogelbuster

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