Масштабирование и прокрутка в SpriteKit

Я новичок в SpriteKit. Мне нужно отобразить изображение через UIImageView OR SpriteNode (это фоновое изображение для игры). Тем не менее, мне нужен пользователь, чтобы иметь возможность приближаться к фону и перемещаться вокруг. Я легко справился с этим, не используя SpriteKit, но не могу понять, как сделать node принятие масштабирования и панорамирования. Если я использую UIScrollView в раскадровке, все спрайты, добавленные в моей сцене, не становятся дочерними и не будут увеличивать или панорамировать.

Возможно ли это и вы можете указать мне в правильном направлении?


EDIT:

Я немного смущен вашим ответом, но вот что я сделал:

В моем контроллере корневого представления .m:

- (void)viewDidLoad
{
    [super viewDidLoad];
    SKView * showDesigner = (SKView *)self.view;
    SKScene * scene = [iMarchMyScene sceneWithSize:showDesigner.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;
    [showDesigner presentScene:scene];
}

В моей сцене .m: (ваши шаги прокомментированы)

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {

        //create SKView (step 1)
        SKView *skview_field = [[SKView alloc]initWithFrame:(CGRectMake(0, 0, size.width, size.height))];

        //create UIScrollView with same dimensions as your SpriteKit view (step 2)
        UIScrollView* uiscrollview_field = [[UIScrollView alloc]initWithFrame:skview_field.frame];

        //initialize the field (field is the game background node)
        SKSpriteNode *field = [SKSpriteNode spriteNodeWithImageNamed:@"Field"];
        field.position = CGPointMake(CGRectGetMidX(self.frame), (CGRectGetMidY(self.frame)));
        field.size = CGSizeMake(self.size.width, self.size.height / 3);

        //create a UIView (called scrollViewContent) with dimensions of background node (step 3)
        UIView* scrollViewContent = [[UIView alloc]initWithFrame:field.frame];

        //set UIScrollView contentSize with same dimensions as your scrollViewContent and add a scrollContentview as your UISCrollView subview (step 4)
        [uiscrollview_field setContentSize:(CGSizeMake(scrollViewContent.frame.size.width, scrollViewContent.frame.size.height))];
        scrollViewContent.frame = field.frame;
        [uiscrollview_field addSubview:scrollViewContent];



        //[self addChild:field]; -- my original code
        //.....does a bunch of other inits

        return self;
}

И здесь, где я заблудился: Шаг 5: Теперь в корневом представлении добавьте SKView и UIScrollView поверх SKView.

Если вы правильно поняли, мне нужно перейти в myRootViewController.m и в методе viewDidLoad, добавьте SKView и UIScrollView (которые инициализируются в myScene.m) в качестве подпрограмм для SKView, инициализированных здесь в методе viewDidLoad? Я не вижу, как это сделать.

Ответ 1

Вот мое решение проблемы прокрутки. Он вращается вокруг "кражи" поведения из UIScrollView. Я узнал об этом из WWDC Video с 2012 года о смешении UIKit с OpenGL.

  • Добавьте методы UIScrollViewDelegate в ViewController и установите делегат представления прокрутки в ViewController
  • Добавить/Перенести PanGestureRecognizer из UIScrollView в SKView
  • Используйте метод обратного вызова scrollViewDidScroll для управления любым node вашим желанием, когда пользователь прокручивает

Пример проекта: https://github.com/bobmoff/ScrollKit

Как я уже упоминал в комментарии выше, я испытываю небольшое отставание примерно в 30% случаев, когда я запускаю приложение. FPS все еще 60, но, похоже, есть какой-то небольшой конфликт или что-то вроде того, как часто называется метод делегата, поскольку он что-то чувствует себя немного лагги. Если кому-нибудь удастся решить эту проблему, я бы услышал об этом. Кажется, что только когда я держу палец вниз, он никогда не отстает, когда происходит торможение.

Ответ 2

Джастин

вы можете использовать небольшой трюк, я использую его, и он отлично работает для меня. Вот настройка:

  • создать SKView
  • создайте UIScrollView с теми же параметрами, что и представление SpriteKit
  • создайте UIView (давайте назовем его scrollContentView) с теми же размерами, что и ваш фон node
  • задайте свой контент UIScrollView contentSize с такими же размерами, как ваш scrollContentView, и добавьте scrollContentView в качестве подзаголовка UIScrollView

Теперь в корневом представлении (UIView) добавьте SKView и UIScrollView поверх SKView. Это похоже на это (контроллер просмотра):

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];


Последняя часть трюка заключается в реализации 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
}

Таким образом вы можете моделировать SKView внутри UISCrollView. Надеюсь, это поможет.

EDIT 2014-04-20

У меня есть открытый компонент моего компонента для панорамирования и прокрутки сцен в SpriteKit, пожалуйста, посмотрите: https://github.com/pzbyszynski/PIOSpriteKit

Ответ 3

Вы можете начать с этого учебника Ray W учебник по SpriteKit, как перетащить и отбросить, который включает в себя некоторые из вещей, которые вы просите включить в распознавания жеста.

Это хорошо: Ссылка на указатель Apple gesture

Другой из Ray W: uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more

Выше было хорошо начать.

Ответ 4

Я создал свой собственный метод для масштабирования конкретного node без необходимости масштабирования всей сцены, и это его основа, но это не идеально (и на самом деле я создал свой запрос на помощь здесь: Масштабирование SKNode непоследовательно)

Этот оператор if находится в touchsMoved методе сцены:

   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];
        }
    }

Единственная проблема, с которой я сталкиваюсь, - это непоследовательное поведение и негладкая прокрутка. Я использую слово "только" в этом смысле с большой либерализацией.

Ответ 5

Если вы используете SKCameraNode, вы можете рассчитать разницу между двумя касаниями, чтобы сразу определить масштабирование и панорамирование.

  • Определите SKCameraNode для вашего SKView
  • Проверьте, есть ли два касания
  • Если это так, вычислите разницу между предыдущими касаниями и текущими касаниями
  • Скажите, чтобы камера масштабировалась таким образом
  • Панорамирование камеры по среднему значению двух точек или по первому (если есть только одно)

Результат должен быть примерно таким. Обратите внимание, что я использовал event?.allTouches вместо принятого набора, если один палец не перемещается между касаниями, он не находится в наборе touches, что вызовет панорамирование, в котором пользователь ожидает масштабирование.

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)))
}