viernes, 23 de mayo de 2014

Cocos2dx (SceneGraph: Sprites I)

Categoría: Cocos2dx

Nivel: Básico


Los sprites son elementos del scene graph y por tanto heredan de un nodo, una de las principales características de los sprites es que se les puede asociar una textura para renderizarse completa o parcialmente mediante la especificación de un área de recorte. En esta serie de artículos voy explicar el uso de los sprites en cocos2dx y como optimizar su uso ya que es uno de los pilares principales en la creación de un videojuego


Conceptos básicos:

Un sprite es uno de los elementos más importantes del SceneGraph ya que su principal característica es el renderizado de una textura. En un juego pueden existir desde unas decenas hasta cientos de sprites y por ello es uno de los apartados que más atención hay que prestar para poder ofrecer una buena experiencia de usuario (UX User eXperience), el principal problema en los juegos es el conocido "cuello de botella" en el sistema de render lo que hace bajar el framerate del juego y producir un retraso en la interacción del usuario, comúnmente conocido como "lag". No todos los dispositivos disponen de una GPU de última generación y como la finalidad es abarcar el mayor número de dispositivos siempre se debe poner mayor énfasis en la optimización, consiguiendo también con ello alargar la vida de la batería del dispositivo.

Los siguientes son algunos objetos y técnicas relacionadas con los sprites que deben conocerse antes de comenzar a trabajar con ellos.


Texture2D:

Como se ha comentado, los sprites renderizan texturas (objetos de la clase Texture2D), las texturas básicamente son áreas bidimensionales que contienen datos de una imagen y se renderizan según las propiedades establecidas en la textura.

Actualmente se pueden crear texturas desde imágenes en los formatos (Jpg, Png, Tiff, Tga, Webp, Pvr, Etc, S3tc y Atitc) aunque generalmente se usa el formato Png por ser de los más extendidos con soporte de transparencias.

Entre las principales propiedades de las texturas se encuentra el formato de pixel (PixelFormat) que por defecto es RGBA8888 (Rojo-Verde-Azul-Alpha de 8 bits cada uno = 32 bits), para optimizar se puede usar cualquier otro de los disponibles intentando buscar una buena relación con la calidad.

Las texturas también pueden hacer uso de técnicas como mipmap y antialiasing para renderizar las imágenes además de la posibilidad de establecer un programa de sombreado (ShaderProgram).


Texture Atlas y Sprite Sheet:

Texture Atlas y Sprite Sheet son dos conceptos diferentes para una misma cosa, ambos consisten en una imagen grande que agrupa subimagenes, pero el concepto de Sprite Sheet es el de usar solo subimagenes para animaciones de sprites en frames, mientras que un Texture Atlas es más abstracto y puede contener subimagenes para cualquier composición, tiles para la creación de mapas, frames de sprites, partes de un objeto para animar mediante huesos o esqueleto y en general cualquier otro grafico que pueda usarse en un juego.

La finalidad de crear la imagen compuesta de subimagenes es la de optimizar el proceso de renderizado, ya que al usar una única textura, solo hace falta una llamada a la función de dibujo de la librería de render para dibujar todos los objetos que hagan uso de la textura.

En cocos2dx existe la clase TextureAtlas, la cual contiene una textura (Texture2D) que puede crearse desde la imagen conteniendo subimagenes.

TextureAtlas es usada por algunas clases para optimizar el proceso de renderizado, en el caso de los sprites, se puede hacer uso de la clase SpriteBatchNode para tal fin.


TextureCache:

Para optimizar la creación y mantenimiento de texturas, cocos2dx hace uso de la clase TextureCache, la cual es básicamente una cache para almacenar las texturas que van siendo usadas por la aplicación. Mediante esta clase, las texturas serán creadas solo en caso de no existir en la cache, en caso de que ya exista la textura, será retornada la referencia y no se volverá a crear, optimizando de este modo los recursos disponibles.

En versiones anteriores a cocos2dx 3.0 la clase TextureCache era un singleton y se podía acceder mediante su método 'TextureCache::getInstance()', a partir de la versión 3.0 ya no es tratada como un singleton, en su lugar será gestionada por la clase 'Director' de modo que para acceder a la instancia de la clase TextureCache deberá usarse el método:

Director::getInstance()->getTextureCache()

La clase TextureCache crea y añade texturas a la cache mediante imágenes de forma síncrona o asíncronamente además de implementar métodos para su gestión.


Sprite:

Los Sprites son las clases más usadas para renderizar imágenes, para ello se crea una textura desde la imagen y se establece en el sprite, la textura puede ser renderizada completa o parcialmente mediante un área de recorte.

Los sprites heredan de un nodo, por lo tanto disponen de todas las propiedades de estos, como pueden ser rotación, escalado, translación, etc.


