使用 'bodyWithTexture' (SpriteKit) 时改变 SKPhysicsBody 的中心点

Changing center point of SKPhysicsBody while using 'bodyWithTexture' (SpriteKit)

似乎 Spritekit 的 API 在使用 'bodyWithTexture' 时不允许更改物理体的中心(或者我可能遗漏了什么)。

我想知道您在使用像素精度物理体时如何解决这个问题。

//Upper Claw
SKSpriteNode *claw = [SKSpriteNode spriteNodeWithImageNamed:BOSS_CLAW_SPRITE];
claw.position = CGPointMake(800, 520);
claw.anchorPoint = CGPointMake(1, .7);

//physics
claw.physicsBody = [SKPhysicsBody bodyWithTexture:claw.texture size:claw.size];

你可以清楚地看到physicsbody的中心位置就是锚点所在的位置。

或者,bodyWithCircle / bodyWithRectangle 有一个 'center' 属性。但是,它不是那么精确,需要大量代码才能实现(不可扩展)。

-(SKPhysicsBody*)getPhysicsForClaw:(BOOL)isUpperClaw
{
     NSInteger reverseConstant = 1;
     if (!isUpperClaw) {
         reverseConstant = -1;
     }
     SKPhysicsBody *clawTip1 = [SKPhysicsBody bodyWithCircleOfRadius:5 center:CGPointMake(-545, -140*reverseConstant)];
     SKPhysicsBody *clawTip2 = [SKPhysicsBody bodyWithCircleOfRadius:6 center:CGPointMake(-540, -130*reverseConstant)];
     SKPhysicsBody *clawTip3 = [SKPhysicsBody bodyWithCircleOfRadius:7 center:CGPointMake(-535, -120*reverseConstant)];
     SKPhysicsBody *clawTip4 = [SKPhysicsBody bodyWithCircleOfRadius:8 center:CGPointMake(-530, -110*reverseConstant)];
     SKPhysicsBody *clawTip5 = [SKPhysicsBody bodyWithCircleOfRadius:8 center:CGPointMake(-525, -100*reverseConstant)];
     SKPhysicsBody *clawTip6 = [SKPhysicsBody bodyWithCircleOfRadius:9 center:CGPointMake(-515, -90*reverseConstant)];
     SKPhysicsBody *clawTip7 = [SKPhysicsBody bodyWithCircleOfRadius:11 center:CGPointMake(-508, -78*reverseConstant)];
     SKPhysicsBody *clawTip8 = [SKPhysicsBody bodyWithCircleOfRadius:12 center:CGPointMake(-495, -65*reverseConstant)];
     SKPhysicsBody *clawTip9 = [SKPhysicsBody bodyWithCircleOfRadius:13 center:CGPointMake(-480, -50*reverseConstant)];
     SKPhysicsBody *clawTip10 = [SKPhysicsBody bodyWithCircleOfRadius:14 center:CGPointMake(-465, -35*reverseConstant)];

     SKPhysicsBody *clawTeeth1 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(6, 40) center:CGPointMake(-433, -70)];
     SKPhysicsBody *clawTeeth2 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(15, 20) center:CGPointMake(-420, -60)];
     SKPhysicsBody *clawTeeth3 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(9, 40) center:CGPointMake(-395, -70)];
     SKPhysicsBody *clawTeeth4 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(15, 20) center:CGPointMake(-382, -60)];
     SKPhysicsBody *clawTeeth5 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(9, 40) center:CGPointMake(-345, -70)];
     SKPhysicsBody *clawTeeth6 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(10, 20) center:CGPointMake(-334, -60)];
     SKPhysicsBody *clawTeeth7 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(9, 30) center:CGPointMake(-295, -60)];
     SKPhysicsBody *clawTeeth8 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(9, 30) center:CGPointMake(-255, -42)];

     SKPhysicsBody *clawBody1 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(100, 45) center:CGPointMake(-400, -30)];
     SKPhysicsBody *clawBody2 = [SKPhysicsBody bodyWithCircleOfRadius:26 center:CGPointMake(-325, -25*reverseConstant)];
     SKPhysicsBody *clawBody3 = [SKPhysicsBody bodyWithCircleOfRadius:28 center:CGPointMake(-290, -12*reverseConstant)];
     SKPhysicsBody *clawBody4 = [SKPhysicsBody bodyWithCircleOfRadius:29 center:CGPointMake(-250, 0*reverseConstant)];
     SKPhysicsBody *clawBody5 = [SKPhysicsBody bodyWithCircleOfRadius:28 center:CGPointMake(-210, 10*reverseConstant)];
     SKPhysicsBody *clawBody6 = [SKPhysicsBody bodyWithCircleOfRadius:30 center:CGPointMake(-165, 24*reverseConstant)];

     SKPhysicsBody *clawBase1 = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(120, 55) center:CGPointMake(-75, 24)];

     SKPhysicsBody *claw = [SKPhysicsBody bodyWithBodies:@[clawTip1, clawTip2, clawTip3, clawTip4, clawTip5, clawTip6, clawTip7, clawTip8, clawTip9, clawTip10, clawTeeth1, clawTeeth2, clawTeeth3, clawTeeth4, clawTeeth5, clawTeeth6, clawTeeth7, clawTeeth8, clawBody1, clawBody2, clawBody3, clawBody4, clawBody5, clawBody6, clawBase1]];

     return claw;
 }

