Home » Cursos » SpriteKit, juegos 2D (I): Sprites (en Swift y Objective-C)
Curso SpriteKit (I)

SpriteKit, juegos 2D (I): Sprites (en Swift y Objective-C)

Comenzamos a hablar de SpriteKit, y lo hacemos directamente metiéndonos en vereda. Y además, os lo presentamos en un nuevo y revolucionario formato: el curso dual. Cuando haya código fuente de referencia, podréis ver el código para seguir la lección tanto en Swift como en Objective-C, pudiendo elegir cuál de las dos opciones preferís. Y lo primero son los sprites, qué son y cómo manipularlos.

Creando el proyecto

Podéis seguir esta lección de 2 formas diferentes, aunque ambos parten de la creación de un proyecto. Lo único que hemos de hacer es abrir Xcode 6 y elegir Create a New Project. Una vez ahí, vamos a iOS, elegimos Application, y veremos que la última opción que nos da es Game.

Si queremos usar Swift, alternativamente podríamos escoger crear un Playground siguiendo nuestra guía y podréis ver los resultados en tiempo real. Para ello deberéis copiar los recursos del proyecto como se indica en dicha guía.

SpriteKit Proyecto

Elegimos un nombre para el proyecto, el nombre de nuestra compañía y el identificador de la misma (normalmente las apps tienen un ID que corresponde a com. seguido del nombre de la empresa o desarrollador seguido de otro punto y el nombre). Aquí podemos elegir el lenguaje (Swift u Objective-C), la tecnología en que haremos el juego (entre SpriteKit, SceneKit, OpenGL ES o Metal) y si el juego será Universal, solo para iPhone y iPod Touch o solo para iPad. Elegimos SpriteKit y Universal, luego el lenguaje que queramos y pulsamos Next.

Partes del proyecto

Las partes comunes de ambos proyectos son 4: un fichero GameScene.sks que corresponde a nuestra escena principal, el fichero .storyboard principal (que se une al controlador de la vista), un conjunto de imágenes Images.xcassets donde tenemos un asset (o conjunto de recursos) donde están los placeholders (o huecos) para poner los diferentes iconos que necesita la app y la imagen de muestra que nos permitirá jugar en este primer tutorial: Spaceship.png.

También tenemos el archivo LaunchScreen.xib, una nueva forma de arrancar las apps que se incorpora en iOS 8. Este, a través de una vista UIView de inicio, nos permite tener una vista en vez de una simple imagen estática como hasta ahora. De esta forma, con una sola imagen dentro de una vista, podemos mediante restricciones (constraints) conseguir que esta se adapte a cada dispositivo de manera más óptima, sin tener que preparar todas y cada una de ellas para cada resolución y dispositivo.

El resto de archivos los vemos a continuación en función del lenguaje que hayáis elegido:

  • Swift
  • Objective-C
