Vistas de página en total

Mostrando entradas con la etiqueta proporciones. Mostrar todas las entradas
Mostrando entradas con la etiqueta proporciones. Mostrar todas las entradas

miércoles, 14 de septiembre de 2016

Múltiples tamaños de pantalla en Corona SDK

Pixels reales vs pixels virtuales

En la entrada anterior vimos claramente el problema: Si no le decimos nada a Corona, las coordenadas y tamaños que especifiquemos para los objetos de pantalla se traducen directamente a pixels en la pantalla del móvil. O sea, las coordenadas x=300,y=400 se traducen a los pixels 300,400 de la pantalla. Igualmente con los tamaños: Un rectángulo con ancho=100 y alto=200 se traduce en la pantalla a un rectángulo de 100 pixels de ancho y 200 pixels de alto. Esto que parece lógico, hemos visto que causa problemas muy importantes cuando la aplicación tiene que ejecutarse en dispositivos con tamaños distintos de pantalla.

¿Cómo soluciona Corona SDK este problema? Pues mediante un mecanismo que se denomina "content scaling" (escalado de contenido). La idea, es no trabajar con los pixels reales directamente, sino sobre lo que podríamos denominar pixels virtuales. Es decir, definimos un tamaño de pantalla, por ejemplo, 320x480, y trabajamos sobre esa pantalla siempre. Corona se encargará después de convertir las coordenadas y tamaño de los objetos en pixels reales del dispositivo.

Por ejemplo, si definimos nuestra pantalla virtual en 320x480 y queremos un rectángulo centrado verticalmente y que ocupe todo el ancho de la pantalla, lo situaremos en x=160,y=240, y le definiremos un ancho de 320. Si el dispositivo tiene una resolución de 640x960, Corona se encargará de traducir las coordenadas y tamaño a esos pixels reales del dispositivo. En nuestro ejemplo, Corona situará el objeto en x=320,y=480, y el ancho será 640. En otro dispositivo con otra resolución, hará otra conversión a pixels reales, pero siempre podemos conseguir que el rectángulo salga centrado verticalmente y que ocupe todo el ancho.

Para que Corona pueda trabajar con una pantalla virtual, tenemos que crear un fichero nuevo, en el mismo directorio que el main.lua, denominado config.lua. El contenido de este fichero será:

application = {
    content = {
        width = 320,
        height = 480,
    },
}

Este fichero tiene una tabla, llamada application, y dentro de esta tabla hay otra tabla, llamada content que define el ancho y el algo de la pantalla virtual. En nuestro caso 320x480. Como veremos más adelante en la tabla application, podremos definir otros valores de configuración de nuestra aplicación, de momento nos quedamos con el tamaño de la pantalla virtual.

Probemos otra vez con el programa anterior, main.lua:

local rect = display.newRect( 160, 240, 320, 200 )
rect:setStrokeColor( 1,0,0 )
rect.strokeWidth = 5

Si ejecutamos ahora, veremos que el resultado es siempre el mismo, independientemente del dispositivo que elijamos en el simulador.

La proporción o relación de aspecto

Aunque parezca que ya hemos resuelto el problema, realmente no es así.
Vamos a ver qué pasaría si en lugar de un rectángulo, tenemos un círculo:

local circle = display.newCircle( 160, 240, 100 )
circle:setStrokeColor( 1,0,0 )
circle.strokeWidth = 5

Probamos primero en iPhone 4:



Todo bien, sale un círculo perfectamente centrado en la pantalla. Estamos trabajando con una pantalla virtual de 320x480, colocamos el círculo en x=160,y=240 (centro de la pantalla virtual) y Corona traslada correctamente estas coordenadas al centro de la pantalla del iPhone 4.
Pero cambiemos ahora el dispositivo a Nexus One, por ejemplo. Nos encontramos con una sorpresa:


