Como soportar todas las resoluciones en un desarrollo 2D

13 jun 2015 by shadow_of__soul, No Comments »

Android_screensize

Hace tiempo tenia ganas de escribir este articulo, pero no lo hacia por que queria probar al 100% la tecnica que utilizo para adaptar contenido a cualquier pantalla en un entorno 2D (en mi caso con OpenFL / Haxe pero puede ser usado en cualquier otro entorno que sea puramente 2D como flash, cocos2D etc..).

Un poco de teoria

En VG, hay 2 formas principales de adaptar el contenido a la pantalla del usuario. La mas facil y comun, es simplemente estirar el contenido al tamaño de la pantalla, respetando la relacion original de la altura y ancho de los elementos (por lo que se agregarian lineas negras arriba o a los costados dependiendo que proporcion se estire mas). O sin respetar la relacion original, dandole un look “estirado”. A esta tecnica de estirado, no solo esta el problema de las barras negras o el estirado, sino que si no se usan imagenes originales que esten minimamente cerca a la resolucion usada (osea, un sprite de 32×32 estirado a 250×250 ) tambien va a pixelar la imagen.

La otra forma de adaptarse a la pantalla, es usando un fondo dinamico, que puede ser o generado en ejecucion (por repeticion de una imagen para producir un fondo, o dibujando formas a mano) o con una imagen del tamaño mas grande a soportar, centrada, que en pantallas mas chicas, todo el exceso es invisible al usuario y solo se ve desde el centro a los limites de la pantalla. Todo el contenido interactivo y no estatico, es agrandado o achicado a partir de un ratio obtenido de dividir las dimensiones de la pantalla / con las dimensiones originales, usando imagenes de alta definicion para resoluciones grandes para evitar el pixelado. Esta ultima tecnica es la que yo utilizo, y es la que voy a pasar a explicar (junto con algunos tips propios que la complementan)

En la practica, el codigo

Mis ejemplos de codigo estan basados en Haxe / OpenFL pero son facilmente porteables a cualquier otra plataforma. Van a trabajar con 4 valores principales:

1) Ancho de la pantalla del usuario

2) Largo de la pantalla del usuario

3) Ancho original del juego

4) Largo original del juego

El ancho y el largo de la pantalla lo obtienen con el framework que usen, en el caso de openFL lo provee  Capabilities.screenResolutionX y Capabilities.screenResolutionY.

La resolucion original del juego, es eso, en que ancho y largo originalmente planearon y codearon el juego para que funcionara en una relacion 1:1 con los assets.

Las imagenes tiene un margen en la cual pueden ser agrandadas o achicacadas sin que se note visiblemente un pixelado. Este margen depende del renderizado de la herramienta que usen, pero en la mayoria de los casos, no debe superar un 100% para ambas operaciones (osea, no puede ser el doble de grande ni el doble de pequeño que el original). Basados en esta regla, y teniendo en cuenta que las resoluciones actuales de mobile (y pasando por PC tambien) van desde los 320px para las ldpi (low dpi) a los 2250px para las pantallas retina (y proximamente +4000px para el 4K) si tenemos un juego que usa una resolucion original estandar, que tenga un minimo de 500px en alguna direccion con un maximo de 1000px, podemos con 1 set de imagenes original, soportar pantallas low res (resizeando para abajo un 50%) hasta 1500px (resizeando un 100% aprox). Les doy valores como estos para que se den una idea, pero en realidad ni siquiera se resizea a un 100% a 1500px en algunos aspect ratio especificos. Por esto, para nuestro ejemplo, cualquier pantalla con 1500px de resolucion en ancho o alto, pasaria a ser alta definicion y requiere de otro set de imagenes, del doble de resolucion que la original. Igualmente, si con el set original observamos pixelacion con pantallas de 1500px, podemos simplemente bajar el limite y usar un set de alta definicion, es cuestion de probar dependiendo el tipo de arte y la relacion alto/ancho usada. Bajo esta teoria, escribamos un poco de codigo:

