Diseño de mapa completo vs. diseño de matriz de mosaicos

11

Estoy trabajando en un juego de rol 2D, que contará con los mapas habituales de mazmorras / ciudades (pregenerados).

Estoy usando fichas, que luego combinaré para hacer los mapas. Mi plan original era ensamblar los mosaicos usando Photoshop, o algún otro programa gráfico, para tener una imagen más grande que luego pudiera usar como mapa.

Sin embargo, he leído en varios lugares que la gente habla sobre cómo usaron matrices para construir su mapa en el motor (por lo que le da una matriz de mosaicos x a su motor y los ensambla como un mapa). Puedo entender cómo se hace, pero parece mucho más complicado de implementar, y no puedo ver las ventajas obvias.

¿Cuál es el método más común y cuáles son las ventajas / desventajas de cada uno?

Cristol.GdM
fuente
1
El problema principal es la memoria. Una textura que llena una pantalla de 1080p una vez es de aproximadamente 60mb. Entonces, una imagen usa un int por píxel, un mosaico usa un int por (píxel / (tilesize * tilesize)). Entonces, si los mosaicos son 32,32, puede representar un mapa 1024 veces más grande utilizando mosaicos frente a un píxel. También los tiempos de intercambio de texturas van a doler presionando tanta memoria, etc ...
ClassicThunder

Respuestas:

19

En primer lugar, permítanme decir que los juegos de rol en 2D son cercanos y queridos por mi corazón y que trabajan con viejos motores DX7 VB6 MORPG (no se rían, fue hace 8 años, ahora :-)) es lo que primero me interesó en el desarrollo del juego . Más recientemente, comencé a convertir un juego en el que trabajé en uno de esos motores para usar XNA.

Dicho esto, mi recomendación es que use una estructura basada en mosaicos con capas para su mapa. Con cualquier API de gráficos que use, tendrá un límite en el tamaño de las texturas que puede cargar. Sin mencionar los límites de memoria de textura de la tarjeta gráfica. Entonces, teniendo en cuenta esto, si desea maximizar el tamaño de sus mapas y no solo minimizar la cantidad y el tamaño de las texturas que carga en la memoria, sino también disminuir el tamaño de sus activos en el disco duro del usuario Y los tiempos de carga, está definitivamente va a querer ir con azulejos.

En cuanto a la implementación, he entrado en detalles sobre cómo lo manejé en algunas preguntas aquí en GameDev.SE y en mi blog (ambos enlazados a continuación), y eso no es exactamente lo que estás preguntando, así que simplemente entra en lo básico aquí. También tomaré nota de las características de los mosaicos que los hacen beneficiosos al cargar varias imágenes grandes renderizadas previamente. Si algo no está claro, hágamelo saber.

  1. Lo primero que debe hacer es crear una hoja de mosaico. Esta es solo una imagen grande que contiene todos sus mosaicos alineados en una cuadrícula. Esto (y tal vez uno adicional dependiendo del número de mosaicos) será lo único que necesitará cargar. ¡Solo 1 imagen! Puedes cargar 1 por mapa o uno con cada mosaico en el juego; cualquier organización que trabaje para usted.
  2. A continuación, debe comprender cómo puede tomar esa "hoja" y traducir cada mosaico en un número. Esto es bastante sencillo con algunas matemáticas simples. Tenga en cuenta que la división aquí es la división de enteros , por lo que los decimales se eliminan (o se redondean hacia abajo, si lo prefiere). Cómo convertir celdas en coordenadas y viceversa.
  3. Bien, ahora que ha dividido la hoja de mosaico en una serie de celdas (números), puede tomar esos números y conectarlos al contenedor que desee. En aras de la simplicidad, puede usar una matriz 2D.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. A continuación, quieres dibujarlos. Una de las formas en que puede hacer que esto sea MUCHO más eficiente (dependiendo del tamaño del mapa) es calcular solo las celdas que la cámara está viendo actualmente y recorrerlas. Puede hacer esto buscando las coordenadas de la matriz de mosaico del mapa de las esquinas superior izquierda ( tl) e inferior derecha ( br) de la cámara . Luego haga un bucle desde tl.X to br.Xy, en un bucle anidado, desde tl.Y to br.Ypara dibujarlos. Código de ejemplo a continuación:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
  5. ¡Bote! Eso es lo básico del motor de azulejos. Puede ver que es fácil tener incluso un mapa de 1000x1000 con poca sobrecarga. Además, si tiene menos de 255 mosaicos, puede usar una matriz de bytes que reduce la memoria en 3 bytes por celda. Si un byte es demasiado pequeño, un ushort probablemente sea suficiente para sus necesidades.

