Change the center point of SKPhysicsBody when using "bodyWithTexture" (SpriteKit)

It seems that the Spritekit API does not allow changing the center of the physical body when using bodyFithTexture (or maybe I am missing something).

I am wondering how you get around this when using pixel-precise physics physics.

//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];

      

You can clearly see that the position of the center of the physical body is the anchor point. enter image description here

Alternatively, bodyWithCircle / bodyWithRectangle have a "center" property. However, this is not as accurate and requires a lot of code to do (indiscreet).

-(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;
 }

      

enter image description here

+3


source to share


3 answers


Anchor points do not affect the physical body. There are several physical bodies for which you can define a center point.

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

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

      



Unfortunately, it bodyWithTexture:

does not have such an option. As a hack, you can use a series of rectangles of different sizes, rotate them to the desired angle and combine them together with (SKPhysicsBody *)bodyWithBodies:(NSArray *)bodies

. This will allow you to pretty much cover your texture.

As an added benefit, using rectangles instead of bodyWithTexture is also less of a burden on your FPS.

+2


source


My solution for situations like this is to use an invisible SKNode.



There are claws without modified anchor point so that it is in line with the body. Then the parent that is the node to an empty SKNode and slide the claws accordingly. Now, by rotating the empty SKNode you get the effect of the changed anchor point while the physics body is one more line.

+3


source


The sample code from primaryartemis' solution will work for any other shapes near the circle and rectangle, which is more practical.

In other words, we use SKNode

to contain our sprite (s), the position SKNode

will act as anchorPoint
, we will undo our sprite (s) from this SKNode

container to our desired position so that our transformation works as intended.

This is a graphical simple semicircle arc with size (width: 100, height: 50), we want this graph to rotate with anchorPoint (0.5, 0.0).

semi_arc

class SemiArc: SKNode {

    var sprite: SKSpriteNode

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

        super.init()

        // Move away from this container SKNode, this SKNode 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)

    }

}

      

Then we can just place this one SemiArc

SKNode

in our scene, it will snap to the point we want.


We can create a helper for this container SKNode

so that we can easily use it for a simple 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
    }
}

      

Usage example:

// 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)

      

Caveat and workaround

I found a caveat from this solution that when you want to remove this SKNode

one based on a physically modeled position, it won't work because you didn't give the container a SKNode

physical body, so it will never change its position. Your logic that checks it won't work as you are checking this container. It is also the same when you detect contact and collision, the physical body attached to the node inside the container causes the contact and collision, not the container itself. But you can still react normally to contact and collision, the problem only occurs when you want to remove thisSKNode

Workaround 1: Calculate the relative position of its children node with physical body plus the position of the container.

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

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

      

Workaround 2: Don't use this for a dynamic body, it doesn't make sense not to be confused with an anchor point for a dynamic body, as its property should mimic and not manually update.

Workaround 3: Better solution is to use SKConstraint to achieve what you need.

+1


source







All Articles