Si nos fijamos bien, esto ya no es un círculo, ¡es una elipse! ¿qué ha pasado? El problema es que nuestra pantalla virtual, 320x480, tiene una relación de aspecto (ancho/alto) de 0.66 (320/480). El iPhone 4 tiene una relación de aspecto idéntica: 640/960=0.66, con lo cual, Corona no distorsiona el círculo al escalarlo en el iPhone 4. Sin embargo, la pantalla del Nexus es 480x800, y por tanto, la relación de aspecto es: 480/800=0.6, ligeramente distinta a las anteriores. Podríamos probar con otros dispositivos y veremos que la relación de aspecto cambia en cada uno.
TRUCO: para mostrar el tamaño de la pantalla virtual y el tamaño de la pantalla real, podemos utilizar el siguiente código al principio de main.lua:

-- dimensiones de la pantalla virtual
print( display.contentWidth, display.contentHeight, display.contentWidth/display.contentHeight )

-- dimensiones de la pantalla real
print( display.pixelWidth, display.pixelHeight, display.pixelWidth/display.pixelHeight )

Si vamos cambiando el dispositivo, podemos ir comprobando las distintas relaciones de aspecto de los distintos dispositivos.

Este problema lo podemos arreglar simplemente añadiendo una línea a la tabla application de config.lua:

application = {
    content = {
        width = 320,
        height = 480,
        scale = "letterbox",
    },
}

Si ahora probamos con distintos dispositivos veremos que siempre tenemos un círculo perfecto (no una elipse) centrado perfectamente en la pantalla.
El parámetro scale dice a Corona cómo tiene que escalar los objetos cuando traduce los pixels virtuales a pixels reales del dispositivo. Después veremos los distintos modos, de momento nos quedamos con que el modo de escalado "letterbox" no deforma los objetos.

Parece que ya hemos llegado al final del problema, y que con esto ya podemos hacer aplicaciones que funcionen en todo tipo de dispositivos. Pero, por desgracia, no es así. Estamos cerca de la solución, pero todavía queda algo. Parece que este problema no acaba nunca, y es que realmente estamos ante un problema que trae de cabeza a muchos desarrolladores de aplicaciones para múltiples dispositivos.

Letterbox y las bandas

Vamos a dibujar un rectángulo de color azul en nuestra aplicación. Ese rectángulo debería hacer de fondo de la aplicación y cubrir toda la pantalla. Añadamos estas líneas en main.lua justo antes de crear el círculo:

local background = display.newRect( 160, 240, 320, 480 )
background:setFillColor( 0,0,1 )

Probamos con iPhone 4.



Todo perfecto, el rectángulo azul cubre todo el fondo.
Ahora como siempre, cambiemos a otro dispositivo con otra relación de aspecto, por ejemplo el Nexus:



Aunque podría pasar desapercibido, han aparecido dos bandas negras, una arriba y otra abajo. Realmente no ha aparecido ninguna banda. El problema es que la pantalla virtual, en modo de escalado "letterbox" (que no deforma), no puede cubrir completamente la pantalla del Nexus, porque la relación de aspecto es distinta. Es decir, en modo "letterbox" la pantalla virtual sólo puede cubrir completamente la pantalla real si la relación de aspecto es la misma (como ocurre en el iPhone 4).

Hagamos números: En el iPhone 4, para convertir de pixels virtuales a pixels reales, Corona multiplica por 2 tanto el ancho como el alto (como estamos en modo "letterbox" se multiplican ambas dimensiones por el mismo valor, para no deformar). Al multiplicar por 2, el rectángulo de 320x480 se convierte en 640x960 pixels reales, y cubre la pantalla completa del iPhone 4.
En el caso del Nexus, Corona busca un factor multiplicativo para convertir de 320x480 a 480x800. Como no puede deformar, el valor es el mismo en ambas dimensiones. En este caso, 1.5, que hace que el ancho se ajuste completamente, ya que 320 multiplicado por 1.5 es  480. Sin embargo, al multiplicar 480 por 1.5 tenemos 720, que es ligeramente inferior a los 800 pixels reales del alto de pantalla del Nexus. De ahí que Corona deja sin rellenar una parte por arriba y por abajo.

¿De dónde sale es valor de 1.5 para el Nexus? No es complicado. Es el valor que permite rellenar completamente una de las dos dimensiones, ancho o alto, sin pasarse en la otra dimensión. Así, para el caso del Nexus:

    valor para rellenar el ancho: 480 / 320 = 1.5
    valor para rellenar el alto: 800 / 480 = 1.66

