SpriteKit side scrolling doesn't update physicsWorld

Following the example here (except in Objective C) and the standard Apple documentation, I discovered that the physicsWorld doesn’t update its positions when myWorld updates its position. I turned on showsPhysics on my skView to verify this. See these screenshots.

Before any motion:
screen 1
After a little motion
screen 2
Getting a little farther
screen 3
And just before contact
screen 4

  • Function to run a wait action at decreasing intervals indefinitely
  • Swift: Make translucent overlapping lines of the same color not change color when intersecting
  • Pausing a SpriteKit Game - UIApplicationWillResignActive vs. UIApplicationDidBecomeActive?
  • GameplayKit > Scene Editor Navigation Graph > How to use it for pathfinding?
  • Swift: Deallocate GameScene after transition to new scene?
  • SKSPriteNode position changes to 0,0 for no reason
  • Here’s my code for centering myWorld. Most of the length is due to me being extremely verbose in dealing with the edge limits.

    - (void)centerOnNode:(SKNode *)monkeyNode
    {
        CGPoint monkeyPosition = monkeyNode.position;
    
        // Check if monkey died
        if (monkeyPosition.y < myWorld.frame.origin.y - sceneHeight/2) {
            [self monkeyDied];
        }
    
        // Check if monkey won
        if (monkeyPosition.x > skyFarRightSide.x) {
            [self monkeyWon:monkeyNode];
        }
    
        // Define limits
        CGFloat scrollTopLimit = skyFarTopSide.y - sceneHeight/2;
        CGFloat scrollBottomLimit = 0;
        CGFloat scrollLeftLimit = 0;
        CGFloat scrollRightLimit = skyFarRightSide.x - sceneWidth/2;
    
        // Normal (no limits hit) scrolling
        if (monkeyPosition.x > scrollLeftLimit && monkeyPosition.x < scrollRightLimit && monkeyPosition.y > scrollBottomLimit && monkeyPosition.y < scrollTopLimit) {
            [myWorld setPosition:CGPointMake(-monkeyPosition.x, -monkeyPosition.y)];
        }
    
        // At far left scrolling
        if (monkeyPosition.x < scrollLeftLimit) {
            // No y limits hit
            if (monkeyPosition.y > scrollBottomLimit && monkeyPosition.y < scrollTopLimit) {
                [myWorld setPosition:CGPointMake(0, -monkeyPosition.y)];
            }
    
            // Bottom limit hit
            if (monkeyPosition.y < scrollBottomLimit) {
                [myWorld setPosition:CGPointMake(0, 0)];
            }
    
            // Top limit hit
            if (monkeyPosition.y > scrollTopLimit) {
                [myWorld setPosition:CGPointMake(0, -scrollTopLimit)];
            }
        }
    
        // At far right scrolling
        if (monkeyPosition.x > scrollRightLimit) {
            // No y limits hit
            if (monkeyPosition.y > scrollBottomLimit && monkeyPosition.y < scrollTopLimit) {
                [myWorld setPosition:CGPointMake(-scrollRightLimit, -monkeyPosition.y)];
            }
    
            // Bottom limit hit
            if (monkeyPosition.y < scrollBottomLimit) {
                [myWorld setPosition:CGPointMake(-scrollRightLimit, 0)];
            }
    
            // Top limit hit
            if (monkeyPosition.y > scrollTopLimit) {
                [myWorld setPosition:CGPointMake(-scrollRightLimit, -scrollTopLimit)];
            }
        }
    
        // At far bottom scrolling
        if (monkeyPosition.y < scrollBottomLimit) {
            // No x limits hit
            if (monkeyPosition.x > scrollLeftLimit && monkeyPosition.x < scrollRightLimit) {
                [myWorld setPosition:CGPointMake(-monkeyPosition.x, 0)];
            }
    
            // Left limit hit
            if (monkeyPosition.x < scrollLeftLimit) {
                [myWorld setPosition:CGPointMake(0, 0)];
            }
    
            // Right limit hit
            if (monkeyPosition.x > scrollRightLimit) {
                [myWorld setPosition:CGPointMake(-scrollRightLimit, 0)];
            }
        }
    
        // At far top scrolling
        if (monkeyPosition.y > scrollTopLimit) {
            // No x limits hit
            if (monkeyPosition.x > scrollLeftLimit && monkeyPosition.x < scrollRightLimit) {
                [myWorld setPosition:CGPointMake(-monkeyPosition.x, -scrollTopLimit)];
            }
    
            // Left limit hit
            if (monkeyPosition.x < scrollLeftLimit) {
                [myWorld setPosition:CGPointMake(0, -scrollTopLimit)];
            }
    
            // Right limit hit
            if (monkeyPosition.x > scrollRightLimit) {
                [myWorld setPosition:CGPointMake(-scrollRightLimit, -scrollTopLimit)];
            }
        }
    }
    

    Is there an elegant way to fix this? So far my only idea is to include more code in centerOnNode that resets the positions of every physicsBody in myWorld.

    2 Solutions Collect From Internet About “SpriteKit side scrolling doesn't update physicsWorld”

    I noticed that once the monkey makes contact, all of the physics bodies snap back to their proper locations. However, a joints anchorPoint ends up in the wrong place.

    I added the following convenience method to my GameScene.m.

    -(CGPoint)convertSceneToFrameCoordinates:(CGPoint)scenePoint
    {
        CGFloat xDiff = myWorld.position.x - self.position.x;
        CGFloat yDiff = myWorld.position.y - self.position.y;
        return CGPointMake(scenePoint.x + self.frame.size.width/2 + xDiff, scenePoint.y + self.frame.size.height/2 + yDiff);
    }
    

    I use this method to add joints. It handles all of the coordinate system transformations that need to be dealt with that lead to the issue raised in this question. For example, the way I add joints

        CGPoint convertedRopePosition = [self convertSceneToFrameCoordinates:ropePhysicsBody.node.position];
        SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
        jointPin.upperAngleLimit = M_PI/4;
        jointPin.shouldEnableLimits = YES;
        [self.scene.physicsWorld addJoint:jointPin];
    

    I don’t know what’s changed in the last 2 years, but the other answer here didn’t work anymore. Instead, now I combine the rope segments position with its parent’s (the full rope). So I just do

    CGPoint convertedRopePosition = CGPointMake(ropePhysicsBody.node.parent.position.x + ropePhysicsBody.node.position.x, ropePhysicsBody.node.parent.position.y + ropePhysicsBody.node.position.y);
    SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
    

    This is needed because the rope segment is placed under the full length of the rope, so it’s position is relative to the full rope rather than the scene.