miércoles, octubre 17, 2012

Soportar múltiples resoluciones en un desarrollo moderno en 2D



Me ronda por la cabeza una pregunta sobre la programación de juegos 2D en entornos modernos con un problema que no se sufre en los juegos 3D (ni en el Spectrum, claro). Se me dió hace uno o dos años cuando me planteé la posibilidad de acabar mi PCMaziacs y hacerlo multiplataforma e incluso de hacer algún otro remake de juego de Spectrum. La he planteado en los foros de Speccy.org, por curiosidad (sección de desarrollo) en la siguiente URL:

[Enlace a consulta en foros de Speccy.org]

Hago la versión resumida de la pregunta, y luego la exposición. Ojo, es posible que la pregunta no tenga una respuesta directa o fácil. Yo tengo mi propia idea de cómo atacar el problema, pero lo que quiero saber no es cómo se puede hacer sino cómo se debe hacer o cómo se hace normalmente a día de hoy.


--- Pregunta versión resumida: ---

¿Cómo se tiene que diseñar o programar un juego 2D en cuanto a los gráficos y el GUI para que pueda funcionar en diferentes resoluciones y aspects ratios, es decir, poder llegar tanto a PC, como a iOS, Android, etc?

Me estoy refiriendo a soportar:

  • Diferentes resoluciones (sin perder calidad gráfica).
  • Diferentes aspects-ratios (4:3, 16:9, 16:10...) (e incluso orientación vertical/horizontal!)

Y me refiero a soportarlo en estos aspectos:

  • Gráficos de fondos y personajes (calidad de los mismos, ¿varios sets?, ¿gráficos vectoriales? ¿2.5D?, ¿renderizar escena+zoom?).
  • Elementos del GUI (fuentes, menúes, inventarios, puntuación) : posicionamiento en pantalla, etc.
  • Velocidades y posiciones de los personajes proporcionales a la resolución .


La duda puede ser tan simple como elegir en qué resolución hacer un remake, y que el usuario no lo vea en una ventanita de tamaño ridículo o en un fullscreen de píxeles del tamaño de un puño porque la resolución nativa del monitor es FullHD. De elegir entre varias resoluciones, o de cuál elegir.

--Fin pregunta resumida--------------------------------


Ahora viene el contexto histórico y el torrente de dudas e ideas.

Personalmente, siempre que he programado aplicaciones gráficas (demos, juegos, lo que sea) ha sido con una resolución fija.

En el Spectrum no tenías ninguna duda con la resolución: 256x192 y a trabajar. En el PC, en la época del 486 y Pentium MMX, lo habitual era tanto en MSDOS como en Windows elegir una resolución fija (320x200, 320x240, 640x480...) y lanzar el juego en fullscreen. Tampoco había muchas resoluciones a elegir: 320x200, 640x480, 800x600 y 1024x768 en 4:3 eran los "estándares".

La selección fija de resolución te permitía utilizar un único set de gráficos, y (erróneamente) mover todo el entorno de juego (personajes, etc) con "coordenadas de pantalla" en lugar de utilizar "coordenadas de mundo" y después transformarlas (ej: la bala se mueve 2 píxeles por segundo, la nave 3 píxeles por segundo, etc). Incluso se fijaba el número de cuadros por segundo y se asumía que no variaba.

Si querías soportar más de una resolución en un PC, como eran "conocidas" (desde 320x200 a 1024x768), podías optar por 2 sets gráficos a diferentes resoluciones y "hardcodear" (también error) en cierta manera todo (GUI, movimientos, etc) según si estabas en una resolución u otra.

Cuando entraron en juego hardwares más potentes, tenías la posibilidad de alcanzar más cuadros por segundo y los podías aprovechar bien especificando una tasa de FPS fija superior (normalmente la máxima del refresco 50-60Hz, y asumiendo que el juego no iría bien en sistemas más antiguos) o basando el movimiento de los personajes en el tiempo transcurrido desde el anterior fotograma, lo que adaptaba los frames a la velocidad de la máquina de forma "automática" (más fluído en hw potente, más "choppy" en hardware antiguo). Esto solucionaba el problema del "posicionamiento" y "movimiento", en cierta medida.

Los problemas aparecen por la parte de las resoluciones y los gráficos, cuando:


  • Entran en juego diferentes resoluciones.
  • El jugador puede tener monitor panorámico 16:9, 16:10, o un monitor 4:3.
  • Entran en juego tablets, móviles, etc, con resoluciones variadas o desconocidas a priori y con 2 posibles orientaciones.
  • Entra en juego permitir al usuario CAMBIAR de resolución (en ordenadores).
  • Los LCDs/TFTs tienen una resolución nativa y forzar una resolución en FULL SCREEN queda horroroso.



Concretamente:


Caso Monitor panoramico 16:9/16:10 vs 4:3:

Si eliges una única resolución "base" (por ej 800x600) y fuerzas ese modo de pantalla en fullscreen, puede darse el caso de que el usuario tenga un monitor panorámico (aparte de que se vea horrible en un LCD/TFT por la diferencia con la resolución nativa). En ese caso se me ocurre que tienes que detectar la resolución y elegir entre dos opciones:

a.- El juego transcurre en 4:3 a todos los efectos, tanto internamente como visualmente -> Si el monitor es 4:3 no se cambia nada al renderizar la escena y si el monitor es 16:9 se selecciona el modo equivalente en 16:9 y o bien se centra la imagen en pantalla (con bandas negras) o bien se pone algún tipo de marco decorativo, o bien se aprovecha el área para mejorar los marcadores / inventario / etc. Lo ideal supongo que sería asumir que el juego sea 16:9 y hacer la "adaptación" para 4:3, por eso de que casi todos los monitores hoy en día son panorámicos, pero ... el iPAD es 4:3 y no se puede despreciar su público en número de usuarios.

