0

I'm following Ray Wenderlich's 'iOS Games by Tutorials' & I got everything in my world setup & working: The entire game is in Landscape mode & there's one SKNode called _worldNode & everything, except _uiNode (in-game UI), is added to it. Player character walks to a touched location & _worldNode moves under him like a treadmill. However, like all functionality (or as they call it: "juice") addicts I wanted to add zoom in/out functionality through UIPinchGestureRecognizer by scaling _worldNode, which I did. But now every time I zoom in, the "camera" moves to the bottom left. Zooming out moves the view to the top right of the screen. It's a mess. I need the view to stay centered on the player character & I've tried everything I could come up with & find online. The closest thing I came to was using the technique from SKNode scale from the touched point but I still get the bloody bottom left/top right mess. I realized this mess happens only when I update the camera/view (it's really _worldNode.position). Therefore, 'didSimulatePhysics' or 'didFinishUpdate' methods don't help. In fact, even a one time button that slightly moves/updates the camera view (_worldNode.position) still gives me the bottom left/top right problem. Here is my code. I hope someone can take a look & tell me what to modify to get things working.

@interface GameScene () <SKPhysicsContactDelegate, UIGestureRecognizerDelegate>
{
    UIPinchGestureRecognizer        *pinchGestureRecognizer;
}

//Properties of my GameScene.

@property SKNode        *worldNode;   
@property etc. etc.


//Called by -(id)initWithSize:(CGSize)size method & creates the in-game world.
-(void)createWorld
{
    [_worldNode addChild:_backgroundLayer];

    [self addChild:_worldNode];
    self.anchorPoint        = CGPointMake(0.5, 0.5); //RW tutorial did it this way.
    _worldNode.position     = CGPointMake(-_backgroundLayer.layerSize.width/2, -_backgroundLayer.layerSize.height/2); //Center.

    //Then I add every node to _worldNode from this point on.
}

//Neccessary for gesture recognizers.
-(void)didMoveToView:(SKView *)view
{
    pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleZoomFrom:)];
    [view addGestureRecognizer:pinchGestureRecognizer];
}

//"Camera" follows player character. 'didSimulatePhysics' doesn't help either.
-(void)didFinishUpdate
{
    //IF '_pinchingScreen' == YES then screen pinching is in progress. Thus, camera position update will seize for the duration of pinching.
    if (!_pinchingScreen)
    {
        _worldNode.position = [self pointToCenterViewOn:_player.position];
    }
}

//Method that is called by my UIPinchGestureRecognizer. Answer from: https://stackoverflow.com/questions/21900614/sknode-scale-from-the-touched-point?lq=1
-(void)handleZoomFrom:(UIPinchGestureRecognizer*)recognizer
{
    CGPoint anchorPoint = [recognizer locationInView:recognizer.view];
    anchorPoint         = [self convertPointFromView:anchorPoint];

    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        // No code needed for zooming...

        _player.movementMode    = 2; //Stop character from moving from touches.
        _pinchingScreen         = YES; //Notifies 'didFinishUpdate' method that pinching began & camera position update should stop for now.
    }
    else if (recognizer.state == UIGestureRecognizerStateChanged)
    {
        //Technique from the above Stack Overflow link - Commented out.
//        CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
//        
//        [_worldNode setScale:(_worldNode.xScale * recognizer.scale)];
//        
//        CGPoint mySkNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
//        CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, mySkNodeAnchorPointInScene);
//
//        _worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//        
//        recognizer.scale = 1.0;


        //Modified scale: 2.0
        if(recognizer.scale > _previousWorldScale)
        {
            _previousWorldScale = recognizer.scale;

            CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
            [_worldNode setScale:2.0];
            CGPoint worldNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
            CGPoint translationOfAnchorInScene  = CGPointSubtract(anchorPoint, worldNodeAnchorPointInScene);
            _worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);


            //[_worldNode runAction:[SKAction scaleTo:2.0 duration:0]]; //This works too.
        }
        //Original scale: 1.0
        if(recognizer.scale < _previousWorldScale)
        {
            _previousWorldScale = recognizer.scale;

            CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
            [_worldNode setScale:1.0];
            CGPoint worldNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
            CGPoint translationOfAnchorInScene  = CGPointSubtract(anchorPoint, worldNodeAnchorPointInScene);
            _worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);


            //[_worldNode runAction:[SKAction scaleTo:1.0 duration:0]]; //This works too.
        }
    }
    else if (recognizer.state == UIGestureRecognizerStateEnded)
    {
        // No code needed here for zooming...

        _pinchingScreen         = NO; //Notifies 'didFinishUpdate' method that pinching has stopped & camera position update should resume.
        _player.movementMode    = 0; //Resume character movement.
    }
}

So could anyone please tell me, by looking at the above code, why the camera/view shifts to the bottom left upon zooming in? I've sat several days on this problem & I still can't figure it out.

Community
  • 1
  • 1
Krekin
  • 1,516
  • 1
  • 13
  • 24
  • So I tried to mitigate this bottom left effect by adding this into the didFinishUpdate method: CGPoint thePoint = [self pointToCenterViewOn:_player.position]; thePoint.x += -self.size.width/2; thePoint.y += -self.size.height/2; So now when I zoom in with this: [_worldNode setXScale:1.5]; [_worldNode setYScale:1.5]; it works but the player character doesn't stay strictly inside the middle of the screen. He's "loose" and can eventually run outside the camera after a while... Any ideas how to stabilize character position into the middle of the screen? – Krekin Aug 08 '15 at 19:27

1 Answers1

0

Thanks to JKallio, who wrote detailed code in his answer to Zoom and Scroll SKNode in SpriteKit, I've been able to find a piece of code that solves the problem. There's a method called 'centerOnNode' that is small, elegant & solves my problem perfectly. Here it is for anyone that just needs that:

-(void) centerOnNode:(SKNode*)node
{
    CGPoint posInScene = [node.scene convertPoint:node.position fromNode:node.parent];
    node.parent.position = CGPointMake(node.parent.position.x - posInScene.x, node.parent.position.y - posInScene.y);
}

Then you call that method inside your 'didSimulatePhysics' or inside 'didFinishUpdate' like so:

//"Camera" follows player character.
-(void)didFinishUpdate
{
    //IF '_pinchingScreen' == YES then screen pinching is in progress. Thus, camera position update will seize for the duration of pinching.
    if (!_pinchingScreen)
    {
        if (_previousWorldScale > 1.0) //If _worldNode scale is greater than 1.0
        {
            [self centerOnNode:_player]; //THIS IS THE METHOD THAT SOLVES THE PROBLEM!
        }
        else if (_previousWorldScale == 1.0) //Standard _worldNode scale: 1.0
        {
            _worldNode.position = [self pointToCenterViewOn:_player.position];
        }
    }
}

P.S. Just remember the question wasn't HOW to zoom in. But how to fix the issue with the camera once the world is ALREADY zoomed in.

Community
  • 1
  • 1
Krekin
  • 1,516
  • 1
  • 13
  • 24