Advertisement
  1. Code
  2. SpriteKit

Combinando el poder de SpriteKit y SceneKit

Scroll to top
Read Time: 8 min

Spanish (Español) translation by steven (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Introducción

SpriteKit y SceneKit son frameworks de iOS diseñados para facilitar a los desarrolladores la creación de activos 2D y 3D en juegos casuales. En este tutorial, te mostraré cómo combinar el contenido creado en ambos frameworks en una sola vista para utilizar las API que Apple ha puesto a disposición.

Lo lograrás agregando un botón simple de reproducción/pausa y funcionalidades de mantenimiento de puntajes a una escena de SceneKit 3D mediante el uso de una interfaz basada en 2D SpriteKit.

Este tutorial requiere que estés ejecutando al menos Xcode 6+ y tengas experiencia previa con el contenido básico de SpriteKit y SceneKit. Si no es así, te recomiendo que primero leas algunos de nuestros otros tutoriales de Tuts+ sobre SpriteKit y SceneKit.

También se recomienda que tengas un dispositivo iOS físico para usar en las pruebas, lo que requiere una cuenta de desarrollador iOS de pago activa. También deberás descargar el proyecto de inicio desde GitHub.

1. Configuración del proyecto

Cuando abras el proyecto de inicio, verás que, además de las clases AppDelegate y ViewController predeterminadas, también tienes otras dos clases, MainScene y OverlayScene.

La clase MainScene es una subclase de SCNScene y proporciona el contenido 3D de tu aplicación. Asimismo, la clase OverlayScene es una subclase de SKScene y contiene el contenido de SpriteKit 2D en tu aplicación.

No dudes en ver las implementaciones de estas clases. Debería resultarte familiar si tienes alguna experiencia con SpriteKit y SceneKit. En el método viewDidLoad de tu clase ViewController, también encontrarás el código para configurar una instancia básica de SCNView.

Crea y ejecuta tu aplicación en el simulador de iOS o en tu dispositivo físico. En esta etapa, tu aplicación contiene un cubo giratorio azul.

Initial 3D sceneInitial 3D sceneInitial 3D scene

Es hora de agregar una escena SpriteKit sobre el contenido 3D. Esto se hace estableciendo la propiedad overlaySKScene en cualquier objeto que cumpla con el protocolo SCNSceneRenderer, en nuestro ejemplo esa es la instancia de SCNView. En ViewController.swift agrega las siguientes líneas al método viewDidLoad:

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

Cuando compiles y ejecutes tu aplicación nuevamente, verás que ahora tienes un botón de pausa ubicado en la parte inferior izquierda y una etiqueta de puntuación en la parte inferior central de la vista de tu aplicación.

Overlay SpriteKit sceneOverlay SpriteKit sceneOverlay SpriteKit scene

Quizás te preguntes por qué es mejor usar esta propiedad en lugar de simplemente agregar un SKView como una subvista del objeto SCNView. Cuando se usa la propiedad overlaySKScene, los componentes 2D y 3D de tu aplicación usan el mismo contexto OpenGL para representar contenido en la pantalla. Esto funciona significativamente mejor que la creación de dos vistas separadas, cada una de las cuales tendría su propio contexto OpenGL y canal de procesamiento. Aunque la diferencia es insignificante para esta configuración simple, el rendimiento obtenido en escenas más complejas puede ser invaluable.

2. Interacción de SceneKit y SpriteKit

Hay muchas formas diferentes en las que puedes transferir información entre tus instancias de MainScene y OverlayScene. En este tutorial, utilizarás la observación de valores-clave, KVO para abreviar.

Antes de implementar la capacidad de pausar la animación del cubo, primero debes agregar esta funcionalidad a la escena SpriteKit. En OverlayScene.swift, agrega el siguiente método a la clase OverlayScene:

1
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
2
    
3
    let touch = touches.first as? UITouch
4
    let location = touch?.locationInNode(self)
5
    
6
    if self.pauseNode.containsPoint(location!) {
7
        if !self.paused {
8
            self.pauseNode.texture = SKTexture(imageNamed: "Play Button")
9
        }
10
        else {
11
            self.pauseNode.texture = SKTexture(imageNamed: "Pause Button")
12
        }
13
        
14
        self.paused = !self.paused
15
    }
16
}

El método touchesEnded(_: withEvent:) se invoca cuando el usuario levanta sus dedos de la pantalla del dispositivo. En este método, verificas si el usuario ha tocado el botón de pausa y actualiza la escena en consecuencia. Compila y ejecuta tu aplicación nuevamente para verificar que esta pieza del rompecabezas funcione correctamente.

Working pause buttonWorking pause buttonWorking pause button