Se coge el valor más pequeño, o sea, 1.5
En el caso del iPhone 4, ambos valores salen 2, y por tanto, la pantalla virtual se corresponde exactamente con la pantalla real.

Todo esto, como he dicho, es para el modo de escalado "letterbox". En la siguiente entrada hablaré de otros modos de escalado.

Podríamos conseguir que las bandas laterales salgan a la izquierda y a la derecha si definimos una pantalla virtual muy "estilizada", por ejemplo:

application = {
    content = {
        width = 160,
        height = 480,
        scale = "letterbox",
    },
}

Y el main:

local background = display.newRect( 80, 240, 160, 480 )
background:setFillColor( 0,0,1 )


Cómo quitar las bandas

Hemos visto que el modo de escalado "letterbox" no deforma al escalar desde la pantalla virtual a la pantalla real. Además, si sobra espacio al escalar, deja el mismo espacio por arriba y por abajo (o bien por la izquierda y derecha en caso de que la dimensión "perjudicada" sea el ancho).

Para resolver este y otros problemas relacionados con los tamaños, es conveniente conocer una serie de constantes que nos proporciona Corona SDK a través de su API:

  • display.contentWidth y display.contentHeight: ancho y alto de la pantalla virtual
  • display.pixelWidth y display.pixelHeight: ancho y alto de la pantalla real
  • display.actualContentWidth y display.actualContentHeight: ancho y alto real de la pantalla, pero en pixels virtuales. Esto merece una explicación. En el caso de bandas arriba y abajo, display.actualContentWidth va a ser igual a display.contentWidth, sin embargo, display.actualContentHeight va a ser superior a display.contentHeight
Con estas constantes, ya podemos definir fácilmente un rectángulo que cubra toda la pantalla en cualquier dispositivo:

local background = display.newRect( display.contentWidth/2, display.contentHeight/2, display.actualContentWidth, display.actualContentHeight )

background:setFillColor( 0,0,1 )

Existen otras constantes en el API de Corona que nos ayudarán en el futuro:

  • display.contentCenterX y display.contentCenterY: coordenadas del centro de la pantalla en pixels virtuales
En la siguiente entrada, seguiré hablando de los modos de escalado y de otras técnicas para hacer aplicaciones multidispositivo.






El problema de múltiples pantallas y proporciones

El problema

Este tema de hoy es quizás unos de los más peliagudos y difíciles de entender en la programación de aplicaciones para móviles. En la siguiente entrada veremos cómo resolverlo en Corona SDK.

El problema de la programación para móviles es la (inmensa) diversidad de tamaños de pantalla en los dispositivos. Tenemos móviles desde 4 pulgadas hasta 6 pulgadas, y además tenemos las tablets, que pueden ir desde 7 pulgadas hasta las casi 13 pulgadas del iPad Pro de Apple. Además, para completar el panorama, las proporciones de la pantalla también cambian. Tenemos pantallas 4x3, 5x4, y casi cualquier combinación de parejas números.
Obviamente, como buenos programadores (y como interesados en distribuir/vender muchas unidades de nuestra aplicación), queremos que nuestra aplicación se ejecute igual de bien en el número máximo de dispositivos posible. Este es uno de los problemas más importantes que se dan en la programación móvil. Y hay varias soluciones:
  • layouts: que mueven y redimensionan dinámicamente los elementos en función del tamaño de la pantalla en base a una serie de reglas del estilo: este elemento se ajusta a la izquierda, este elemento va a la izquierda de este otro, etc. Esta programación con layouts es una solución muy interesante y que funciona muy bien "la primera vez", quiero decir, para la primera versión de nuestra aplicación. Pero el gran problema es cuando el cliente o nuestro jefe nos dice: "Tenemos que meter una lista debajo de ese botón", y es entonces cuando aparecen los sudores y temblores en el programador, pensando que va a tener que volver a redefinir todas las reglas de layouts para que todo quede bien por culpa de un simple elemento. Y lo peor llega cuando rematan la frase con: "Esto es fácil, verdad? Es sólo añadir un elemento más". Se puede desprender de estas líneas que aunque es bonita la idea de los layouts, no creo que sean la mejor idea (es mi opinión absolutamente personal). En cualquier caso, Apple utiliza layouts en los desarrollos para iOS y parece que a los programadores les gusta.
  • hacer proporcionales los elementos con respecto al tamaño de la pantalla: esto es más o menos lo que hace Corona, y que veremos en esta entrada.
  • llenar el código de if's y colocar los elementos en función del tamaño y proporciones de la pantalla, asegurándose que todo cuadra en todas las posibilidades. El lector comprenderá que esta opción, un tanto primitiva, no es muy adecuada en cuanto tengamos 10 o más elementos en una misma pantalla. Pero como siempre, para gustos los colores, hay gente muy muy paciente que es capaz de rellenar el código de if's preguntando por miles de posibles valores.