Nota: omití el concepto de coordenadas mundiales (que es en lo que se basará la posición de su cámara) ya que creo que está fuera del alcance de esta respuesta. Puedes leer sobre eso aquí en GameDev.SE.

Mis recursos de Tile-Engine
Nota: Todos estos están dirigidos a XNA, pero se aplica prácticamente a cualquier cosa: solo necesita cambiar las llamadas de extracción.

  • Mi respuesta a esta pregunta describe cómo manejo las celdas del mapa y las capas en mi juego. (Ver tercer enlace).
  • Mi respuesta a esta pregunta explica cómo almaceno los datos en formato binario.
  • Esta es la primera publicación de blog (bueno, técnicamente segunda, pero primera "técnica") sobre el desarrollo del juego en el que estaba trabajando. Toda la serie contiene información sobre cosas como sombreadores de píxeles, iluminación, comportamientos de mosaico, movimiento y todas esas cosas divertidas. De hecho, actualicé la publicación para incluir el contenido de mi respuesta al primer enlace que publiqué anteriormente, por lo que es posible que desee leerla en su lugar (es posible que haya agregado cosas). Si tiene alguna pregunta, puede dejar un comentario allí o aquí y con gusto lo ayudaré.

Otros recursos del motor de azulejos

  • El tutorial del motor de mosaico de este sitio me dio la base que usé para crear mis mapas.
  • Todavía no he visto estos tutoriales en video porque no he tenido tiempo, pero probablemente sean útiles. :) Sin embargo, pueden estar desactualizados si estás usando XNA.
  • Este sitio tiene algunos tutoriales más que (creo) se basan en los videos anteriores. Puede valer la pena echarle un vistazo.
Richard Marskell - Drackir
fuente
Waow, eso es dos veces más información de lo que pregunté, y prácticamente responde a todas las preguntas que no hice, genial, gracias :)
Cristol.GdM
@Mikalichov - ¡Me alegro de poder ayudar! :)
Richard Marskell - Drackir
Por lo general, con una matriz 2D desea utilizar la primera dimensión como su Y y la siguiente como su X. En la memoria, una matriz será secuencialmente 0,0-i luego 1,0-i. Si haces bucles anidados con X la primera gallina, en realidad estás saltando de un lado a otro en la memoria en lugar de seguir una ruta secuencial de lectura. Siempre para (y) luego para (x).
Brett W
@BrettW: un punto válido. Gracias. Me hizo preguntarme cómo se almacenan las matrices 2D en .Net (orden de filas principales. ¡Aprendí algo nuevo hoy! :-)). Sin embargo, después de actualizar mi código, me di cuenta de que es exactamente lo mismo que está describiendo, solo con las variables cambiadas. La diferencia es en qué orden se dibujan los mosaicos. Mi código de ejemplo actual dibuja de arriba a abajo, de izquierda a derecha, mientras que lo que describe dibujaría de izquierda a derecha, de arriba a abajo. Entonces, en aras de la simplicidad, decidí volver al original. :-)
Richard Marskell - Drackir
6

Tanto los sistemas basados ​​en mosaicos como un modelo estático / sistema de textura se pueden usar para representar un mundo y cada uno tiene diferentes puntos fuertes. Si uno es mejor que el otro se reduce a cómo usa las piezas y qué funciona mejor para su conjunto de habilidades y necesidades.

Dicho esto, ambos sistemas se pueden usar por separado o también juntos.