锚点对物理体没有影响。有几个物理体,您可以为其定义中心点。

(SKPhysicsBody *)bodyWithCircleOfRadius:(CGFloat)r
                                 center:(CGPoint)center

(SKPhysicsBody *)bodyWithRectangleOfSize:(CGSize)s
                                  center:(CGPoint)center

不幸的是 bodyWithTexture: 没有这样的能力。作为 hack,您可以使用许多不同大小的矩形,将它们旋转到所需的角度并使用 (SKPhysicsBody *)bodyWithBodies:(NSArray *)bodies 将它们连接在一起。这将允许您几乎覆盖您的纹理。

作为一个额外的好处,使用矩形而不是 bodyWithTexture 也减少了 FPS 的负担。

我对这种情况的解决方案是使用不可见的 SKNode。

让爪子没有改变锚点,使其与 body 对齐。然后 parent 该节点到一个空白的 SKNode 并相应地移动爪子。现在,通过旋转空白的 SKNode,您可以获得更改锚点的效果,同时物理 body 仍然排列。

代码示例来自的解决方案,适用于除圆形和矩形之外的任何其他形状,更实用。

换句话说,我们使用SKNode来包含我们的精灵,SKNode的位置将像anchorPoint一样,我们将我们的 sprite(s) 从这个 SKNode 容器移到我们想要的位置,以便我们的转换按预期工作。

这是图形,一个简单的半圆弧,大小为 (width:100, height: 50) ,我们希望这个图形在 (0.5, 0.0) 的锚点处旋转。

class SemiArc: SKNode {

    var sprite: SKSpriteNode

    override init() {
        sprite = SKSpriteNode(imageNamed: "semi_arc")

        super.init()

        // Move away from this container SKNode, this SKNode's position act like anchorPoint
        sprite.position.y = sprite.size.height / 2

        // The physicsBody will move along as well
        sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!, size: sprite.size)

        // Add the sprite to this container SKNode
        addChild(sprite)

        // To test the transformation from the anchor point we desire
        let rotationForever = SKAction.repeatActionForever(SKAction.rotateByAngle(CGFloat(M_PI) * 2, duration: 2.0))
        runAction(rotationForever)

    }

}

然后我们可以将这个 SemiArc SKNode 放置到我们的场景中,它会锚定在我们想要的位置。


我们可以为这个 SKNode 容器创建一个助手,这样我们就可以更轻松地为简单的 sprite 重用它。

extension SKNode {
    class func containerNodeWithSprite(sprite: SKSpriteNode, withAnchorPoint anchorPoint: CGPoint) -> SKNode {
        let containerNode = SKNode()

        sprite.position.x = (sprite.size.width / 2) - ( sprite.size.width * anchorPoint.x)
        sprite.position.y = (sprite.size.height / 2) - ( sprite.size.height * anchorPoint.y)

        containerNode.addChild(sprite)

        return containerNode
    }
}

用法示例:

// Somewhere in a scene class

let sprite = SKSpriteNode(imageNamed: "semi_arc")
sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!, size: sprite.size)

let container = SKNode.containerNodeWithSprite(sprite, withAnchorPoint: CGPointMake(0.5 , 0.0))

container.position = view!.center

let rotationForever = SKAction.repeatActionForever(SKAction.rotateByAngle(CGFloat(M_PI) * 2, duration: 2.0))
containedNode.runAction(rotationForever)

addChild(container)

警告和解决方法

我发现这个解决方案的一个警告是当你想删除这个基于物理模拟位置的 SKNode 将不起作用,因为你没有给容器 SKNode 一个物理体,所以它永远不会改变它的位置。你检查它是否越界的逻辑将不起作用,因为你正在检查这个容器。这在检测接触和碰撞时也是一样的,它是容器内的物理体附加节点导致接触和碰撞,而不是容器本身。但是你仍然可以对接触和碰撞做出正确的反应,只有当你想删除这个问题时才会出现这个问题SKNode

解决方法 1:计算其子节点与物理体的相对位置加上容器位置。

enumerateChildNodesWithName("fallingObjectWithMiddleBottomAnchor") {
  (node, stop) in

  if ((node.children.first?.position.y)! + node.position.y) < 0 {
    node.removeFromParent()
  }
}

变通方法 2: 不要将此用于动态主体,不要弄乱动态主体的锚点是有意义的,因为它的 属性 应该是模拟的,不手动更新。

解决方法 3:更好的解决方案是使用 SKConstraint 来实现您的需要。