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

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

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


РЕДАКТИРОВАТЬ:

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

В моем корневом контроллере представления .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's 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? Я не понимаю, как сделать эту часть.


person Justin Moore    schedule 29.09.2013    source источник
comment
Я бы использовал действия, они решили мою проблему с масштабированием, и, похоже, набор спрайтов держит всю свою мощь!   -  person DogCoffee    schedule 02.10.2013


Ответы (5)


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

  1. Добавьте методы UIScrollViewDelegate в свой ViewController и установите делегат представления прокрутки в ViewController.
  2. Добавить/переместить PanGestureRecognizer из UIScrollView в SKView
  3. Используйте метод обратного вызова scrollViewDidScroll для управления любым узлом, который вы хотите, когда пользователь прокручивает

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

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

person bobmoff    schedule 05.10.2013
comment
Я хотел бы попробовать это, но с помощью действий и т. Д. Довольно легко добавить собственную пользовательскую прокрутку - и это действительно гладко. - person DogCoffee; 06.10.2013
comment
Да, @Smick, довольно просто просто подключить tochesMoved и заставить узлы двигаться, но как вы справляетесь с замедлением и подпрыгиванием, что, на мой взгляд, является сложной частью получения достаточно хорошего ощущения прокрутки. - person bobmoff; 06.10.2013
comment
только что попробовал, работает очень даже хорошо. Спасибо за совет! Теперь попробовать щипок/зум??? ..... - person DogCoffee; 06.10.2013
comment
думаю, что это ошибка - SKScene: установка масштаба SKScene не имеет никакого эффекта! Но усаживается нормально... - person DogCoffee; 06.10.2013
comment
Я не верю, что можно изменить положение SKScene. Я пробовал это раньше и получил предупреждающее сообщение о том, что положение сцены не должно изменяться во время выполнения или что-то в этом роде, поэтому мне лучше изменить положение SKNode или любого потомка на этот класс. - person bobmoff; 06.10.2013
comment
поэтому вы не можете использовать его так, как вам нужно, - (UIView *) viewForZoomingInScrollView: (UIScrollView *)scrollView { return [_scene view]; } , я понял, что это увеличение и уменьшение масштаба, но это нежелательный эффект. - person DogCoffee; 07.10.2013
comment
Кажется, это решение для масштабирования всей сцены, когда все узлы в ней масштабируются одновременно, верно? Что, если вы хотите, чтобы масштабировался только фоновый узел, а все остальные узлы с более высокими zPosition оставить немасштабированными? - person zeeple; 17.10.2013
comment
Это было только решение проблемы с прокруткой. Но масштабирование может быть обработано таким же образом. Создайте UIView того же размера, что и узел, который вы хотите увеличить, и добавьте его в представление прокрутки, а также верните это представление в методе делегата прокрутки viewForZoomingInScrollView. Я пробовал это только для Cocos2d, но, думаю, должно быть то же самое. - person bobmoff; 19.10.2013
comment
есть ли шанс, что вы можете загрузить эту работу? Я взломал это, хотя уже несколько часов, и я просто не могу заставить его работать. - person John Riselvato; 20.11.2013
comment
Я создам с ним чистый проект и выложу на github. Но у меня садятся батареи на ноутбуке, а кабель зарядного устройства я забыл у клиента, так что смогу сделать это только завтра. - person bobmoff; 20.11.2013
comment
Я пошел в наш офис и взял еще один. Вот пример проекта, где прокрутка настроена и работает: github.com/bobmoff/ScrollKit - person bobmoff; 21.11.2013
comment
@bobmoff Это фантастически хорошо написанный проект, спасибо вам за это. Я просмотрел и скопировал его вручную, чтобы лучше понять его, и я не могу не завидовать тому, насколько хорошо организовано и гибко ваше решение. - person Micrified; 18.02.2015

Джастин,

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

  • создать SKView
  • создайте UIScrollView с теми же размерами, что и представление SpriteKit.
  • создайте UIView (давайте назовем его scrollContentView) с теми же размерами, что и ваш фоновый узел
  • установите свой 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. Надеюсь это поможет.

ИЗМЕНИТЬ 20 апреля 2014 г.

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

person Piotr Zbyszynski    schedule 30.09.2013
comment
Я использую этот метод, но иногда (я не могу найти закономерность, когда это происходит), когда я удерживаю палец и перемещаю его вокруг объекта, который я перемещаю, он начинает заикаться. Частота кадров по-прежнему составляет 60 кадров в секунду, но, как и метод делегата, он получает только около половины количества вызовов, как обычно, поэтому похоже, что объект, который я перемещаю, имеет около 10-15 кадров в секунду. Было бы здорово, если бы кто-то решил это. - person bobmoff; 30.09.2013
comment
Я попробовал ваше решение, но немного застрял. Я отредактировал свой исходный пост с фрагментами кода и где я заблудился. Любая помощь, которую я был бы признателен. - person Justin Moore; 30.09.2013
comment
Джастин, мне кажется странным, что ты инициализируешь 2 экземпляра SKView. Я думаю, что в вашей сцене вы должны настроить только дочерние узлы сцены. Переместите свой код установки UIScrlooView в корневой контроллер представления и используйте только один SKView (сейчас я отредактирую свой ответ с некоторыми фрагментами кода) - person Piotr Zbyszynski; 01.10.2013
comment
@PiotrZbyszynski я просто не могу заставить это работать правильно, не могли бы вы предоставить полный пример кода, как вы это делаете, в т.ч. ViewController и файлы сцены h&m? - person PeterK; 15.02.2014
comment
Разве с помощью этого метода scrollView не поглощал бы прикосновения и не позволял сцене воспринимать любое прикосновение? - person Lay González; 28.07.2014

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

Это хорошо: справочник по распознаванию жестов Apple

Еще от Ray W: uigesturerecognizer-tutorial in-ios-5-pinches-pans-and-more

Вышеупомянутое хорошо для начала.

person PeterK    schedule 29.09.2013
comment
Спасибо. Я взгляну. - person Justin Moore; 30.09.2013
comment
Приветствую вас, кстати, я не думаю, что scrollView работает для SpriteKit, поэтому я думаю, вы должны обслуживать его самостоятельно с помощью записи жестов .. - person PeterK; 30.09.2013
comment
В настоящее время я пытаюсь увеличить узел сцены, используя setScale, но тогда это портит всю физику. Все спрайты масштабируются, как и ожидалось, но масса объекта остается прежней. - person DogCoffee; 30.09.2013

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

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

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

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

person zeeple    schedule 19.10.2013

Если вы используете 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)))
}
person Kevin R    schedule 07.12.2017