How can I prevent the child of an SKSpriteNode from rotating with its parent?
Asked Answered
M

4

5

I'm using SpriteKit to write an iOS game that involves a number of labeled balls. I'm constructing these 'rollingBalls' by building a parent SKSpriteNode with 2 children:

a) an SKShapeNode (the actual circle shape)

b) and an SKLabelNode (the label)

The balls will be moving all over the screen, interacting with each other and other objects, in 2 dimensions, and entirely dependent on the expected physics (think billiards). But if at all possible I'd like the label to NOT rotate with the parent, so that it's remains easily readable at all times.

What's the easiest way to do this?

Should the label not be a child of the container? Is there some other way to peg it to the ballShape? Or is there some property I can set on the label, etc.?

Here's what I've got now:

double ballDiameter = 40;
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: 
                            CGRectMake(-ballDiameter / 2, -ballDiameter / 2, 
                                        ballDiameter, ballDiameter)];

SKSpriteNode *container = [[SKSpriteNode alloc]init];

container.name = @"rollingBall";

SKShapeNode *ballShape = [[SKShapeNode alloc] init];
ballShape.path = ovalPath.CGPath;

SKLabelNode *ballLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
ballLabel.text = @"some random string";
ballLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
ballLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
ballLabel.position = CGPointMake(0,0);

[container addChild:ballShape];
[container addChild:ballLabel];

container.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ballDiameter / 2];
container.physicsBody.usesPreciseCollisionDetection = YES;
Misdemean answered 16/10, 2013 at 21:27 Comment(5)
this is weird - I'm doing the exact same thing in an app I just started. I just going to add a pin joint somehow, so the label is pined to the parent node.Leatherleaf
hmmm.. this sounds more promising than my solution below... but if the label is pinned to the parent, won't it still rotate?Misdemean
it will be spin, but an somewhat independent from the parent physics body, so you can tweak positioning etc. I plan for mine to appear like a carriage on a ferris wheel. The large wheel goes around but the carriage remains hanging under it.Leatherleaf
interesting... any chance you could post or share some code? would like to see how you do it, exactly?Misdemean
i am yet to try it out, the other answer will keep it happy for the moment.Leatherleaf
C
6

enter image description here

Put all of the labels in a single container, add the label to the associated node's userData, then update label positions.

// Add a container as a scene instance variable.
SKNode *labels;

- (void)addLabelContainer
{
    labels = [SKNode node];
    [self addChild:labels];
}

- (void)addLabelForNode:(SKNode*)node
{
    SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    node.userData = [NSMutableDictionary dictionaryWithObject:label forKey:@"label"];
    label.text = node.name;
    label.fontSize = 5;
    label.zPosition = node.zPosition + 1;
    [labels addChild:label];
}

- (void)removeLabelForNode:(SKNode*)node
{
    [[node.userData objectForKey:@"label"] removeFromParent];
    [node.userData removeObjectForKey:@"label"];
}

- (void)update:(NSTimeInterval)currentTime
{
    for (SKNode *node in self.children) {
        SKNode *label = (SKLabelNode*)[node.userData objectForKey:@"label"];
        if (label) {
            label.position = node.position;
        }
    }
}

See https://github.com/pmark/MartianRover/blob/master/hiSpeed/hiSpeed/Scenes/LimboScene.m

Carboxylate answered 10/4, 2014 at 21:41 Comment(0)
L
5

This seems to work

-(void) didSimulatePhysics
{
    [self enumerateChildNodesWithName:@"ball" usingBlock:^(SKNode *node, BOOL *stop) {

        for (SKNode *n in node.children) {
            n.zRotation = -node.zRotation;
        }

    }];
}
Leatherleaf answered 16/10, 2013 at 21:54 Comment(6)
or just set zRotation = 0 everytime. works fine. is this computationally inefficient?Misdemean
yeah it works for my purposes here, but my gut tells me there's a better way. Not thrilled about blindly adjusting zRotation for all children -- how would we handle more complex SKNodes that have multiple children, some of which we might still want to rotate along with the container...? I like the idea of using the SKPhysicsJointPin, but can't figure out how to make it work, as the pins are anchored in the scene's coordinate system. Going to mark this as unanswered for the time being to see if we can get some more help. But yes, your solution is working for my pursoses now, so many thanks.Misdemean
you could use the node.UserData to hold a second bit of info, ie allow spin. Then if the child node is allow spin it will spin etc. Can update answer if you need help for that.Leatherleaf
That would work, but it just doesn't smell right conceptually. Gets more and more complicated and violates encapsulation best practices, don't you think? Really feels like we should be able to involve the actual physics.Misdemean
Also... would be great to allow label to spin just a little bit, by setting torque and angle limits... think it would look more realistic. Again thanks so much for your help.Misdemean
yeah -- i just tried the pin joint, and it failed as im storing my objects in an array for later use ... but you should try ... [self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:aNumber.physicsBody bodyB:myLabel.physicsBody anchor:myLabel.position]];Leatherleaf
M
1

One potential, super easy solution:

container.physicsBody.allowsRotation = NO

But of course this will prevent the entire sprite from rotating.

Misdemean answered 16/10, 2013 at 21:36 Comment(1)
yeah that defeats the purpose.Leatherleaf
Y
0

Although the other solutions work as well, i found that both nodes have a small gap between them when moving them using the physics simulation.

You can use 2 physics bodies and add a pin joint between them, this way they'll be updated simultaneously. My sample code for a subclass of SKNode (note: you need to have added the node to a scene/node to add a joint):

SKSpriteNode *overlay = [SKSpriteNode spriteNodeWithImageNamed:@"overlay"];
overlay.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10.0];
overlay.allowsRotation = NO;
[self addChild:overlay];

self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:26.0];

SKPhysicsJointPin *pinJoint = [SKPhysicsJointPin jointWithBodyA:overlay.physicsBody bodyB:self.physicsBody anchor:self.position];
[self.scene.physicsWorld addJoint:pinJoint];
Yurev answered 11/7, 2015 at 18:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.