¿Cómo lidiar con imágenes arbitrariamente grandes en juegos 2D?

13

Pregunta

Cuando tienes un juego en 2D que usa imágenes realmente grandes (más grandes de lo que admite tu hardware), ¿qué haces? Quizás haya alguna solución interesante a este problema que nunca antes me haya ocurrido.

Básicamente estoy trabajando en una especie de creador de juegos de aventura gráfica. Mi público objetivo son personas con poca o ninguna experiencia en desarrollo de juegos (y programación). Si estuvieras trabajando con una aplicación de este tipo, ¿no preferirías que maneje el problema internamente en lugar de decirte que "dividas tus imágenes"?

Contexto

Es bien sabido que las tarjetas gráficas imponen un conjunto de restricciones en el tamaño de las texturas que pueden usar. Por ejemplo, cuando trabajas con XNA estás restringido a un tamaño de textura máximo de 2048 o 4096 píxeles dependiendo del perfil que elijas.

Por lo general, esto no representa un gran problema en los juegos 2D porque la mayoría de ellos tienen niveles que se pueden construir a partir de piezas individuales más pequeñas (por ejemplo, fichas o sprites transformados).

Pero piense en algunos juegos clásicos de aventura gráfica point'n'click (por ejemplo, Monkey Island). Las salas de los juegos de aventuras gráficas generalmente están diseñadas en su conjunto, con secciones pequeñas o nulas repetibles, como un pintor que trabaja en un paisaje. O tal vez un juego como Final Fantasy 7 que utiliza grandes fondos pre-renderizados.

Algunas de estas habitaciones pueden ser bastante grandes, con frecuencia abarcando tres o más pantallas de ancho. Ahora, si consideramos estos fondos en el contexto de un juego moderno de alta definición, superarán fácilmente el tamaño máximo de textura admitido.

Ahora, la solución obvia para esto es dividir el fondo en secciones más pequeñas que se ajusten a los requisitos y dibujarlas una al lado de la otra. Pero, ¿debería usted (o su artista) ser el que divide todas sus texturas?

Si está trabajando con una API de alto nivel, ¿no debería estar preparado para lidiar con esta situación y dividir y ensamblar las texturas internamente (ya sea como un proceso previo como el Canal de contenido de XNA o en tiempo de ejecución)?

Editar

Esto es lo que estaba pensando, desde el punto de vista de la implementación de XNA.

Mi motor 2D solo requiere un subconjunto muy pequeño de la funcionalidad de Texture2D. De hecho, puedo reducirlo a esta interfaz:

public interface ITexture
{
    int Height { get; }
    int Width { get; }
    void Draw(SpriteBatch spriteBatch, Vector2 position, Rectangle? source, Color  color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth);
}