AppDelegate.swift, donde tenemos la delegación de la app y donde responde la ejecución de toda ella. En el famoso didFinishLaunchingWithOptions es donde inicializamos cosas que necesitemos al arrancar la app como analíticas, control de compras integradas, etc. Tenemos funciones que se lanzan cuando la app se cierra, cuando entra en un proceso en segundo plano, cuando sale de él…
GameScene.swift, donde tenemos un pequeño ejemplo que nos dibuja la nave en pantalla en el lugar donde toquemos y crea una acción que hace que rote sobre sí misma continuamente. Todo ello sobre un fondo que dice “Hello World!”. Este archivo es nuestra escena principal.
GameViewController.swift, el controlador de la vista, que lo primero que hace es extender el tipo SKNode (nodo principal de SpriteKit) para cargar el contenido del fichero .sks y mostrarlo tal cual se configure. Mejor no tocar esto, pues es imprescindible esta extensión del tipo para que los editores de escenas (que veremos en otra lección) funcionen correctamente.
AppDelegate.m al que acompaña su cabecera .h. En él tenemos la delegación de la app y donde responde la ejecución de toda ella. En el famoso didFinishLaunchingWithOptions es donde iniciamos cosas que necesitemos al arrancar la app como analíticas, control de compras integradas, etc. Tenemos funciones que se lanzan cuando la app se cierra, cuando entra en un proceso en segundo plano, cuando sale de él…
GameScene.m y su cabecera .h. Aquí tenemos un pequeño ejemplo que nos dibuja la nave en pantalla en el lugar donde toquemos y crea una acción que hace que rote sobre sí misma continuamente. Todo ello sobre un fondo que dice “Hello World!”. Este archivo es nuestra escena principal.
GameViewController.m y su cabecera .h, el controlador de la vista, que lo primero que hace es modificar y extender la implementación del tipo SKScene (tipo principal de las escenas en SpriteKit) añadiendo un nuevo método unarchiveFromFile que permite cargar el contenido del fichero .sks y mostrarlo tal cual se configure. Mejor no tocar esto, pues es imprescindible para que los editores de escenas (que veremos en otra lección) funcionen correctamente. Al ser el controlador de vista principal, cualquier nueva escena que creáramos y llamáramos desde el controlador, heredaría esta ampliación en la implementación.

Estructura de SpriteKit

SpriteKit se compone de una vista principal, en cuyo interior reside una escena (SKScene). Dicha escena, como nodo principal de la misma, es un SKNode (nodo), objeto principal de todo SpriteKit. Mientras puede haber tantos nodos como se quiera, tanto a un mismo nivel como aninados como padre-hijo, no se puede presentar más de un escena a la vez aunque se pueden crear varias y cambiar de una a otra durante la ejecución del juego.

Una escena podría ser el menú principal, o la pantalla de selección, los niveles de juego… cada uno sería una escena, que como hemos dicho, no permite mostrar más de una a la vez. El resto de elementos, todos también hijos de SKNode, puede haber tantos como se quiera y para poder verlos hay que añadirlos a la escena con la instrucción addChild. Un cambio de escena supone la descarga de memoria de todos sus recursos y contenidos para cargar la siguiente.

Cualquier SKNode, y en su defecto la SKScene, puede contener sprites, etiquetas, texturas, cuerpos físicos, partículas, campos de fuerza y mucho más. SKNode es el objeto que los contiene y donde están las propiedades que todos comparten: posición, punto de anclaje, rotación o escala, entre otras.

El sprite (SKSpriteNode) a su vez tiene tamaño, color o la textura (o imagen) que lo representa y que vemos en pantalla. La creación de un sprite depende normalmente de la imagen o textura que le asignemos. También nos permite cambiar dicha imagen en tiempo de ejecución en cualquier momento o que esta forme parte de una animación que permita en un mismo sprite, mostrar varios estados para conseguir, por ejemplo, que un personaje ande por pantalla mostrando los diferentes fotogramas (o imágenes) que dan forma a dicha animación.

Los sprites normalmente disponen de un capa alfa (por lo que suele usarse siempre el formato PNG como base) que distingue sobre el rectángulo de la imagen qué parte es transparente y debe mostrar lo que hay detrás y qué parte no. Es importante notar que cuando detectamos si hemos tocado a un sprite con nuestro dedo, la comprobación se hace sobre lo que se denomina el frame que es el cuadro que lo contiene incluyendo las partes transparentes. Por lo tanto, tocar un sprite en una zona donde no lo veamos es totalmente posible si forma del cuadrado que forma la imagen.

Para evitar esto, hay que saltar al mundo de la física de objetos y aplicar lo que se llaman colisiones de pixel perfecto, que veremos en otro capítulo más adelante y que el nuevo SpriteKit facilita muchísimo.

Creando nuestro propio ejemplo

Primero vamos a limpiar nuestro código, y para ello vamos a GameScene y, según el lenguaje, lo dejamos como sigue:

  • GameScene.swift (Swift)
  • GameScene.m (Objective-C)

