Zooming and scrolling in SpriteKit

前端 未结 5 467
一整个雨季
一整个雨季 2020-12-02 17:43

I am new to SpriteKit. I need to display an image via UIImageView OR SpriteNode (it is a background image for a game). However, I need the user to be able to zoom in to the

相关标签:
5条回答
  • 2020-12-02 18:10

    You could start with this Ray W tutorial SpriteKit tutorial how to drag and drop that includes some of the things you ask for including Gesture recognizers.

    This is good: Apple gesture Recognizer reference

    Another from Ray W: uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more

    The above is good to start with.

    0 讨论(0)
  • 2020-12-02 18:13

    Justin,

    you can use a little trick, I am using it and it works great for me. Here is the setup:

    • create SKView
    • create UIScrollView with same dimensions as your SpriteKit view
    • create a UIView (lets name it scrollContentView) with same dimensions as your background node
    • set your UIScrollView contentSize with same dimensions as your scrollContentView and add scrollContentView as your UIScrollView subview

    Now, to your root view (UIView) add your SKView, and UIScrollView on top of SKView. It goes something like this (view controller):

    self.scrollView              = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds.size];
    self.scrollContentView       = [[UIView alloc] init];
    self.scrollView.delegate     = self;
    
    self.spriteKitView           = [[SKView alloc] initWithFrame:[UIScreen mainScreen].bounds.size];
    self.scrollContentView.frame = self.scene.bacgroundNode.frame;
    self.scrollView.contentSize  = self.scrollContentView.frame.size;
    
    [self.scrollView addSubview:self.scrollContentView];
    
    [self.view addSubview:self.spriteKitView];
    [self.view addSubview:self.scrollView];
    
    [self.spriteKitView presentScene:self.scene];
    


    Last part of the trick is to implement UIScrollViewDelegate:

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
      //here you set your background node position based on scrollView contentoffset
    }
    
    - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    
      //here you set background node scale based on scrollView zoomScale
    }
    
    - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    
      //just return scrollContentView
    }
    

    This way you can simulate SKView inside UISCrollView. Hope this helps.

    EDIT 2014-04-20

    I have open sourced my component for panning and scrolling scenes in SpriteKit, please have a look at: https://github.com/pzbyszynski/PIOSpriteKit

    0 讨论(0)
  • 2020-12-02 18:16

    If you are using an SKCameraNode you can calculate the difference between two touches to define zooming and panning at once.

    • Define an SKCameraNode for your SKView
    • Check if there are two touches
    • If so, calculate the difference between the previous touches and current touches
    • Tell the camera to scale by that much
    • Pan by the camera by either the average of the two points, or the first one (if there's only one)

    Result should be something like this. Note that I used the event?.allTouches instead of the received set, if one finger doesn't move between touches, it's not in the touches set, which would cause a pan where a user would expect a zoom.

    let cameraNode = SKCameraNode()
    
    override func didMove(to view: SKView) {
    
        cameraNode.position = CGPoint(x: size.width / 2, y: size.height / 2)
        addChild(cameraNode)
        camera = cameraNode
    }
    
    override func touchesMoved(_ touch: Set<UITouch>, with event: UIEvent?) {
    
        guard let touches = event?.allTouches else {
            return
        }
    
        let arr = Array(touches)
        guard let touch = arr.first else {
            return
        }
    
        var positionInScene = touch.location(in: self)
        var previousPosition = touch.previousLocation(in: self)
    
        if touches.count > 1 {
    
            let touch2 = arr[1]
    
            let positionInScene2 = touch2.location(in: self)
            let previousPosition2 = touch2.previousLocation(in: self)
    
            let oldDistance = distance(previousPosition, previousPosition2)
            let newDistance = distance(positionInScene, positionInScene2)
    
            let diff = (oldDistance / newDistance)
    
            if diff.isNormal && diff != 1 {
    
                let scaleAction = SKAction.scale(by: diff, duration: 0)
                cameraNode.run(scaleAction)
            }
    
            previousPosition = average(previousPosition, previousPosition2)
            positionInScene = average(positionInScene, positionInScene2)
        }
    
        let translation = CGPoint(x: positionInScene.x - previousPosition.x, y: positionInScene.y - previousPosition.y)
    
        let panAction = SKAction.moveBy(x: -translation.x, y: -translation.y, duration: 0)
        cameraNode.run(panAction)
    }
    
    func average(_ a: CGPoint, _ b: CGPoint) -> CGPoint {
    
        return CGPoint(x: (a.x + b.x) / 2, y: (a.y + b.y) / 2)
    }
    
    func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
    
        let xDist = a.x - b.x
        let yDist = a.y - b.y
        return CGFloat(sqrt((xDist * xDist) + (yDist * yDist)))
    }
    
    0 讨论(0)
  • 2020-12-02 18:26

    I have created my own method for zooming a specific node without having to zoom the entire scene and this is the basics of it, however it is not perfect (and in fact I have created its own help request here: Zooming an SKNode inconsistent )

    This if statement goes in the touchesMoved method of the scene:

       if (touches.count == 2) {
            // this means there are two fingers on the screen
            NSArray *fingers = [touches allObjects];
            CGPoint fingOneCurr = [fingers[0] locationInNode:self];
            CGPoint fingOnePrev = [fingers[0] previousLocationInNode:self];
            CGPoint fingTwoCurr = [fingers[1] locationInNode:self];
            CGPoint fingTwoPrev = [fingers[1] previousLocationInNode:self];
    
            BOOL yPinch = fingOneCurr.y > fingOnePrev.y && fingTwoCurr.y < fingTwoPrev.y;
            BOOL yUnpinch = fingOneCurr.y < fingOnePrev.y && fingTwoCurr.y > fingTwoPrev.y;
    
            BOOL xPinch = fingOneCurr.x > fingOnePrev.x && fingTwoCurr.x < fingTwoPrev.x;
            BOOL xUnpinch = fingOneCurr.x < fingOnePrev.x && fingTwoCurr.x > fingTwoPrev.x;
    
            if (xUnpinch | yUnpinch) {
                if (YES) NSLog(@"This means an unpinch is happening");
                mapScale = mapScale +.02;
                [map setScale:mapScale];
            }
    
            if (xPinch | yPinch) {
                if (YES) NSLog(@"This means a pinch is happening");
                mapScale = mapScale - .02;
                [map setScale:mapScale];
            }
        }
    

    The only problem I am having with it is inconsistent behavior and unsmooth scrolling. I use the word 'only' in that sense with great liberalness.

    0 讨论(0)
  • 2020-12-02 18:36

    Here is my solution to the scrolling problem. It revolves around "stealing" the behaviour from the UIScrollView. I learned this from a WWDC Video from 2012 about mixing UIKit with OpenGL.

    1. Add the UIScrollViewDelegate methods to your ViewController and set the scroll view delegate to the ViewController
    2. Add/Move the PanGestureRecognizer from the UIScrollView to the SKView
    3. Use the scrollViewDidScroll callback-method to control whatever node your want when the user scrolls

    Example project: https://github.com/bobmoff/ScrollKit

    Like I mentioned in a comment above, I am experiencing some tiny lag about 30% of the times I am running the app. The FPS is still 60 but there seem to be some slight conflict or something with how often the delegate method is called, as it sometimes feel a little laggy. If anyone manages to solve this issue, I would love to hear about it. It seems to be only when I am holding my finger down, it never lags when the deceleration is happening.

    0 讨论(0)
提交回复
热议问题