El único método externo que uso que se basa en Texture2D es SpriteBatch.Draw () para poder crear un puente entre ellos agregando un método de extensión:

    public static void Draw(this SpriteBatch spriteBatch, ITexture texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
    {
        texture.Draw(spriteBatch, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
    }

Esto me permitiría usar una ITexture de manera intercambiable cada vez que use una Texture2D antes.

Entonces podría crear dos implementaciones diferentes de ITexture, por ejemplo, RegularTexture y LargeTexture, donde RegularTexture simplemente envolvería un Texture2D normal.

Por otro lado, LargeTexture mantendría una Lista de Texture2D cada una correspondiente a una de las piezas divididas de la imagen completa. La parte más importante es que llamar a Draw () en LargeTexture debería poder aplicar todas las transformaciones y dibujar todas las piezas como si fueran solo una imagen contigua.

Finalmente, para hacer que todo el proceso sea transparente, crearía una clase de cargador de texturas unificado que actuaría como un Método de Fábrica. Tomaría la ruta de la textura como parámetro y devolvería una ITexture que sería una RegularTexture o una LargeTexture, dependiendo de que el tamaño de la imagen exceda el límite o no. Pero el usuario no necesitaba saber cuál era.

¿Un poco exagerado o suena bien?

David Gouveia
fuente
1
Bueno, si desea ir con la división de la imagen, extienda la canalización de contenido para hacerlo mediante programación. No toda la funcionalidad existe para cubrir cada escenario y en algún momento tendrá que extenderla usted mismo. La división personal suena como la solución más simple, ya que puede manejar imágenes como mapas de mosaicos, en los que hay numerosos recursos.
DMan
Hacerlo en la tubería de contenido es el enfoque más sensato. Pero en mi caso, necesitaba cargar mis imágenes en tiempo de ejecución y ya he logrado encontrar una solución de tiempo de ejecución (es por eso que hice esta pregunta ayer). Pero la verdadera belleza viene de tomar este "mapa de mosaicos" y hacer que se comporte como una textura normal. Es decir, todavía puede pasarlo a spritebatch para dibujar, y aún puede especificar la posición, la rotación, la escala, los rectángulos src / dest, etc. Ya estoy a mitad de camino :)
David Gouveia
Pero la verdad sea dicha, ya estoy en un punto en el que me pregunto si todo eso realmente vale la pena o si simplemente debo mostrar una advertencia al usuario diciéndole que el tamaño máximo de imagen admitido es 2048x2048 y listo.
David Gouveia
Realmente depende de ti sobre el diseño. Si finaliza el proyecto y no puede desplazarse entre las habitaciones, será mejor que el editor no terminado que le permite desplazarse por texturas grandes: D
Aleks
Ocultarlo en la API parece una buena manera de facilitar la vida de los autores de juegos. Probablemente lo implementaría en las clases Polígono (sprite) y Textura, y no en casos especiales las imágenes más pequeñas, sino que simplemente las convertiría en un mosaico de 1x1. Entonces, la clase de textura es un grupo de textura que describe todos los mosaicos y cuántas filas / columnas. La clase Polygon luego mira esta información para dividirse y dibujar cada mosaico con la textura adecuada. De esa manera, es la misma clase. Puntos de bonificación si su clase de sprite solo dibuja mosaicos visibles, y los grupos de texturas no cargan texturas hasta que sean necesarias
uliwitness

Respuestas:

5

Creo que estás en el camino correcto. Tienes que dividir las imágenes en piezas más pequeñas. Como creas un creador de juegos, no puedes usar la canalización de contenido de XNA. A menos que su creador sea más como un motor que aún requerirá que los desarrolladores usen Visual Studio. Por lo tanto, debe crear sus propias rutinas que harán la división. Incluso debería considerar transmitir las piezas un poco antes de que se vuelvan visibles para que aún no ponga límites a la cantidad de memoria de video que deberían tener los reproductores.

Hay algunos trucos que puedes hacer específicamente para juegos de aventura. Me imagino que, como todos los juegos de aventura clásicos, tendrás pocas capas de estas texturas para que ... tu personaje pueda caminar detrás del árbol o del edificio ...

Si desea que estas capas tengan un efecto de paralaje, cuando divida sus mosaicos, omita todos los translúcidos una vez. Aquí debes pensar en el tamaño de los mosaicos. Los mosaicos más pequeños agregarán gastos generales de gestión y llamadas de extracción, pero serán menos datos donde los mosaicos más grandes serán más amigables con la GPU pero tendrán mucha translucidez.

Si no tiene un efecto de paralaje, entonces, en lugar de cubrir toda la escena con 2-4 imágenes gigantes que tienen una profundidad de 32 bits, puede crear solo una profundidad de 32 bits. Y puede tener una capa de plantilla o profundidad que será de solo 8 bits por píxel.

Sé que esto suena difícil de administrar por parte de desarrolladores que no son desarrolladores de juegos, pero en realidad no tiene que ser así. Por ejemplo, si tiene una calle con 3 árboles y el jugador va detrás de 2 de ellos y frente al tercer árbol y el resto del fondo ... Simplemente haga el diseño de la escena como desee en su editor y luego en un paso a paso de su creador de juegos, puede hornear estas imágenes gigantes (bueno, pequeños mosaicos de una imagen grande).

Sobre cómo lo hacían los juegos clásicos. No estoy seguro de que probablemente hicieran trucos similares, pero debes recordar que en el momento en que la pantalla era de 320x240, los juegos tenían 16 colores que, al usar texturas palattelizadas, te permiten codificar 2 píxeles en un solo byte. Pero las arquitecturas actuales no funcionan así: D

Buena suerte

Aleks
fuente
Gracias, ¡Muchas ideas muy interesantes aquí! De hecho, he visto la técnica de capa de plantilla utilizada antes en Adventure Game Studio. En cuanto a mi aplicación, estoy haciendo las cosas un poco diferente. Las habitaciones no necesariamente tienen una imagen de fondo. En cambio, hay una paleta de imágenes que importó el usuario y una capa de fondo / primer plano. El usuario es libre de arrastrar y soltar piezas en la sala para crearlo. Entonces, si quisiera, también podría componerlo de piezas más pequeñas sin tener que crear objetos de juego no interactivos. Sin embargo, no hay paralaje, ya que solo hay esas dos capas.
David Gouveia
Y he mirado los archivos de datos para Monkey Island Special Edition (la nueva versión de HQ). Cada fondo se divide exactamente en 1024x1024 piezas (incluso si una gran parte de la imagen no se usa). De todos modos, revise mi edición para saber qué hay en mente para que todo el proceso sea transparente.
David Gouveia
Probablemente elegiría algo más razonable como 256x256. De esta manera, no desperdiciaré recursos y si luego decido implementar la transmisión, cargará fragmentos más razonables. Tal vez no lo expliqué lo suficientemente bien, pero sugerí que podría hornear todos estos pequeños objetos estáticos del juego en una gran textura. Lo que guardará es, en caso de que tenga una gran textura de fondo, todos los píxeles cubiertos por los objetos estáticos en la parte superior.
Aleks
Ya veo, así que combine todos los objetos estáticos en el fondo, luego divídalos y cree un atlas de texturas. También podría hornear todo lo demás en un atlas de texturas siguiendo ese tren de pensamiento. Esa es una buena idea también. Pero lo dejaré para más tarde, porque en este momento no tengo ningún paso de "compilación", es decir, tanto el editor como el cliente del juego se ejecutan sobre el mismo motor y el mismo formato de datos.
David Gouveia
Me gusta el enfoque de plantilla, pero si estoy haciendo un editor de juegos de aventuras, probablemente no lo usaría ... Tal vez porque si estoy construyendo dicha herramienta, voy a estar destinado a los desarrolladores de juegos. Bueno, tiene sus pros y sus contras. Por ejemplo, puede usar otra imagen en la que los bits pueden definir disparadores por píxel o, si tiene un poco de agua animada, puede dibujarla fácilmente. Nota: Una sugerencia extraña no relacionada busca en los diagramas de estado / actividad de UML para sus secuencias de comandos.
Aleks
2

Córtelos para que ya no sean arbitrariamente grandes, como usted dice. No hay magia Esos viejos juegos 2D lo hicieron o se renderizaron en software, en cuyo caso no hubo restricciones de hardware más allá del tamaño de RAM. Es de destacar que muchos de esos juegos 2D realmente no tenían grandes fondos, simplemente se desplazaban lentamente para que se sintieran enormes.

Lo que está buscando hacer en su creador de juegos de aventuras gráficas es preprocesar esas imágenes grandes durante algún tipo de paso de "compilación" o "integración" antes de que el juego esté empaquetado para ejecutarse.

Si desea utilizar una API y una biblioteca existentes en lugar de escribir la suya, le sugiero que convierta esas imágenes grandes en mosaicos durante esa "compilación" y utilice un motor de mosaico 2D, del cual hay muchos.

Si desea utilizar sus propias técnicas 3D, es posible que quiera dividirse en mosaicos y usar una API 2D bien conocida y bien diseñada para escribir su propia biblioteca 3D para eso, de esta manera, al menos, está garantizado que al menos comenzará con un buen diseño de API y menos diseño requerido de su parte.

Patrick Hughes
fuente
Hacerlo en tiempo de compilación sería ideal ... Pero mi editor también usa XNA para renderizar, no es solo el motor. Eso significa que una vez que el usuario importa la imagen y la arrastra a una habitación, XNA ya debería poder renderizarla. Entonces, mi gran problema es si hacer la división en la memoria al cargar el contenido, o en el disco al importar el contenido. Además, dividir físicamente la imagen en múltiples archivos significaría que necesitaría una forma de almacenarlos de manera diferente, mientras que hacerlo en la memoria en tiempo de ejecución no requeriría ningún cambio en los archivos de datos del juego.
David Gouveia
1

Si está familiarizado con el aspecto de un quadtree en la práctica visual, he utilizado un método que corta imágenes detalladas grandes en imágenes más pequeñas, relativamente similares a cómo se ve una prueba / visualización de un quadtree. Sin embargo, los míos se hicieron de esa manera para cortar áreas específicamente que podrían codificarse o comprimirse de manera diferente según el color. Podría usar este método y hacer lo que describí, ya que también es útil.

En tiempo de ejecución, cortarlos requeriría más memoria temporalmente y reduciría el tiempo de carga. Sin embargo, diría que depende de si te importa más el almacenamiento físico o el tiempo de carga de un juego de usuarios hecho con tu editor.

Tiempo de carga / ejecución: la forma en que haría la carga es simplemente cortarlo en trozos de tamaño proporcional. Tenga en cuenta un píxel en la pieza más correcta si es un número impar de píxeles, a nadie le gusta que se corten sus imágenes, especialmente a los usuarios sin experiencia que tal vez no sepan que no depende totalmente de ellos. Haga que tengan la mitad del ancho O la mitad de la altura (o 3rds, 4tos, lo que sea), la razón por la que no está en cuadrados o 4 secciones (2 filas 2 columnas) es porque reduciría la memoria de mosaico ya que no se preocuparía por x e y al mismo tiempo (y dependiendo de cuántas imágenes sean tan grandes, podría ser bastante importante)

Antes de la mano / Automáticamente: en este caso, me gusta la idea de darle al usuario la opción de almacenarlo en una imagen o almacenarlo como piezas separadas (una imagen debería ser la predeterminada). Almacenar la imagen como un .gif funcionaría muy bien. La imagen estaría en un archivo, y en lugar de que cada cuadro sea una animación, sería más como un archivo comprimido de la misma imagen (que es esencialmente lo que es un .gif de todos modos). Permitiría la facilidad del transporte de archivos físicos, la facilidad de carga:

  1. Es un solo archivo
  2. Todos los archivos tendrían un formato casi idéntico
  3. Podrías elegir fácilmente cuándo Y si cortarlo en texturas individuales
  4. Puede ser más fácil acceder a las imágenes individuales si ya están en un único archivo .gif cargado, ya que no requeriría tantos archivos de imágenes en la memoria

Ah, y si usa el método .gif, debe cambiar la extensión del archivo a otra cosa (.gif -> .kittens) en aras de una menor confusión cuando su .gif se anima como si fuera un archivo roto. (no se preocupe por la legalidad de eso, .gif es un formato abierto)

Joshua Hedges
fuente
Hola y gracias por tu comentario! Lo siento si no entendí bien, pero ¿no es lo que escribiste principalmente relacionado con la compresión y el almacenamiento? En este caso, todo lo que realmente me preocupa es que el usuario de mi aplicación pueda importar cualquier imagen, y que el motor (en XNA) sea capaz de cargar y mostrar cualquier imagen en tiempo de ejecución . Me las arreglé para resolverlo simplemente usando GDI para leer diferentes secciones de la imagen en texturas XNA separadas y envolviéndolas dentro de una clase que se comporta como una textura regular (es decir, admite dibujos y transformaciones en su conjunto).
David Gouveia
Pero por curiosidad, ¿podría elaborar un poco más sobre qué criterios utilizó para seleccionar qué área de la imagen entró en cada sección?
David Gouveia
Algo de esto puede estar sobre tu cabeza si tienes menos experiencia con el manejo de imágenes. Pero esencialmente, estaba describiendo cortar la imagen en segmentos. Y sí, el punto .gif describía tanto el almacenamiento como la carga, pero está pensado como un método para cortar la imagen y luego almacenarla en la computadora como está. Si lo guarda como un gif, estaba diciendo que puede cortarlo en las imágenes individuales y luego guardarlo como un gif animado, con cada corte de la imagen guardado como cuadros de animación separados. Funciona bien e incluso agregué otras características con las que podría ayudar.
Joshua Hedges
El sistema quadtree, como lo describí, se establece completamente como una referencia, ya que es de donde saqué una idea. Aunque en lugar de llenar una pregunta posiblemente utilizable para lectores que puedan tener problemas similares, deberíamos usar mensajes privados si desea obtener más información.
Joshua Hedges
Roger, obtuve la parte sobre el uso de marcos GIF como una especie de archivo. Pero también me pregunto, ¿no es GIF un formato indexado con una paleta de colores limitada? ¿Puedo almacenar imágenes RGBA de 32bpp completas en cada cuadro? E imagino que el proceso de cargarlo termina siendo aún más complicado, ya que XNA no admite nada de esto.
David Gouveia
0

Esto va a sonar obvio, pero ¿por qué no dividir tu habitación en pedazos más pequeños? Elija un tamaño arbitrario que no choque con las tarjetas gráficas (como 1024x1024, o tal vez más grande) y simplemente divida sus habitaciones en varias piezas.

Sé que esto evita el problema y, sinceramente, este es un problema muy interesante de resolver. De todos modos, esta es una solución trivial, que puede funcionar algunas veces para usted.

cenizas999
fuente
Creo que podría haber mencionado la razón en alguna parte de este hilo, pero como no estoy seguro, repetiré. Básicamente porque no es un juego, sino más bien un creador de juegos para el público en general (piense en algo con el mismo alcance que el creador de juegos de rol), por lo que en lugar de forzar la limitación del tamaño de la textura cuando los usuarios importan sus propios activos, podría ser agradable manejarlo dividir y coser automáticamente detrás de escena sin molestarlos con los detalles técnicos de las limitaciones del tamaño de la textura.
David Gouveia
Vale, eso tiene sentido. Lo siento, supongo que me perdí eso.
cenizas999