Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. iOS

SpriteKit Desde Cero: técnicas avanzadas y optimizaciones

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called SpriteKit From Scratch.
SpriteKit From Scratch: Visual and Audio Effects

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Introducción

En este tutorial, la quinta y última entrega de la serie SpriteKit From Scratch, observamos algunas técnicas avanzadas que puede utilizar para optimizar sus juegos basados ​​en SpriteKit para mejorar el rendimiento y la experiencia del usuario.

Este tutorial requiere que esté ejecutando Xcode 7.3 o superior, que incluye Swift 2.2 y iOS 9.3, TVOS 9.2 y SDK OS X 10.11.4. Para seguir, puede usar el proyecto que creó en el tutorial anterior o descargar una copia nueva de GitHub.

Los gráficos utilizados para el juego en esta serie se pueden encontrar en GraphicRiver. GraphicRiver es una excelente fuente para encontrar ilustraciones y gráficos para tus juegos.

1. Atlas de texturas

Con el fin de optimizar el uso de memoria de su juego, SpriteKit proporciona la funcionalidad de atlas de textura en forma de la clase SKTextureAtlas. Estos atlas combinan eficazmente las texturas que especifique en una única textura grande que ocupa menos memoria que las texturas individuales por sí mismas.

Afortunadamente, Xcode puede crear atlas de textura muy fácilmente para usted. Esto se hace en los mismos catálogos de activos que se usan para otras imágenes y recursos en sus juegos. Abra su proyecto y navegue hasta el catálogo de activos Assets.xcassets. En la parte inferior de la barra lateral izquierda, haz clic en el botón + y selecciona la opción New Sprite Atlas.

Create a New Sprite Atlas

Como resultado, se agrega una nueva carpeta al catálogo de activos. Haga clic en la carpeta una vez para seleccionarla y vuelva a hacer clic para cambiarle el nombre. Nómbralo Obstacles. A continuación, arrastre los recursos de Obstacle 1 y Obstacle 2 a esta carpeta. También puede eliminar el activo Sprite en blanco que Xcode genera, si así lo desea, pero eso no es obligatorio. Cuando se haya completado, su atlas de textura Obstacles ampliado debería verse así:

Obstacles Texture Atlas

Ahora es el momento de usar el atlas de textura en el código. Abra MainScene.swift y agregue la siguiente propiedad a la clase MainScene Inicializamos un atlas de textura usando el nombre que ingresamos en nuestro catálogo de activos.

Si bien no es obligatorio, puede precargar los datos de un atlas de textura en la memoria antes de usarlo. Esto le permite a su juego eliminar cualquier retraso que pueda ocurrir al cargar el atlas de textura y recuperar la primera textura de él. La carga previa de un atlas de textura se realiza con un único método y también puede ejecutar un bloque de código personalizado una vez que se haya completado la carga.

En la clase MainScene, agregue el siguiente código al final del método didMoveToView(_:):

Para recuperar una textura de un atlas de textura, utilice el método textureNamed(_:) con el nombre que especificó en el catálogo de activos como parámetro. Actualicemos el método spawnObstacle(_:) en la clase MainScene para usar el atlas de textura que creamos hace un momento. Buscamos la textura del atlas de textura y la usamos para crear un nodo de sprite.

Tenga en cuenta que, si su juego aprovecha los Recursos a pedido (ODR), puede especificar fácilmente una o más etiquetas para cada atlas de textura. Una vez que haya accedido correctamente a la (s) etiqueta (s) de recursos correctas con las API de ODR, puede usar su atlas de texturas tal como lo hicimos en el método spawnObstacle(_:). Puede leer más sobre los recursos a pedido en otro tutorial mío.

2. Guardar y cargar escenas.

SpriteKit también le ofrece la posibilidad de guardar y cargar escenas fácilmente desde y hacia el almacenamiento persistente. Esto permite a los jugadores abandonar su juego, relanzarlo más adelante y seguir en el mismo punto de su juego como antes.

El guardar y cargar tu juego se maneja con el protocolo NSCoding, que la clase SKScene ya cumple. La implementación de SpriteKit de los métodos requeridos por este protocolo automáticamente permite que todos los detalles en su escena sean guardados y cargados muy fácilmente. Si lo desea, también puede anular estos métodos para guardar algunos datos personalizados junto con su escena.

Debido a que nuestro juego es muy básico, vamos a usar un valor de Bool simple para indicar si el auto se ha estrellado. Esto le muestra cómo guardar y cargar datos personalizados que están vinculados a una escena. Agregue los siguientes dos métodos del protocolo NSCoding a la clase MainScene

Si no está familiarizado con el protocolo NSCoding, el método encodeWithCoder(_:) maneja el guardado de su escena y el inicializador con un único parámetro NSCoder maneja la carga.