El método DidMoveToView que recibe como parámetro la vista de iOS donde está contenida nuestra escena, es la inicialización de este método: lo primero que se llama. Allí es donde comúnmente se pre-dibuja toda la escena y sus componentes. Se puede poner el código dentro del propio método o, si queremos organizarlo bien, llamar a otros métodos en la misma clase u otra, pero lo que importa es que todo se ejecutará en cuanto la vista se presente.

El método touchesBegan, que siempre ha de tener esa construcción y de hecho se nos pondrá tal cual, hereda de la clase superior de la propia vista y nos permite controlar los diferentes toques a la pantalla: cada vez que un dedo toque la pantalla, ese inicio de gesto, provocará que se ejecute el código que hay en este método.

Vemos que tenemos un bucle que recorre un array llamado touches donde se registran los n toques que hayan provocado que se ejecute este método. Por cada uno de ellos se recupera la localización del toque dentro de la vista y se guarda dicha coordenada en una variable location de tipo CGPoint, una variable con dos componentes de tipo flotante: x e y, que guardan las coordenadas bidimensionales de un punto de localización en la pantalla.

Vamos a pintar dos naves, cada una en su variable pues, aunque sean la misma imagen si queremos ponerla dos veces hemos de crear dos sprites diferentes. Así que sustituimos el método didMoveToView de antes por este nuevo.

  • GameScene.swift (Swift)
  • GameScene.m (Objective-C)

Si hemos ejecutado en un iPhone veremos dos naves gigantes en pantalla, una encima de la otra. La extensión del archivo (.png en este caso) no hay que ponerla ya que el sistema se encarga solo de buscarla. Pero vamos a fijarnos en un curioso detalle: la que primero hemos puesto en pantalla por orden de instrucción (la nave 1) está en la coordenada y 200. En SpriteKit, los puntos se empiezan a contar desde abajo, no desde arriba como cuando construimos interfaces (se debe a que OpenGL ES lo hace así), por lo que la nave1 (la primera dibujada) está debajo de la nave2 que es la segunda.

Esto se debe a que no hemos indicado posición del elemento dentro de la tercera coordenada: la z. Cuando no indicamos profundidad (o posición z, como se llama en SpriteKit) este orden se establece por orden de dibujado. Si le diéramos la vuelta a la instrucciones addChild para pintar antes la nave2, veríamos que ahora se invierte y la nave situada más abajo se sitúa sobre la de más arriba.

Pero esto podemos controlarlo manualmente fijando el parámetro zPosition. Vamos a volver a poner el dibujado en su orden natural (primero la 1 y luego la 2) pero antes de dibujarlas vamos a poner:

Si estáis con el ejemplo en Objective-C, solo poned un punto y coma al final de cada línea para que funcione.

SK 01 zPosition Automática
SK 01 zPosition Manual

Como veis en la imagen la diferencia es bien clara. SpriteKit funciona por capas como haría un software como el famoso Photoshop. Establece una posición de profundidad y crea la ilusión de diferentes planos de profundidad a la hora de mostrar las cosas. No por nada, SpriteKit no deja de ser un motor 2D sobre uno 3D, donde en realidad trabajamos con una cámara fija mirando de frente a diferentes planos.

Ahora vamos a intentar modificar el tamaño de la nave ya que es muy grande para controlarla fácilmente. Modificamos las propiedades xScale e yScale, ya que en SpriteKit las proporciones de la x y la y van por separado. Vamos a poner una al 50% y la otra al 40% de su tamaño). Esta propiedad, de tipo flotante, da sus valores de 0 a 1, por lo que si queremos un 50% de reducción el valor habría que indicar 0.5.

  • Swift
  • Objective-C

El movimiento se demuestra tocando, drag & drop

Y ahora vamos a mover el sprite por la pantalla con nuestro dedo (o ratón si estamos en el simulador iOS). Para ello primero vamos a poner un nombre a cada sprite que nos permita identificarlo.

  • Swift
  • Objective-C

