Статьи

Объединяя мощь SpriteKit и SceneKit

Конечный продукт
Что вы будете создавать

SpriteKit и SceneKit — это платформы iOS, разработанные для того, чтобы облегчить разработчикам создание 2D и 3D ресурсов в казуальных играх. В этом руководстве я покажу вам, как объединить контент, созданный в обеих платформах, в одно представление, чтобы использовать API, которые Apple сделала доступными.

Вы достигнете этого, добавив простую кнопку воспроизведения / паузы и функции ведения счета к 3D-сцене SceneKit, используя 2D-интерфейс на основе SpriteKit.

Это руководство требует, чтобы вы работали как минимум с Xcode 6+ и имели опыт работы с базовым содержимым SpriteKit и SceneKit. Если нет, то я рекомендую сначала прочитать некоторые другие наши учебники Tuts + о SpriteKit и SceneKit .

Также рекомендуется использовать физическое устройство iOS для тестирования, для которого требуется активная платная учетная запись разработчика iOS. Вам также необходимо скачать стартовый проект с GitHub .

Когда вы откроете начальный проект, вы увидите, что, помимо стандартных AppDelegate и ViewController , у вас также есть два других класса, MainScene и OverlayScene .

Класс MainScene является подклассом SCNScene и предоставляет 3D-контент вашего приложения. Аналогично, класс OverlayScene является подклассом SKScene и содержит 2D-контент SpriteKit в вашем приложении.

Не стесняйтесь смотреть на реализации этих классов. Это должно выглядеть знакомо, если у вас есть некоторый опыт работы с SpriteKit и SceneKit. В ViewController вашего класса viewDidLoad вы также найдете код для настройки базового экземпляра SCNView .

Создайте и запустите приложение на iOS Simulator или на физическом устройстве. На этом этапе ваше приложение содержит синий вращающийся куб.

Начальная 3D сцена

Пришло время добавить сцену SpriteKit поверх 3D-контента. Это делается путем установки свойства overlaySKScene для любого объекта, который соответствует протоколу SCNSceneRenderer , в нашем примере это экземпляр SCNView . В ViewController.swift добавьте следующие строки в метод viewDidLoad :

1
2
3
4
5
override func viewDidLoad() {
    …
    self.spriteScene = OverlayScene(size: self.view.bounds.size)
    self.sceneView.overlaySKScene = self.spriteScene
}

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

Наложение сцены SpriteKit

Вы можете задаться вопросом, почему лучше использовать это свойство, а не просто добавить SKView в качестве SCNView объекта SCNView . При overlaySKScene свойства overlaySKScene и 2D, и 3D компоненты вашего приложения используют один и тот же контекст OpenGL для отображения содержимого на экране. Это работает значительно лучше, чем создание двух отдельных представлений, каждое из которых имеет собственный контекст OpenGL и конвейер рендеринга. Несмотря на то, что для этой простой установки разница незначительна, производительность, полученная в лагере, может быть неоценимой для более сложных сцен.

Существует много разных способов передачи информации между MainScene и OverlayScene . В этом уроке вы будете использовать наблюдение значения ключа, для краткости KVO.

Прежде чем реализовать возможность приостановить анимацию куба, сначала необходимо добавить эту функцию в сцену SpriteKit. В OverlayScene.swift добавьте следующий метод в класс OverlayScene :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
     
    let touch = touches.first as?
    let location = touch?.locationInNode(self)
     
    if self.pauseNode.containsPoint(location!) {
        if !self.paused {
            self.pauseNode.texture = SKTexture(imageNamed: «Play Button»)
        }
        else {
            self.pauseNode.texture = SKTexture(imageNamed: «Pause Button»)
        }
         
        self.paused = !self.paused
    }
}

Метод touchesEnded(_:withEvent:) вызывается, когда пользователь touchesEnded(_:withEvent:) палец с экрана устройства. В этом методе вы проверяете, нажал ли пользователь кнопку паузы, и соответственно обновили сцену. Снова соберите и запустите приложение, чтобы убедиться, что этот фрагмент головоломки работает правильно.

Рабочая кнопка паузы

Теперь нам нужно остановить вращение трехмерного куба, когда пользователь нажал кнопку паузы. Вернитесь к ViewController.swift и добавьте следующую строку в метод viewDidLoad :

1
2
3
4
override func viewDidLoad() {
    …
    self.spriteScene.addObserver(self.sceneView.scene!, forKeyPath: «paused», options: .New, context: nil)
}

Наконец, добавьте следующий метод в MainScene.swift, чтобы включить наблюдение значения ключа:

1
2
3
4
5
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
    if keyPath == «paused» {
        self.paused = change[NSKeyValueChangeNewKey] as!
    }
}

Если вы соберете и снова запустите свое приложение, куб должен перестать вращаться при нажатии кнопки паузы.

