JRam13
JRam13

Reputation: 1132

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

It seems like the Spritekit's API does not allow to change the center of the physicsbody when using 'bodyWithTexture' (or maybe I'm missing something).

I'm wondering how you get around this when using a pixel precision physicsbody.

//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 physicsbody's center position is where the anchor point is. enter image description here

Alternatively, bodyWithCircle / bodyWithRectangle have a 'center' property. However, it isn't as precise and needs a lot of code to make (unscalable).

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

Upvotes: 4

Views: 812

Answers (3)

Kent Liau
Kent Liau

Reputation: 925

Code example from the solution of primaryartemis, will work for any other shapes beside from circle and rectangle, which is more practical.

In other words, we use a SKNode to contain our sprite(s), the SKNode's position will act like anchorPoint, we shift our sprite(s) away from this SKNode container to our desire position for our transformation to work as intended.

This is the graphic, a simple half circle arc with size of (width:100, height: 50) ,we want this graphic to rotate at the anchorPoint of (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'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)

    }

}

We then can just place this SemiArc SKNode to our scene, it will anchor at the point we desire.


We can create a helper of this SKNode container so we can reuse it more easily for 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
    }
}

Example usage:

// 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 & Workaround

I discover a caveat of this solution which is when you want to remove this SKNode based on physic simulated position will not work, because you did not give the container SKNode a physic body, so it will never change its position. Your logic that check it is out of bound will not work, since you are checking this container. This also the same when you detecting contact and collision, it is the physic body attached node inside the container cause contact and collision, NOT the container itself. But you can still react properly for contact and collision, the problem only arise when you want to remove this SKNode

Workaround 1: Compute the relative position of its children node with physic body plus the container position.

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

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

Workaround 2: Don't use this for dynamic body, it make sense not mess with anchor point for dynamic body, as its property should be simulate, not manually update.

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

Upvotes: 1

primaryartemis
primaryartemis

Reputation: 91

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

Have the claws without a changed anchor point so that it lines up with the body. Then parent that node to a blank SKNode and shift the claws over accordingly. Now by rotating the blank SKNode you get the effect of a changed anchor point whilst the physics body still lines up.

Upvotes: 3

sangony
sangony

Reputation: 11696

Anchor points have no effect on a physics body. There are a couple of physics bodies for which you can define a center point.

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

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

Unfortunately the bodyWithTexture: has no such capability. As a hack you could use a number of various sized rectangles, rotate them to the desired angle and join 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 to your FPS.

Upvotes: 2

Related Questions