El motivo de esto es simple: cuando tocamos la pantalla solo podemos recuperar el punto donde el dedo ha tocado: nada más. Luego podemos preguntar qué nodo (o sprite en nuestro caso) está en dicha posición, pero si queremos que este quede “marcado” y podamos usarlo para moverlo (que se hace en otro método) nos conviene que tenga un nombre por el que podamos recuperarlo en cualquier momento.

Y ahora vamos con lo complicado: para mover un sprite arrastrándolo, tenemos que ir siguiendo el punto que se ha tocado en ese momento con el dedo pero a su vez el que se tocó previamente, con lo que conseguimos justo la posición que hemos de sumar a la actual de nuestro sprite para que siga al dedo.

Primero creamos dos variables locales a la clase, justo encima del método didMoveToView. Ojo, en Objective-C hay que incluir una línea tocoNave = NO dentro del método didMoveToView para inicializar el valor la primera vez.

  • Swift
  • Objective-C

Luego vamos a usar los cuatro métodos de control de toques en pantalla: touchesBegan, touchesMoved, touchesEnded y touchesCancelled (este último por seguridad). Cuando el dedo comienza a tocar vemos a qué nave ha tocado y la marcamos encendiendo el semáforo que nos indica que hemos iniciado un movimiento (un tipo bool que ponemos a true o YES, dependiendo del lenguaje).

Cuando el dedo se mueve, como sabemos qué nave mover, calculamos la diferencia entre el toque actual y el anterior y se lo sumamos a la posición de nuestra nave. En el momento que levantamos el dedo de la pantalla, si teníamos iniciado un movimiento de nave, lo desactivamos (poniendo el semáforo en false o NO) y listo. También hacemos lo mismo en el disparador de cancelación de movimiento, que salta cuando el dedo sale de rango, por ejemplo, cuando nos salimos de la pantalla donde el toque (en teoría) realmente no ha terminado si no que se ha cancelado porque hemos salido de su radio de acción (la pantalla).

  • Swift
  • Objective-C

Ahora cuando ejecutemos, veremos las dos naves nuevamente. Pero si las arrastramos, podremos moverlas con el dedo donde queramos. Tal como está programado, solo podremos mover una nave a la vez, pues solo recoge el primer toque y solo marca una nave, pero es el procedimiento más normal a la hora de jugar a algún juego.

Si por cualquier razón, necesitamos procesar cuál es la posición del elemento en su destino (por si tenemos sprites que queremos mover de A a B) en el método touchesEnded es donde podemos meter esta dinámica.

A probar

Y ahora solo queda que juguéis con las diferentes propiedades, o pongáis alguna nave más y que modifiquéis el código para ir aprendido diferentes casuísticas.

Si queréis probar la lección completa, podéis bajaros el pequeño proyecto desde GitHub, directamente del repositorio público de AppleCoding, y probarlo y juguetear con él. Y ahora, con toda la razón, que tengáis Good Apple Coding.

Proyecto Lección 1 de SpriteKit en Swift | Repositorio en GitHub | Descarga código Swift
Proyecto Lección 1 de SpriteKit en Objective-C | Repositorio en GitHub | Descargar código Obj-C

Acerca de Julio César Fernández

Analista, consultor y periodista tecnológico, desarrollador, empresario, productor audiovisual, actor de doblaje e ingeniero de vídeo y audio.

Otras recomendaciones

Lecciones Prototipos (I): UITableView

Lecciones por prototipos (I): Vistas de tabla (UITableView)

Primera lección por prototipos, un nuevo e innovador contenido. A veces, lo normal es que nos perdamos sin terminar de entender qué es o cómo funcionan los componentes que forman parte de una app o un juego. Para este caso hemos creado las lecciones por prototipos. Una exploración básica de conceptos esenciales a través de prototipos en Playground que podemos probar con Swift Playgrounds en el iPad o con Xcode 8. En esta primera lección abordamos las UITableView (vistas de tabla). Un elemento esencial en la mayoría de apps de iOS que muchas veces no es entendido desde su base y por lo tanto, provoca un mal uso de las mismas.