Так же, как информация может быть перенесена из сцены SpriteKit в сцену SceneKit, вы также можете отправлять данные из экземпляров SceneKit в экземпляры SpriteKit. В этом простом приложении вы будете добавлять одно очко к счету каждый раз, когда пользователь нажимает на куб. В ViewController.swift добавьте следующий метод:

01
02
03
04
05
06
07
08
09
10
11
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
    let touch = touches.first as?
    let location = touch?.locationInView(self.sceneView)
    let hitResults = self.sceneView.hitTest(location!, options: nil)
     
    for result in (hitResults as! [SCNHitTestResult]) {
        if result.node == (self.sceneView.scene as! MainScene).cubeNode {
            self.spriteScene.score += 1
        }
    }
}

Обратите внимание, что мы использовали метод touchesEnded(_:withEvent:) а не UITapGestureRecognizer , поскольку объекты UIGestureRecognizer приводят к touchesEnded(_:withEvent:) , что UIGestureRecognizer touchesEnded(_:withEvent:) выполняется очень непоследовательно. Поскольку вы используете этот метод для кнопки паузы, вы должны быть уверены, что она будет вызываться каждый раз, когда пользователь нажимает на экран.

В touchesEnded(_:withEvent:) мы выполняем проверку попадания для окончательного местоположения касания в вашем sceneView . Если местоположение касания соответствует местоположению куба, к баллу вашей spriteScene добавляется одно очко. Текст в сцене будет автоматически обновляться благодаря наблюдателю свойства свойства OverlayScene классе OverlayScene .

Запустите приложение снова и коснитесь куба, чтобы убедиться, что метка счета обновляется при каждом касании куба.

Оценка обновлений при нажатии куба

И кнопка паузы, и метка счета служат примером того, как вы можете передавать информацию между сценами SpriteKit и SceneKit. Эта информация, однако, не ограничивается числовыми и логическими значениями, это может быть любой тип данных, который нужен вашему проекту.

Помимо возможности наложения сцены SpriteKit поверх сцены SceneKit, вы также можете использовать экземпляр SKScene в качестве материала для геометрии SceneKit. Это делается путем присвоения объекта SKScene свойству contents объекта SCNMaterialProperty . Это позволяет легко добавлять анимированный материал на любой трехмерный объект.

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

01
02
03
04
05
06
07
08
09
10
11
override init() {
    super.init()
     
    let cube = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0)
    let cubeMaterial = SCNMaterial()
    cubeMaterial.diffuse.contents = UIColor.blueColor()
    cube.materials = [cubeMaterial]
    self.cubeNode = SCNNode(geometry: cube)
    self.cubeNode.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 0.01, z: 0, duration: 1.0/60.0)))
    …
}

с этим блоком кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
override init() {
    super.init()
     
    let cube = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0)
     
    let materialScene = SKScene(size: CGSize(width: 100, height: 100))
    let backgroundNode = SKSpriteNode(color: UIColor.blueColor(), size: materialScene.size)
    backgroundNode.position = CGPoint(x: materialScene.size.width/2.0, y: materialScene.size.height/2.0)
    materialScene.addChild(backgroundNode)
    let blueAction = SKAction.colorizeWithColor(UIColor.blueColor(), colorBlendFactor: 1, duration: 1)
    let redAction = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1, duration: 1)
    let greenAction = SKAction.colorizeWithColor(UIColor.greenColor(), colorBlendFactor: 1, duration: 1)
    backgroundNode.runAction(SKAction.repeatActionForever(SKAction.sequence([blueAction, redAction, greenAction])))
     
    let cubeMaterial = SCNMaterial()
    cubeMaterial.diffuse.contents = materialScene
    cube.materials = [cubeMaterial]
    self.cubeNode = SCNNode(geometry: cube)
    self.cubeNode.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 0.01, z: 0, duration: 1.0/60.0)))
    …
}

Этот фрагмент кода создает простой экземпляр SKScene с одним фоновым узлом, который изначально имеет синий цвет. Затем мы создаем три действия для перехода от синего к красному и зеленому. Мы выполняем действия в повторяющейся последовательности на фоновом узле.

Хотя в нашем примере мы использовали базовые цвета, все, что можно сделать в сцене SpriteKit, можно превратить в материал SceneKit.

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

Изменение цвета куба

Обратите внимание, что приостановка сцены SceneKit не приостанавливает сцену SpriteKit, которую мы использовали в качестве материала. Чтобы приостановить анимацию материала, вам нужно сохранить ссылку на сцену SpriteKit и сделать ее явной паузой.

Объединение содержимого SpriteKit и SceneKit может быть очень полезным различными способами. Наложение 2D-сцены поверх 3D-сцены позволяет создать динамический интерфейс, что приведет к повышению производительности, поскольку обе сцены используют один и тот же контекст OpenGL и конвейер рендеринга. Вы также узнали, как использовать SpriteKit для создания анимированных материалов для 3D-объектов. Если у вас есть какие-либо комментарии или вопросы, оставьте их в комментариях ниже.