1
2
3
4
5
6
7
8
9
10
11
12
13
screenWidth = Capabilities.screenResolutionX;
screenHeight = Capabilities.screenResolutionY;

if (screenWidth > 1500 || screenHeight > 1500) {
screenResolutoExpon = 2;
AssetLib.folderAsset = "high";
}
var ratioX:Float = screenWidth / (Constants.originalWidth * screenResolutoExpon);
var ratioY:Float = screenHeight / (Constants.originalHeight * screenResolutoExpon);
scaleGeneral = Math.min(ratioX, ratioY);
if (scaleGeneral > 1) {
scaleGeneral = 1;
}

Como explique anteriormente, tenemos que tener un limite de lo que separa, alta defnicion, a definicion estandar / baja. en este caso, el limite es de 1500px, y como ven en el codigo, hace 2 cosas, setea una variable de multiplicacion exponencial, a 2 (por que las imagenes tienen el dobre de resolucion que la original) y cambia la carpeta a usar por la que tiene las imagenes en alta (esto es subjetivo a cada codigo, como organicen los archivos).

Una vez que tiene esto, calcula la relacion entre la resolucion original y la pantalla, para saber si hay que achicar o agrandar las imagenes al cargarlas (teniendo en cuenta el exponencial, ya que si se usa alta defnicion, al resolucion original del juego debe ser multiplicada por este factor). Con las 2 divisiones, se toma el menor valor de estos, y se obtiene el factor a escalar los componentes. El ultimo if() es opcional, por si no quieren escalar hacia arriba las imagenes (o quieren poner un limite a cuanto deberia escalar los mismos).

Con este valor, a cada imagen, se le hace lo siquiente:

1
2
image.scaleX = scaleGeneral;
image.scaleY = scaleGeneral;

En el caso de bitmaps, en haxe se debe usar una matrix, que es exactamente lo mismo. Y listo !

Posiciones relativas y otras yerbas

Las posiciones en un game son muy importantes. y si agrandamos o achicamos contenido, las coordenadas cambian muchisimo. Para poder mantener un alineamiento correcto de los elementos, hay que definir un punto de referencia desde donde parte la alineacion el elemento. Por ejemplo, si algo tiene que estar en el centro, usamos el alto y el largo del contenedor del elemento para calcular su nueva posicion, despues de haberlo escalado. Si tiene que estar a una distancia fija de un margen, usamos ese valor fijo, lo multiplicamos por el exponente ( screenResolutoExpon) y de ahi lo multiplicamos por el factor (scaleGeneral) para obtener:

1
( 300 * screenResolutoExpon) * scaleGeneral =  valor nuevo adaptado

Esta misma formula puede ser aplicada para calcular tamanios de fuente por ejemplo.

Si tenemos muchos componentes con posiciones fijas y podemos ponerlo dentro de un contenedor que pueda ser agrandado o achicado dinamicamente y haga lo mismo con los hijos dentro de el (como hace Sprite en haxe / OpenFL y flash) esto es mas que ideal, por que podemos poner posiciones fijas, y solamente multiplicarlo por el exponente ( screenResolutoExpon) por si estamos usando imagenes de alta defincion, y al contenedor aplicarle el scaleGeneral para adaptarlo a la pantalla.

Conclusiones

Este puede no ser el mejor sistema para adaptar contenido a pantalla, y no va a servir para todos los juegos, pero creo que es muy sencillo y me brindo mucha facilidad al trabajar y adaptar el contenido usando la menor cantidad de imagenes posibles para ahorrar espacio final (muy importante en mobile) y pude soportar todas las pantallas desde low a retina con solo 2 sets de imagenes y con exactamente el mismo codigo y mismas dimensiones y calculos para todas las pantallas.

Etiquetas: , , ,

Sigueme !

Follow Me! Follow Me! Follow Me!