A continuación, agregue el siguiente método a la clase MainScene. El método saveScene() crea una representación NSData de la escena, utilizando la clase NSKeyedArchiver. Para mantener las cosas simples, almacenamos los datos en NSUserDefaults.

A continuación, reemplace la implementación de didBeginContactMethod(_:) en la clase MainScene con lo siguiente:

El primer cambio realizado en este método es la edición de la categoryBitMask del nodo de jugador en lugar de eliminarla por completo de la escena. Esto asegura que al volver a cargar la escena, el nodo de jugador todavía está allí, a pesar de que no es visible, pero que las colisiones duplicadas no se detectan. El otro cambio realizado es llamar al método saveScene() que definimos anteriormente una vez que se ha ejecutado la lógica de explosión personalizada.

Finalmente, abra ViewController.swift y reemplace el método viewDidLoad() con la siguiente implementación:

Al cargar la escena, primero verificamos si hay datos guardados en NSUserDefaults estándar. Si es así, recuperamos esta información y recreamos el objeto MainScene usando la clase NSKeyedUnarchiver. De lo contrario, obtenemos la URL del archivo de escena que creamos en Xcode y cargamos los datos de la misma manera.

Ejecuta tu aplicación y corre hacia un obstáculo con tu auto. En esta etapa, no ves la diferencia. Sin embargo, vuelva a ejecutar su aplicación, y debería ver que su escena ha sido restaurada exactamente como era cuando acaba de estrellar el automóvil.

3. El bucle de animación

Antes de renderizar cada cuadro de tu juego, SpriteKit ejecuta una serie de procesos en un orden particular. Este grupo de procesos se conoce como el bucle de animación. Estos procesos representan las acciones, las propiedades físicas y las restricciones que ha agregado a su escena.

Si, por algún motivo, necesita ejecutar un código personalizado entre cualquiera de estos procesos, puede anular algunos métodos específicos en su subclase SKScene o especificar un delegado que cumpla con el protocolo SKSceneDelegate. Tenga en cuenta que, si asigna un delegado a su escena, las implementaciones de la clase de los siguientes métodos no se invocan.

Los procesos de ciclo de animación son los siguientes:

Paso 1

La escena llama a su método update(_:). Este método tiene un único parámetro NSTimeInterval, que le proporciona la hora actual del sistema. Este intervalo de tiempo puede ser útil ya que le permite calcular el tiempo que tardó en renderizar su fotograma anterior.

Si el valor es mayor a 1/60 de segundo, tu juego no se ejecuta en los suaves 60 fotogramas por segundo (FPS) a los que aspira SpriteKit. Esto significa que puede necesitar cambiar algunos aspectos de su escena (por ejemplo, partículas, número de nodos) para reducir su complejidad.

Paso 2

La escena se ejecuta y calcula las acciones que ha agregado a sus nodos y los coloca en consecuencia.

Paso 3

La escena llama a su método didEvaluateActions(). Aquí es donde puede realizar cualquier lógica personalizada antes de que SpriteKit continúe con el ciclo de animación.

Paso 4

La escena realiza sus simulaciones físicas y cambia su escena en consecuencia.

Paso 5

La escena llama a su método didSimulatePhysics(), que puede anular con el método didEvaluateActions().

Paso 6

La escena aplica las restricciones que ha agregado a sus nodos.

Paso 7

La escena llama a su método didApplyConstraints(), que está disponible para que anule.

Paso 8

La escena llama a su método didFinishUpdate(), que también puede anular. Este es el método final en el que puede cambiar su escena antes de que finalice su aparición para ese cuadro.

Paso 9

Finalmente, la escena muestra sus contenidos y actualizaciones conteniendo SKView en consecuencia.

Es importante tener en cuenta que, si utiliza un objeto SKSceneDelegate en lugar de una subclase personalizada, cada método gana un parámetro adicional y cambia su nombre ligeramente. El parámetro adicional es un objeto SKScene, que le permite determinar en qué escena se está ejecutando el método. Los métodos definidos por el protocolo SKSceneDelegate se nombran de la siguiente manera:

  • update(_:forScene:)
  • didEvaluateActionsForScene(_:)
  • didSimulatePhysicsForScene(_:)
  • didApplyConstraintsForScene(_:)
  • didFinishUpdateForScene(_:)

Incluso si no usa estos métodos para realizar cambios en su escena, aún pueden ser muy útiles para la depuración. Si tu juego se retrasa constantemente y la velocidad de fotogramas cae en un momento particular de tu juego, puedes anular cualquier combinación de los métodos anteriores y encontrar el intervalo de tiempo entre cada llamada. Esto le permite encontrar con precisión si específicamente sus acciones, física, restricciones o gráficos son demasiado complejos para que su juego se ejecute a 60 FPS.

