Sprite-Kit Pinch to Zoom Problems UIPinchGestureRecognizer
Asked Answered
A

1

5

I've been working on this code for quite a while now but it just feels like one step forward and two steps back. I'm hoping someone can help me.

I'm working with Sprite Kit so I have a Scene file that manages the rendering, UI and touch controls. I have an SKNode thats functioning as the camera like so:

_world = [[SKNode alloc] init];
[_world setName:@"world"];
[self addChild:_world];

I am using UIGestureRecognizer, so I add the ones I need like so:

_panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePanFrom:)];
[[self view] addGestureRecognizer:_panRecognizer];
_pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
[[self view] addGestureRecognizer:_pinchRecognizer];

The panning is working okay, but not great. The pinching is the real problem. The idea for the pinching is to grab a point at the center of the screen, convert that point to the world node, and then move to it while zooming in. Here is the method for pinching:

-(void) handlePinch:(UIPinchGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
    _tempScale = [sender scale];
}
if (sender.state == UIGestureRecognizerStateChanged) {
    if([sender scale] > _tempScale) {
        if (_world.xScale < 6) {
            //_world.xScale += 0.05;
            //_world.yScale += 0.05;
            //[_world setScale:[sender scale]];
            [_world setScale:_world.xScale += 0.05];

            CGPoint screenCenter = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
            CGPoint newWorldPoint = [self convertTouchPointToWorld:screenCenter];

            //crazy method why does this work
            CGPoint alteredWorldCenter = CGPointMake(((newWorldPoint.x*_world.xScale)*-1), (newWorldPoint.y*_world.yScale)*-1);

            //why does the duration have to be exactly 0.3 to work
            SKAction *moveToCenter = [SKAction moveTo:alteredWorldCenter duration:0.3];
            [_world runAction:moveToCenter];
        }
    } else if ([sender scale] < _tempScale) {
        if (_world.xScale > 0.5 && _world.xScale > 0.3){
            //_world.xScale -= 0.05;
            //_world.yScale -= 0.05;
            //[_world setScale:[sender scale]];
             [_world setScale:_world.xScale -= 0.05];

            CGPoint screenCenter = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
            CGPoint newWorldPoint = [self convertTouchPointToWorld:screenCenter];

            //crazy method why does this work
            CGPoint alteredWorldCenter = CGPointMake(((newWorldPoint.x*_world.xScale - _initialScreenSize.width)*-1), (newWorldPoint.y*_world.yScale - _initialScreenSize.height)*-1);

            SKAction *moveToCenter = [SKAction moveTo:alteredWorldCenter duration:0.3];
            [_world runAction:moveToCenter];
        }
    }
}
if (sender.state == UIGestureRecognizerStateEnded) {
    [_world removeAllActions];
}
}

I've tried many iterations of this, but this exact code is what is getting me the closest to pinching on a point in the world. There are some problems though. As you get further out from the center, it doesn't work as well, as it pretty much still tries to zoom in on the very center of the world. After converting the center point to the world node, I still need to manipulate it again to get it centered properly (the formula I describe as crazy). And it has to be different for zooming in and zooming out to work. The duration of the move action has to be set to 0.3 or it pretty much won't work at all. Higher or lower and it doesn't zoom in on the center point. If I try to increment the zoom by more than a small amount, it moves crazy fast. If I don't end the actions when the pinch ends, the screen jerks. I don't understand why this works at all (it smoothly zooms in to the center point before the delay ends and the screen jerks) and I'm not sure what I'm doing wrong. Any help is much appreciated!

Achene answered 6/3, 2014 at 3:14 Comment(0)
E
9

Take a look at my answer to a very similar question.

https://mcmap.net/q/972180/-sknode-scale-from-the-touched-point

The code I posted "anchors" the zoom at the location of the pinch gesture instead of the center of the screen, but that is easy to change as I tried it both ways.

As requested in the comments below, I am also adding my panning code to this answer.

Panning Code...

// instance variables of MyScene.
SKNode *_mySkNode;
UIPanGestureRecognizer *_panGestureRecognizer;

- (void)didMoveToView:(SKView *)view
{
    _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanFrom:)];
    [[self view] addGestureRecognizer:_panGestureRecognizer];
}

- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
    if (recognizer.state == UIGestureRecognizerStateBegan) {

        [recognizer setTranslation:CGPointZero inView:recognizer.view];

    } else if (recognizer.state == UIGestureRecognizerStateChanged) {

        CGPoint translation = [recognizer translationInView:recognizer.view];
        translation = CGPointMake(-translation.x, translation.y);

        _mySkNode.position = CGPointSubtract(_mySkNode.position, translation);

        [recognizer setTranslation:CGPointZero inView:recognizer.view];

    } else if (recognizer.state == UIGestureRecognizerStateEnded) {

        // No code needed for panning.
    }
}

The following are the two helper functions that were used above. They are from the Ray Wenderlich book on Sprite Kit.

SKT_INLINE CGPoint CGPointAdd(CGPoint point1, CGPoint point2) {
    return CGPointMake(point1.x + point2.x, point1.y + point2.y);
}

SKT_INLINE CGPoint CGPointSubtract(CGPoint point1, CGPoint point2) {
    return CGPointMake(point1.x - point2.x, point1.y - point2.y);
}
Escritoire answered 6/3, 2014 at 5:20 Comment(2)
Your answer on that page did indeed work perfectly. I cannot thank you enough! Any way you would post your panning code? I'd be curious to see your approach.Achene
Sure. The panning is actually much easier. I just added my code for panning to my answer above.Escritoire

© 2022 - 2024 — McMap. All rights reserved.