Un sistema basado en mosaico estándar tiene las siguientes fortalezas:

  • Matriz 2D simple de mosaicos
  • Cada azulejo tiene un solo material
    • Este material puede ser una textura única, múltiple o mezclada de azulejos circundantes
    • Puede reutilizar texturas para todos los mosaicos de ese tipo, reduciendo la creación de activos y el retrabajo
  • Cada mosaico puede ser transitable o no (detección de colisión)
  • Fácil de representar e identificar partes del mapa.
    • La posición del personaje y la posición del mapa son coordenadas absolutas
    • Simple para recorrer los mosaicos relevantes para la región de la pantalla

La desventaja de los mosaicos es que debe crear un sistema para construir estos datos basados ​​en mosaicos. Puede crear una imagen que use cada píxel como mosaico, creando así su matriz 2D a partir de una textura. También puede crear un formato propietario. Usando banderas de bits puede almacenar una gran cantidad de datos por mosaico en un espacio bastante pequeño.

La razón principal por la que la mayoría de las personas hacen mosaicos es porque les permite crear pequeños activos y reutilizarlos. Esto le permite crear una imagen mucho más grande con piezas más pequeñas. Reduce el retrabajo porque no está cambiando todo el mapa mundial para hacer un pequeño cambio. Por ejemplo, si quisieras cambiar la sombra de toda la hierba. En una imagen grande, tendrías que volver a pintar toda la hierba. En un sistema de mosaicos, simplemente actualiza los mosaicos de césped.

En general, es probable que haga mucho menos trabajo con un sistema basado en mosaicos que un gran mapa gráfico. Puede terminar usando un sistema basado en mosaicos para colisión, incluso si no lo usa para su mapa de tierra. El hecho de que use un mosaico internamente no significa que no pueda usar modelos para representar los objetos del entorno que pueden usar más de 1 mosaico para su espacio.

Debería proporcionar más detalles sobre su entorno / motor / idioma para proporcionar ejemplos de código.

Brett W
fuente
¡Gracias! Estoy trabajando en C #, y voy por una versión similar (pero menos talentosa) de Final Fantasy 6 (o básicamente la mayoría de Square SNES RPG), por lo que las baldosas Y las baldosas de estructura (es decir, casas). Mi mayor preocupación es que no veo cómo construir la matriz 2D sin pasar horas comprobando "hay una esquina de césped allí, luego tres horizontales rectas, luego una esquina de la casa, luego ...", con toneladas de posibles errores a lo largo del camino.
Cristol.GdM
Parece que Richard lo ha cubierto en términos de un código específico. Yo personalmente usaría una enumeración de tipos de mosaico y usaría banderas de bits para identificar el valor del mosaico. Esto le permite almacenar más de 32 valores en un entero promedio. También podrá almacenar múltiples banderas por mosaico. Entonces puede decir Grass = true, Wall = true, Collision = true, etc. sin variables separadas. Luego simplemente asigna "temas" que determinan la hoja de mosaico para los gráficos para esa región del mapa en particular.
Brett W
3

Dibujar el mapa: los mosaicos son fáciles para el motor, porque entonces el mapa se puede representar como una matriz gigante de índices de mosaico. El código de dibujo es solo un bucle anidado:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

Para mapas grandes, una imagen de mapa de bits enorme para todo el mapa podría ocupar mucho espacio en la memoria y podría ser más grande de lo que admite una tarjeta gráfica para un solo tamaño de imagen. Pero no tendrá problemas con los mosaicos porque solo asigna memoria gráfica una vez para cada mosaico (o un total, de manera óptima, si están todos en una hoja)

También es fácil reutilizar la información del mosaico para, por ejemplo, colisiones. por ejemplo, para obtener el mosaico del terreno en la esquina superior izquierda del sprite del personaje:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Suponga que necesita verificar colisiones contra rocas / árboles, etc. Puede obtener el mosaico en la posición de (posición del personaje + tamaño del sprite del personaje + dirección actual) y verificar si está marcado como transitable o no transitable.

Palanqueta
fuente