4. Mejores prácticas de rendimiento

Lote de dibujo

Al renderizar su escena, SpriteKit, de forma predeterminada, se ejecuta a través de los nodos en la matriz de elementos secundarios children de la escena y los dibuja en la pantalla en el mismo orden en que están en la matriz.  Este proceso también se repite y se enlaza para cualquier nodo hijo que un nodo particular pueda tener.

La enumeración individual de nodos secundarios significa que SpriteKit ejecuta una llamada de extracción para cada nodo. Mientras que para escenas simples este método de renderizado no afecta significativamente el rendimiento, a medida que su escena gana más nodos este proceso se vuelve muy ineficiente.

Para que la renderización sea más eficiente, puede organizar los nodos en su escena en distintas capas. Esto se hace a través de la propiedad zPosition de la clase SKNode. Cuanto mayor sea la zPosition de un nodo, más "cerca" estará de la pantalla, lo que significa que se representa encima de otros nodos en su escena. Del mismo modo, el nodo con la zPosition más baja en una escena aparece en el mismo "reverso" y puede ser superpuesto por cualquier otro nodo.

Después de organizar los nodos en capas, puede establecer la propiedad ignoreSiblingOrder de un objeto SKView en true. Esto da como resultado que SpriteKit use los valores de zPosition para representar una escena en lugar del orden de la matriz children. Este proceso es mucho más eficiente ya que todos los nodos con la misma zPosition se agrupan en una única llamada de extracción en lugar de tener uno para cada nodo.

Es importante tener en cuenta que el valor zPosition de un nodo puede ser negativo si es necesario. Los nodos en su escena todavía están reproduciéndose en orden de aumentar zPosition.

Evitar animaciones personalizadas

Las clases SKAction y SKConstraint contienen una gran cantidad de reglas que puede agregar a una escena para crear animaciones. Siendo parte del framework SpriteKit, están optimizados tanto como pueden y también encajan perfectamente con el circuito de animación de SpriteKit.

La amplia gama de acciones y restricciones que se le brindan permiten casi cualquier animación posible que pueda desear. Por estas razones, se recomienda que siempre utilice acciones y restricciones en sus escenas para crear animaciones en lugar de realizar cualquier lógica personalizada en otro lugar de su código.

En algunos casos, especialmente si necesita animar un grupo razonablemente grande de nodos, los campos de fuerza física también pueden incluso producir el resultado que desea. Los campos de fuerza son aún más eficientes ya que se calculan junto con el resto de las simulaciones físicas de SpriteKit.

Máscaras de bits

Sus escenas se pueden optimizar aún más utilizando solo las máscaras de bits apropiadas para los nodos en su escena. Además de ser crucial para la detección de colisiones físicas, las máscaras de bits también determinan cómo las simulaciones de física e iluminación comunes afectan a los nodos en una escena.

Para cualquier par de nodos en una escena, independientemente de si alguna vez colisionarán o no, SpriteKit monitorea dónde están relativos entre sí. Esto significa que, si se deja con las máscaras predeterminadas con todos los bits habilitados, SpriteKit realiza un seguimiento de dónde está cada nodo en su escena en comparación con cada otro nodo. Puede simplificar en gran medida las simulaciones de física de SpriteKit definiendo las máscaras de bits apropiadas para que solo se rastreen las relaciones entre nodos que potencialmente pueden colisionar.

Del mismo modo, una luz en SpriteKit solo afecta a un nodo si el AND lógico de sus máscaras de bits de categoría es un valor distinto de cero. Al editar estas categorías, de modo que solo los nodos más importantes de su escena se vean afectados por una luz particular, puede reducir en gran medida la complejidad de una escena.

Conclusión

Ahora debería saber cómo puede optimizar aún más sus juegos SpriteKit utilizando técnicas más avanzadas, como atlas de textura, dibujo por lotes y máscaras de bits optimizadas. También debería sentirse cómodo al guardar y cargar escenas para brindarles a sus jugadores una mejor experiencia general.

A lo largo de esta serie, hemos analizado muchas de las funciones y funcionalidades del marco SpriteKit en iOS, TVOS y OS X. Hay temas incluso más avanzados que están fuera del alcance de esta serie, tales como los sombreadores de OpenGL ES y Metal personalizados como campos de física y articulaciones.

Si desea obtener más información sobre estos temas, le recomiendo comenzar con la Referencia SpriteKit Framework y leer en las clases relevantes.

Como siempre, asegúrese de dejar sus comentarios y comentarios en los comentarios a continuación.

Advertisement
Advertisement
Advertisement
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.