SpriteBatchNode:

La clase SpriteBatchNode hereda de un nodo y por tanto contiene todas sus propiedades del mismo modo que un Sprite.

Esta clase se usa como un contenedor de sprites para optimizar su renderizado, la finalidad es la de renderizar todos los Sprites que contiene en una única llamada a la función de dibujo de la librería de render (técnica conocida como "Batch Draw"), para ello hace uso de un objeto TextureAtlas que contiene la textura a usar por los sprites.

La clase tiene algunas limitaciones para permitir realizar su tarea correctamente y en caso de no cumplirse será lanzada una aserción:

  • Todos los hijos de un SpriteBatchNode deben ser del tipo Sprite o heredados de él.
  • Todos los Sprites deben usar el mismo Id de textura.

El uso de la clase SpriteBatchNode es el más óptimo en el uso de sprites, no obstante la primera de estas limitaciones envenena el SceneGraph y hay veces que merece la pena sacrificar un poco de rendimiento para ganar otras ventajas, algo que ya se puede hacer a partir de la versión cocos2dx 3.0 con el nuevo sistema de render y la técnica automatic batching.


SpriteFrame:

Como su propio nombre indica, la clase SpriteFrame representa un marco para un sprite, esta clase básicamente contiene una textura (Texture2D) y un rectángulo que representa el marco a usar dentro de la textura.

La clase SpriteFrame está diseñada principalmente para crear sprites animados, para ello se crea un array de SpriteFrame que representan la animación y en un intervalo de tiempo deseado se van estableciendo en el Sprite. Para animaciones complejas existen clases para facilitar todo este trabajo.


SpriteFrameCache:

Al igual que TextureCache, la clase SpriteFrameCache es una clase que implementa un sistema de cache, pero en este caso es para objetos del tipo SpriteFrame.

SpriteFrameCache está implementada con el patrón singleton, esto significa (en este caso) que solo existirá una única instancia en toda la aplicación y se podrá acceder a ella mediante:

SpriteFrameCache::getInstance();

Existen diversas formas de añadir SpriteFrames a la cache, entre ellas destaca la posibilidad de establecer una textura y un archivo plist (lista de propiedades) que define las propiedades de los SpriteFrames en la textura, mediante este método se pueden usar editores de sprites como por ejemploTexturePacker o SpritePacker y crear sprites de forma muy sencilla.


Automatic batching:

La principal optimización en los sprites se consigue renderizando el mayor número de ellos en una única llamada a la función de dibujo de la librería de render. En versiones anteriores de cocos2dx existía una clase llamada 'SpriteBatchNode' que se encargaba de renderizar todos los sprites asociados a ella en una única llamada al render, aunque en la versión 3.0 sigue estando esta clase, una de las características del nuevo sistema de render es la automatización de esta funcionalidad sin requerir el uso de la clase, lo que se conoce con el concepto 'automatic batching'. Para que todos los sprites sean renderizados en una única llamada mediante el automatic batching, deben tener el mismo id de material, un material básicamente es una combinación de los siguientes objetos:

  • Texture
  • Shader Program
  • Blend function

Si estas tres características se cumplen, los sprites serán automáticamente agrupados y renderizados en una única llamada al sistema de render. Para compartir la misma textura se puede hacer uso de una imagen grande con subimagenes (Sprite Sheet o Texture Atlas), el shader program y blend function si no se modifican se usaran los predefinidos.

El uso de un SpriteBatchNode puede ganar entre un 5% ~ 10% en el rendimiento de renderizado contra el automatic batching, pero no permite la misma flexibilidad, por ejemplo un SpriteBatchNode solo permite tener Sprites como hijos. Si se quiere optimizar ese porcentaje de rendimiento en algún caso, tan solo hay que poner los sprites como hijos de un SpriteBatchNode y usar al igual que en el auto batching la misma textura.


Auto culling:

Otro factor a tener en cuenta es el recorte automático, una técnica conocida como 'auto culling', si el AABB (Axis Aligned Bounding Box) del sprite esta fuera de la pantalla no se enviara comandos de dibujo a la cola de render. El auto culling es una optimización que se implementa automáticamente.


Hasta aquí los conceptos básicos que hay que conocer bien antes de trabajar con sprites, en la siguiente parte del artículo dedicado a sprites voy a implementar todos estos conceptos mediante ejemplos para comprenderlos con más detalle.


Saludos y… "a fluzear"

2 comentarios:

  1. Muchísimas gracias por retornar ésto tan pronto. Deseoso de seguir aprendiendo, un saludo.

    ResponderEliminar
  2. Gracias por la información que das sobre cocos2d-x 3.0

    ResponderEliminar