Вступление
SpriteKit и SceneKit — это платформы iOS, разработанные для того, чтобы облегчить разработчикам создание 2D и 3D ресурсов в казуальных играх. В этом руководстве я покажу вам, как объединить контент, созданный в обеих платформах, в одно представление, чтобы использовать API, которые Apple сделала доступными.
Вы достигнете этого, добавив простую кнопку воспроизведения / паузы и функции ведения счета к 3D-сцене SceneKit, используя 2D-интерфейс на основе SpriteKit.
Это руководство требует, чтобы вы работали как минимум с Xcode 6+ и имели опыт работы с базовым содержимым SpriteKit и SceneKit. Если нет, то я рекомендую сначала прочитать некоторые другие наши учебники Tuts + о SpriteKit и SceneKit .
Также рекомендуется использовать физическое устройство iOS для тестирования, для которого требуется активная платная учетная запись разработчика iOS. Вам также необходимо скачать стартовый проект с GitHub .
1. Настройка проекта
Когда вы откроете начальный проект, вы увидите, что, помимо стандартных AppDelegate
и ViewController
, у вас также есть два других класса, MainScene
и OverlayScene
.
Класс MainScene
является подклассом SCNScene
и предоставляет 3D-контент вашего приложения. Аналогично, класс OverlayScene
является подклассом SKScene
и содержит 2D-контент SpriteKit в вашем приложении.
Не стесняйтесь смотреть на реализации этих классов. Это должно выглядеть знакомо, если у вас есть некоторый опыт работы с SpriteKit и SceneKit. В ViewController
вашего класса viewDidLoad
вы также найдете код для настройки базового экземпляра SCNView
.
Создайте и запустите приложение на iOS Simulator или на физическом устройстве. На этом этапе ваше приложение содержит синий вращающийся куб.
Пришло время добавить сцену 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
}
|
Когда вы снова создадите и запустите свое приложение, вы увидите, что теперь у вас есть кнопка паузы, расположенная в левом нижнем углу, и метка оценки в нижней центральной части представления вашего приложения.
Вы можете задаться вопросом, почему лучше использовать это свойство, а не просто добавить SKView
в качестве SCNView
объекта SCNView
. При overlaySKScene
свойства overlaySKScene
и 2D, и 3D компоненты вашего приложения используют один и тот же контекст OpenGL для отображения содержимого на экране. Это работает значительно лучше, чем создание двух отдельных представлений, каждое из которых имеет собственный контекст OpenGL и конвейер рендеринга. Несмотря на то, что для этой простой установки разница незначительна, производительность, полученная в лагере, может быть неоценимой для более сложных сцен.
2. Взаимодействие SceneKit и SpriteKit
Существует много разных способов передачи информации между 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. Эта информация, однако, не ограничивается числовыми и логическими значениями, это может быть любой тип данных, который нужен вашему проекту.
3. Использование сцен 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-объектов. Если у вас есть какие-либо комментарии или вопросы, оставьте их в комментариях ниже.