Ahora debemos detener la rotación del cubo 3D cuando el usuario presionó el botón de pausa. Vuelve a ViewController.swift y agrega la siguiente línea al método viewDidLoad:

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

Finalmente, agrega el siguiente método a MainScene.swift para habilitar la observación de valores clave:

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

Si compilas y ejecutas tu aplicación nuevamente, el cubo debe dejar de girar cuando se toca el botón de pausa.

Así como la información se puede transferir de una escena de SpriteKit a una escena de SceneKit, también puedes enviar datos desde instancias de SceneKit a instancias de SpriteKit. En esta sencilla aplicación, agregarás un punto a la puntuación cada vez que el usuario toque el cubo. En ViewController.swift, agrega el siguiente método:

1
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
2
    let touch = touches.first as? UITouch
3
    let location = touch?.locationInView(self.sceneView)
4
    let hitResults = self.sceneView.hitTest(location!, options: nil)
5
    
6
    for result in (hitResults as! [SCNHitTestResult]) {
7
        if result.node == (self.sceneView.scene as! MainScene).cubeNode {
8
            self.spriteScene.score += 1
9
        }
10
    }
11
}

Ten en cuenta que usamos el método touchesEnded(_:withEvent:) en lugar de un UITapGestureRecognizer, porque los objetos UIGestureRecognizer hacen que el método touchesEnded(_:withEvent:) se ejecute de manera muy inconsistente. Dado que utilizas este método para el botón de pausa, debes asegurarte de que se llamará cada vez que el usuario toque la pantalla.

En el método touchesEnded(_:withEvent:), realizamos una prueba de impacto para la ubicación final de un toque en tu sceneView. Si la ubicación del toque se corresponde con la ubicación del cubo, se agrega un punto a la puntuación de tu spriteScene. El texto de la escena se actualizará automáticamente gracias al observador score de la propiedad de puntuación en la clase OverlayScene.

Ejecuta tu aplicación nuevamente y toca el cubo para verificar que la etiqueta de puntuación se actualice cada vez que se toca el cubo.

Score updates when cube is tappedScore updates when cube is tappedScore updates when cube is tapped

Tanto el botón de pausa como la etiqueta de puntuación ejemplifican cómo puedes transferir información entre escenas de SpriteKit y SceneKit. Esta información, sin embargo, no se limita a valores numéricos y booleanos, puede ser cualquier tipo de datos que tu proyecto necesite.

3. Uso de escenas de SpriteKit como materiales de SceneKit

Además de poder superponer una escena de SpriteKit sobre una escena de SceneKit, también puedes utilizar una instancia de SKScene como material para la geometría de SceneKit. Esto se hace asignando un objeto SKScene a la propiedad contents de un objeto SCNMaterialProperty. Esto te permite agregar fácilmente un material animado a cualquier objeto 3D.

Veamos un ejemplo. En tu aplicación, agregarás una animación de transición de color simple al cubo en lugar del azul estático que tienes actualmente. En el método init de la clase MainScene, reemplaza el siguiente bloque de código:

1
override init() {
2
    super.init()
3
    
4
    let cube = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0)
5
    let cubeMaterial = SCNMaterial()
6
    cubeMaterial.diffuse.contents = UIColor.blueColor()
7
    cube.materials = [cubeMaterial]
8
    self.cubeNode = SCNNode(geometry: cube)
9
    self.cubeNode.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 0.01, z: 0, duration: 1.0/60.0)))
10
    ...
11
}

con este bloque de código:

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

Este fragmento de código crea una instancia simple de SKScene con un nodo de fondo, que inicialmente es azul. Luego creamos tres acciones para la transición de azul a rojo y verde. Ejecutamos las acciones en una secuencia repetida en el nodo de fondo.

Si bien hemos utilizado colores básicos en nuestro ejemplo, cualquier cosa que se pueda hacer en una escena de SpriteKit se puede convertir en un material de SceneKit.

Compila y ejecuta tu aplicación por última vez. Verás que el color del cubo cambia de azul a rojo y verde.

Cube colour changeCube colour changeCube colour change

Ten en cuenta que al pausar la escena SceneKit no se detiene la escena SpriteKit que hemos utilizado como material. Para pausar la animación del material, deberás mantener una referencia a la escena SpriteKit y pausarla explícitamente.

Conclusión

La combinación de contenido de SpriteKit y SceneKit puede ser muy beneficiosa de varias formas. La superposición de una escena 2D sobre una escena 3D te permite crear una interfaz dinámica y dará como resultado una ganancia de rendimiento, ya que ambas escenas utilizan el mismo contexto OpenGL y canalización de renderizado. También aprendiste cómo aprovechar SpriteKit para crear materiales animados para objetos 3D. Si tienes algún comentario o pregunta, déjalo en los comentarios a continuación.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.