b.- El juego se adapta a la resolución adicional -> Se diseña para "jugar" 4:3 pero según el tipo de juego, puede ser factible o no mostrar más área de pantalla. Por ejemplo en un juego con vista superior tipo Advance Wars o Zelda, podemos elegir mostrar más área de juego pero en otros puede no serlo. Por ejemplo, en un Bubble Bobble o un FinalFight ver más cantidad de pantalla no es lo adecuado.

Con lo que la primera duda es: ¿programas pensando en 4:3 y desaprovechando panorámicos (cuando todos los monitores de PC son panorámicos)? ¿Se puede o debe hacer la inversa? (para panorámicos y recortar de alguna forma a 4:3).


Caso diferentes resoluciones:

Vas a diseñar un remake de un juego... ¿qué resolución eliges?

a.- Si eliges una resolución baja y fuerzas FULLSCREEN, en monitores grandes se ven píxeles como puños.

b.- Si eliges una resolución alta, puede haber equipos incapaces de moverlo.

Además, si no es la nativa del monitor y es LCD/TFT el resultado es difuso.

c- Si eliges soportar varias resoluciones, tienes 2 opciones:

c.1. Dar soporte a varias resoluciones preseleccionadas, con lo que tienes el problema de que necesitas varios sets de gráficos si quieres que el área de juego sea la misma en las diferentes resoluciones (y no que en cada resolución sea vea mayor área de juego con los gráficos más pequeños).

c.2. Dar soporte a todas las posibles resoluciones eligiendo una resolución base contra un buffer y escalando el volcado de ese buffer a la resolución de pantalla. De nuevo tienes que tener en cuenta además el aspect ratio para esto. Es decir, si queremos dar soporte no sólo al PC y a varias resoluciones concretas en 4:3 y 16:9, y queremos añadir iOS, Android, etc, la cantidad de resoluciones destino disponibles es enorme. Y puede que el dispositivo no sea lo bastante potente (y esto es CPU) para el escalado en tiempo real de todo el área de trabajo a la resolución destino.

Más posibilidades y problemas:


  • ¿Escalar los personajes? -> definir los gráficos para la mayor de las resoluciones y escalar para las menores o viceversa.
  • ¿Diferentes sets gráficos? -> requiere un enorme trabajo gráfico para juegos 2D.
  • ¿Gráficos vectoriales tipo svg? -> ¿Realmente se usa esta técnica?
  • La solución pueden ser los 2.5D (2D hecho con opengl renderizado con una cámara "lateral" o "superior").


Las múltiples resoluciones y aspect ratios también te obligan a adaptar la "velocidad" de los personajes según la resolución y los widgets del gui (menúes, marcadores, fuentes, etc). Esto no debería ser problema si las velocidades no se definen en "pixeles por segundo" sino en "unidades de movimiento del mundo virtual por segundo" (ej: metros por segundo) y después se escala lo que es "un metro" a las dimensiones en píxeles de los gráficos en la pantalla (según la resolución). Pero ya no es la facilidad de decir "esta nave se mueve 2 pixeles por segundo y esta mas rapido, a 3", hay que andar haciendo conversiones para todo lo que se va a trazar en pantalla.


¿Cuál es la mejor opción entonces para este problema?


  • Sets de gráficos -> "formatos gráficos pequeños + escalar hacia arriba" (entiendo que horrible a menos que quieras zoom de tipo 2x, 3x puro estilo retro), "formatos gráficos grandes + escalar hacia abajo" (entiendo que mejor, aunque el resultado del escalado sea "fuzzy"), "varios formatos (pequeño, mediano, grande) y escalar el más cercano", "2.5D y utilizar opengl"?.
  • 16:9 vs 4:3 -> ¿Marco? ¿centrar? ¿y si el juego no permite "dar más área visible?"
  • Menúes, fuentes, pantallas intermedias y de fin de fase -> ¿tenerlas en diferentes resoluciones? ¿de resolución fija y escalar?


PD: Veo que no soy el único que se plantea o se ha planteado estas preguntas:




Por desgracia, no veo muchas soluciones más allá de "ceñirse al dispositivo mayoritario al que apunta el juego y escalar al vuelo el buffer resultante para el resto, en cada fotograma"... o "hacer diferentes versiones (ipad, pc, android) centrando cada una en una resolución estándar".

Ya ni siquiera considerando Android, sino simplemente el PC, si tuviera que hacer un remake y tuviera que elegir una o varias resoluciones, me veo en un dilema por la existencia de 4:3/16:9/16:10 o de gente con monitores de portátil de 1280x800 y gente con monitores FullHD donde cualquier tamaño tipo 640x480 se vería horrible.

¿Opiniones?

2 comentarios:

Federico J. Álvarez Valero dijo...

Usa un framework, como hace la mayoría de la gente, y seguro que el framework resuelve ese problema (y muchos otros) por ti.

Regla número 1: no reinventar la rueda.

Federico J. Álvarez Valero dijo...

Y ahora que me he leído la entrada:

* Usar 2,5D.
* Usar coordenadas virtuales y reescalar lo que toque al generar el buffer.
* Respecto a las relaciones de aspecto, no creo que haya una solución perfecta. lo suyo es quedarte con una y poner bandas negras en el resto.