Hay otras soluciones, pero realmente la que me interesa explicar con detalle es la que se utiliza en las aplicaciones con Corona.

Ejemplo

Vamos a hacer un sencillo ejemplo donde podemos ver el problema, así de paso, ya vamos viendo el mundo de las apps.
Creamos un directorio en nuestro disco duro y también creamos un fichero nuevo: main.lua, que será el punto de arranque de nuestra aplicación. El contenido del main.lua será:

    local rect = display.newRect( 320, 480, 640, 320 )

Abrimos el simulador de Corona, seleccionamos la carpeta de nuestro proyecto y lo ejecutamos. Pero atención, vamos a ir al menú View > View As > iPhone 4. En la pantalla tendremos algo como lo siguiente:


Reconozco que el programa no es gran cosa, pero no se puede pedir mucho más por una única línea de código.
Aunque creo que se entiende bien, diré que estamos creando un rectángulo en la pantalla, en las coordenadas x=320, y=480, con ancho 640, y alto 320. Como podemos ver, el cuadrado sale centrado verticalmente en la pantalla del móvil y además ocupa todo el ancho de la pantalla. En Corona, como veremos más adelante, las coordenadas x,y de los objetos. sitúan el centro del objeto, por eso el cuadrado aparece centrado en la pantalla del iPhone 4 (que tiene un tamaño de pantalla de 640x960, y el centro es 320x480). En otros entornos, las coordenadas x,y sitúan la esquina superior izquierda del objeto, pero no así en Corona (ya veremos más adelante que este comportamiento se puede cambiar).
Hasta ahora todo muy bien. Pero vamos a ver qué ocurre si visualizamos nuestro programa en otro dispositivo, por ejemplo, en un iPhone 6. Vamos al menú View > View As > iPhone 6 y veremos algo como esto:


El resultado es parecido. Pero hay unas diferencias muy importantes: Sin haber cambiado coordenadas ni tamaño en nuestro programa, resulta que ahora el rectángulo ya no sale centrado verticalmente en la pantalla. ¿Por qué? Esto es fácil de responder: el tamaño del iPhone 6 es distinto, 750x1334, por tanto, la coordenada y=480 ya no es el centro vertical de la pantalla, y por tanto, el objeto no sale centrado verticalmente. Hay otro cambio, el rectángulo no ocupa todo el ancho de la pantalla. La justificación de esto también es obvia: El ancho de pantalla del iPhone 6 es 750, y el ancho de nuestro rectángulo es 640, por tanto no va a ocupar todo el ancho de la pantalla en el iPhone 6 (aunque sí en un iPhone 4).
Vamos a visualizar nuestro programa en un Nexus One (recordemos, View > View As > Nexus One). Aquí la situación todavía es peor, ya que la pantalla de este teléfono tiene 480x800. En este caso, no sólo no tenemos un rectángulo centrado verticalmente en la pantalla, sino que además, no cabe completamente en la pantalla. Esto es todavía peor, porque podemos perder contenido. Creo que no es difícil entender el resultado.

En el caso del Nexus One, estamos perdiendo 160 pixels del rectángulo (640 que es el ancho del rectángulo, menos 480 que es el ancho de pantalla del Nexus).
Si queremos comprobarlo, pongamos un borde alrededor del rectángulo y comprobemos que el borde derecho no se visualiza en el Nexus:

    rect:setStrokeColor( 1,0,0 )
    rect.strokeWidth = 5

En resumen, tenemos un problema. Hay que hacer algo para que cuando coloquemos objetos en la pantalla, no tengamos que preocuparnos del tamaño real del dispositivo en el que se ejecutará nuestra aplicación.