Un vistazo a SpriteKit con Swift por @zjorge

| Última modificación: 24 de abril de 2024 | Tiempo de Lectura: 6 minutos

Algunos de nuestros reconocimientos:

Premios KeepCoding

Código fuente de la App de Ejemplo

Apenas minutos después del anuncio de la llegada de Swift en el WWDC 2014 ya se sentía en el Moscone Center una mezcla de duda, pánico y entusiasmo.  Creo que mi sentimiento era de duda pues con lo poco que conozco de Objective C y de programación en general no sufro el pánico por lo conocido en ObjC pero tampoco entiendo el entusiasmo promovido por Craig con las nuevas características de Swift. Para poder evaluar realmente las ventajas o desventajas de Swift mi mejor opción es simplemente experimentar.

Hasta ahora mi experiencia para gráficos 2D ha sido con Cocos2D usando SpriteBuilder para el montaje de las escenas y TexturePacker para preparar los sprite sheets.  Me pareció una buena idea aprovechar mis experimentos con Swift e integrar SpriteKit, principalmente para probar lo de per pixel collision.

Creando un nuevo proyecto en XCode 6

Comencemos creando un nuevo proyecto en XCode 6.  Como template vamos a elegir las opciones iOS -> application y Game.  Luego le colocamos nombre y definimos Swift como language y como Game Technology utilizaremos SpriteKit con Swift.

SpriteKit con Swift

Captura de pantalla 2014-07-10 09.39.59

Limitemos en este caso la orientación a las opciones Landscape derecha e izquierda.  Aunque la opción de cambiar la versión de iOS no está deshabilitada, este tutorial no funciona con versiones anteriores a iOS 8.0 por lo que debe quedar Deployment Target en 8.0 y en caso de probarlo en un dispositivo éste debe tener instalado iOS 8 también.

Captura de pantalla 2014-07-10 09.52.13  Captura de pantalla 2014-07-10 09.44.30

Este template tiene, además de las imágenes, los archivos de soporte y las carpetas de test y productos cinco archivos. Tres son swift, un storyboard y una escena.  En este tutorial sólo vamos a agregar imágenes y editar los archivos GameScene.swiftGameScene.sks y GameViewController.swift.  En  GameScene.swift borremos el contenido de las funciondidMoveToView  y en touchesBegan  borramos lo que está dentro del for  para quedar así:

import SpriteKit

class GameScene: SKScene {
   override func didMoveToView(view: SKView) {

   }

   override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
      /* Called when a touch begins */
      for touch: AnyObject in touches {

      }
   }

   override func update(currentTime: CFTimeInterval) {
      /* Called before each frame is rendered */
   }
}

La primera línea que vamos a colocar simplemente va a guardar en location la posiciones para cada contacto en la pantalla.

for touch: AnyObject in touches {
   let location = touch.locationInNode(self);
}

El punto y coma es completamente opcional pero después de años de PHP, JavaScript y ObjC se me hace automático colocarlo. Para poder ver en la cosola y comprobar que algo está sucediendo agreguemos la siguiente línea:

for touch: AnyObject in touches {
   let location = touch.locationInNode(self);
   println("\(location.x.description),\(location.y.description)")     
}

Es hora de compilar!  Mi recomendación es hacerlo en un dispositivo en vez del simulador.  En el dispositivo podemos probar multitoques y manipular la aplicación tal cual lo haría el usuario.  Una vez inicia la aplicación y hagamos algunos toques en la pantalla debemos ver en la consola algo así:

314.0,263.5
162.0,202.5
458.5,279.5
373.5,292.0
436.0,291.5

Hasta ahora, aunque sí estamos cargando la escena definida en GameScene.sks, no hemos escrito nada que utilice SpriteKit.  Arrastremos los archivos de imagen guanabara.jpg, redentor.png y brazuca_mini.png a la carpeta Supporting Files y luego, agreguemos touchesBegan  las siguientes líneas:

for touch: AnyObject in touches {

   let location = touch.locationInNode(self);

   println("\(location.x.description),\(location.y.description)")
   let balon = SKSpriteNode(imageNamed:"brazuca_mini.png")
   balon.xScale = 0.50
   balon.yScale = 0.50

   balon.position = location

   self.addChild(balon)
}

Si compilamos ahora podemos ver que al correr la aplicación, para cada toque en la pantalla, además de imprimir las coordenadas en la consola veremos también una brazuca en la pantalla.

SpriteKit con Swift

Todas quietas, sin gravedad, sin impulso, sin efecto de fricción, sin posibilidad de gol.  Vamos a agregarle algo de física.  Justo antes deaddChild  colocamos:

for touch: AnyObject in touches {

   let location = touch.locationInNode(self);

   println("\(location.x.description),\(location.y.description)")
   let balon = SKSpriteNode(imageNamed:"brazuca_mini.png")

   balon.xScale = 0.50
   balon.yScale = 0.50

   balon.position = location

   balon.physicsBody = SKPhysicsBody(circleOfRadius:40)
   balon.physicsBody.dynamic = true
   self.addChild(balon)

}

Aquí lo único que estamos haciendo es darle al sprite un cuerpo y diciéndole a este cuerpo que responde a propiedades físicas como gravedad y colisiones. Probemos que sucede ahora (cmd + b).  Al correr la aplicación notamos que ahora la gravedad hace caer los balones y si los dejamos tocar podemos ver que obedecen también a las colisiones entre ellos.  Es un poco difícil lograr una colisión si no tenemos un piso que detenga la caída así que agregaremos uno.  Para eso vamos a agregar la siguiente función:

func agregaSolidoEnPunto(elPunto: CGPoint, ancho: CGFloat, alto: CGFloat) {

    let tamanio = CGSize(width: ancho, height: alto)
    let laPared = SKNode()
    laPared.position = CGPoint( x: elPunto.x + tamanio.width * 0.5, y: elPunto.y - tamanio.height * 0.5)

    laPared.physicsBody = SKPhysicsBody(rectangleOfSize: tamanio)
    laPared.physicsBody.dynamic = false
    laPared.physicsBody.collisionBitMask = 0
    self.addChild(laPared)

}

Con esta función podemos agregar sólidos rectangulares fijos en cualquier punto de la pantalla (o fuera de ella).  Probemos llamando la función agregando adidMoveToView  las dos líneas a continuación:

override func didMoveToView(view: SKView) {
      let origen = CGPoint(x: 100, y: 100)
      agregaSolidoEnPunto(origen,ancho:20,alto:200)
}

Ahora tenemos un cuerpo sólido de 20 x 200 colocado en el punto 100,100 pero el sólido es invisible.

balones_SpriteKit con Swift

Podemos notar su presencia si tiramos muchos balones en el lado izquierdo de la pantalla pero es mucho mejor, para poder entender que está sucediendo, utilizar la ayuda que nos brinda SKView con la propiedadshowsPhysics . Vamos a abrir el archivo GameViewController.swift y dentro de viewDidLoad , luego deshowsFPS  yshowsNodeCount , agregamos skView.showsPhysics = true  para tener algo así:

class GameViewController: UIViewController {
   override func viewDidLoad() {
      super.viewDidLoad()
      if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
         // Configure the view.
         let skView = self.view as SKView
         skView.showsFPS = true
         skView.showsNodeCount = true
         skView.showsPhysics = true
           ...

Si compilamos ahora podemos demostrar nuestra destreza con el balón equilibrándolo sobre una pared invisible.

Ahora pongamos un escenario propicio para estos balones. Vamos a abrir GameScene.sks y primero cambiamos el ancho y alto de la escena en el panel de utilities a 576 x 320 como indica la imagen.

Captura de pantalla 2014-07-10 13.43.30

Ahora arrastramos desde la carpeta Supporting Files el archivo guanabara.jpg y lo dejamos caer en el centro de la escena. Asegurémonos que la imagen (node) está seleccionada y ajustemos la posición según vemos en la captura de pantalla.

Captura de pantalla 2014-07-10 17.11.29

Este es un fondo estático.  No va a tener ninguna influencia sobre la dinámica de los balones y tampoco recibirá los efectos de la gravedad en la escena. Por esta razón tenemos que ajustar sus propiedades físicas a Body Type: none.

Captura de pantalla 2014-07-10 17.39.08

Vamos a agregar ahora otro nodo, agreguemos al Cristo Redentor arrastrando el archivo redentor.png a la escena.  Ajustemos la posición como lo indica la imagen y probemos la aplicación ahora.

Captura de pantalla 2014-07-10 17.19.58

Vemos que los toques envían sus coordenadas a la consola pero es posible que los balones no se estén creando.  En realidad si están siendo creados pero están detrás de fondo.  Hagamos un pequeño ajuste para corregir esto. En GameScene.sks seleccionamos el nodo donde está la imagen de fondo y en panel de propiedades verifiquemos que zPosition está en 0, luego en GameScene.swift asignemos al balón creado un zPosition = 10  para asegurarnos de que vaya a quedar frente al fondo.

      ...
      balon.physicsBody.dynamic = true
      balon.zPosition = 10
      self.addChild(balon)
}

Probemos nuevamente y vemos como los balones caen frente al paisaje y chocan con el redentor cuando caen sobre ese nodo.  El problema aquí es que el nodo es rectangular y no interactúa como esperado con los balones.  Para lograr colisiones más precisas sería necesario desglosar la forma del redentor en partes más sencillas y unirlas o crear un polígono complejo pero aún así los resultados probablemente sería aproximados.

captura

Utilizando “per-pixel collision”

Para resolver esto ahora tenemos “per-pixel collision”, un método de cálculo de colisiones que utiliza el canal alpha para determinar el borde del sólido.  Hay que resaltar que este método consume muchos más recursos que los rectangulares o circulares, usar con moderación.

captura

Si quieres dejar tu aplicación algo más presentable puedes comentar las líneas auxiliares SKView en GameViewController.swift funciónviewDidLoad :

let skView = self.view as SKView
//skView.showsFPS = true
//skView.showsNodeCount = true
//skView.showsPhysics = true

Ahora podemos ver al redentor haciendo ciertos trucos con las brazucas.  Nada que evite un 7 a 1 desafortunadamente.

mundial

Al comparar Swift con Objective-C en algo sencillo como esto no veo mucha diferencia, de hecho rápidamente olvido que no es Objective-C pues al trabajar con SpriteKit la sintaxis es bastante similar.  Puede que la razón de esto es que aún abordamos todo a la ObjC y dejamos de ver o aprovechar ventajas propias de Swift.  Creo que, como dice mi mujer, “amanecerá y veremos”.

Código fuente de la App de Ejemplo

Fernando Rodríguez

iOS Developer & Co-Fundador de KeepCoding

Posts más leídos